openapi-python-client icon indicating copy to clipboard operation
openapi-python-client copied to clipboard

Add object-oriented client option

Open fyhertz opened this issue 3 years ago • 5 comments

First of all, thank you for this great project, the code is very nice and I think it really has a lot of potential.

Is your feature request related to a problem? Please describe.

As mentioned in https://github.com/triaxtec/openapi-python-client/issues/171, the current approach arguably needs a little too much boilerplate that could be avoided by adding a more object oriented Client.

Describe the solution you'd like

  • Add two new client interfaces, SyncClient and AsyncClient that would wrap the currently generated tag packages in objects, and expose them as properties.

With that approach, a developer could call an endpoint with as little code as:

from petstore_client import SyncPetstoreClient  # Named after the spec title, nicer if you import several auto-generated clients
client = SyncPetstoreClient("http://mypetstore")
pets = client.pets.list_pets()
  • One issue is that we loose the ability to statically tell the user that some endpoint requires an AuthenticatedClient or not... Some thinking required here I guess.

  • Additionally, a default base_url could be provided through an environment variable named after the spec title, for instance: SWAGGER_PETSTORE_BASE_URL. It could be nice to prevent microservices from using different naming conventions.

For reference, my crude attempt at implementing those interfaces is available here: https://github.com/upciti/openapi-python-client/tree/feature/0-boilerplate-clients.

fyhertz avatar Oct 28 '20 13:10 fyhertz

Thanks for the suggestions @fyhertz. I think the best way to go forward with a more object oriented style is to have a separate set of templates for OO, rather than complicating existing generated clients. I do like the idea of the wrapper, I just don't see a world in which users would want both styles present rather than tuning for one or the other.

#231 has already started work toward alternate template support, I think we'd get that finished first then folks can either use their own custom templates (which I think a lot of users are itching to do) or select one of the included template sets.

dbanty avatar Nov 03 '20 16:11 dbanty

@dbanty I've followed some of your comments in various issues/PRs about what it would take to support very flexible templating. I don't know jinja too well so it sounds like it would be pretty tough. Could a compromise be to create a handler base class and then split off the hard-coded logic for the current template setup into a out-of-the-box handler for the default template setup? This client generator could then provide the equipment for building a library but not need to get into all the specifics of how a particular user may do it.

I ask because I forked the project and while a few of my modifications could be decent candidates for PRs, one isn't because it's specific to us: I patched in some methods that operate similar to the pyproject.toml builder but for mkdocs.yaml and the associated .md doc files. We could make this work if we implemented our own handler out of the base class and then crafted the templates as needed.

alexifm avatar Feb 19 '21 21:02 alexifm

@alexifm that's probable the way we'll have to go long term, yes. Without there being a Python-side implementation to drive things (what the Project class does today) it would be hard to properly orchestrate entirely different directory structures (as far as I know). Maybe there's some answer there in using a different templating language or by borrowing cookiecutter somehow?

The hard part will be deciding where to draw lines between shared core logic and useful, reusable components. Like do we leave all the type conversion / generation stuff in the core lib? Or pull that into the templates somewhere? Is generating models for endpoint inputs/outputs useful enough to include? If so how do we do that in a way that is useful to those building their own generators without getting in the way?

I don't have answers to any of the questions around a stable, extensible API. I do know we'll have to be careful when moving the project in that direction though. Part of the reason for this project existing was to make more opinionated, more Pythonic clients from a codebase which is easier to reason about for Python developers. I fear if we move too far in the flexibility direction, we'll end up being effectively the same as the Java tool except written in Python and supporting less of the OpenAPI spec 😅.

Having a stable way for people to build their own generators from this project is very far down the roadmap. Right now most of my focus is around making this tool more compliant with the spec and more accessible for the majority of users. That being said, having the ability to generate mkdocs for a generated client doesn't seem like a horrible option. If we were to add more documentation, that would probably the road we'd go down.

dbanty avatar Feb 19 '21 22:02 dbanty

For whatever my opinion is worth, I think you're right to favor being opinionated since you're making it conform to popular modern Python standards, as opposed to that official tool which looks like Python crafted by Java devs. That's why I gravitated to this library.

Having a stable way for people to build their own generators from this project is very far down the roadmap.

Sounds good. I just wanted to chime in based on our experience. Perhaps a refactoring that keeps the core functionality but opens up a hidden ability to adapt a handler base class that more in depth users could experiment with, kind of similar to the custom template path. We'd like to not use our fork. But maybe the changes can be brought in and more template customization won't be necessary.

That being said, having the ability to generate mkdocs for a generated client doesn't seem like a horrible option.

I'll start a PR with a sketch of how it could be done. It's pretty basic templating.

The other two things I added:

  • the description field for properties (forgive my potential bad terminology) from the OpenAPI schema was getting read by the schema object but not being passed to the property objects and thus wasn't available to use in any of the templates. I added this so that we could add parameter descriptions to a google-style docstring in the sync functions. This is needed for mkdocs and mkdocstrings to render as so:
image
  • an optional command line argument to pass in extra template arguments. it looks like .render(<usual args, if any>, **self.extra_template_args). It doesn't do anything if nothing is passed. Of course, there's no way to tell if an argument was expected in a template and wasn't passed but maybe that can be checked. This is not that important.

alexifm avatar Feb 20 '21 00:02 alexifm

If this can be of any interest to anyone, I've implemented something extremely similar to what OP described, even before reading this issue: https://github.com/ramnes/sftpgo-client/blob/0.2.3/sftpgo_client/init.py

It does a quite bit of Python magic, which I do not particularly like, and add a few things related to SFTPGo. The good part is that it just relies on the existence of sync and asyncio functions, and should be pretty resilient to most changes here.

ramnes avatar Mar 30 '21 18:03 ramnes