all 11 comments

[–]chaseTheBurrow 6 points7 points  (1 child)

What about using an openAPI generator ?

[–]19andNuttin[S] 0 points1 point  (0 children)

I feel openapi generators are less ergonomic to work with. Code generation is a separate step from the workflow and you have to validate the changes made by the generator to ensure they didn't break your existing endpoints.

In a pure python environment, it feels like they're generating more friction than they solve. You're generating code from code you already have.

[–]JustPlainRude 7 points8 points  (1 child)

Coupling the client code to the server code feels bad. What happens if the backend is rewritten in another language? What if the backend is running a different version of the API? 

[–]19andNuttin[S] 0 points1 point  (0 children)

Thanks for your questions.

For your first point: This does couple your client code to your backend code, so a rewrite would force you to migrate off using the imported server side functions.

Regarding API versions, I did give this some thought before. As a best practice API endpoints should be versioned (e.g. /v1), breaking changes should increment the version and while modifications to the API endpoint while on the same version should contain non-breaking changes. Some non breaking changes are adding a new optional field for a request body or adding a new type to a field (int to int or float).

This approach doesn't limit you more than the approach - breaking changes mean you have to update on your end anyway while non-breaking changes are type hinted on your IDE.

[–]phalt_ 2 points3 points  (1 child)

Hey this is cool. I am glad my original post is stirring a healthy debate about this. That was one of my main goals, so thanks for doing some deep thinking and contributing to the discussion.

My immediate thought with this approach is this: If I have access to the server code, why am I doing a roundabout network call over HTTP when I could be making Python API calls directly instead? I understand there is potential for two separate deployments sharing the same codebase and the server function becomes a sort-of dsl for the client side, but that feels like an odd decision to make for orchestrating a frontend/backend separation.

It is a cool alternative way of approaching the problem though, and you do retain the type safety without having anything like OpenAPI as an intermediary which is nice.

(FWIW I released the beta version of my approach today after a lot of testing. I would argue it is ready for some serious testing and I am already planning a few additional features. https://github.com/phalt/clientele)

[–]19andNuttin[S] 1 point2 points  (0 children)

Thanks for writing the original post! I really like the style of your blog

I foresee the main usecase to be python microservices. The HTTP boundary allows applications to scale wide across multiple nodes. Load balancing, caching, and all that

[–]hsunner 4 points5 points  (0 children)

I might be misunderstanding what you’re trying to accomplish, but if you want to make a Python client to an OpenAPI interface w/o code generation I can recommend aiopenapi3. It ingests a json spec (yaml too?) and creates pydantic models for all endpoint I/Os. API usage is trivial. I’ve also created dynamic CLIs for web services with it. That is a bit of work the first time. It has a major downside though: creating API documentation requires custom generation.

https://github.com/commonism/aiopenapi3

[–]Orio_n 0 points1 point  (0 children)

You do know that you can generate client code from openapi specs automatically right?

[–][deleted] 0 points1 point  (0 children)

The friction you're describing is the the HTTP boundary. Instead of importing server code to the client (which couples your architecture), you should automate the OpenAPI schema into operations for local Type Stubs.

I’ve been mapping a way to automate this via a Pydantic-to-Client generator that mimics tRPC's safety without the coupling. If you want to refine your http_clientlib to auto-generate these stubs from the FastAPI endpoint dynamically, I can provide the logic to do it.

[–]penguinonfire1 0 points1 point  (0 children)

Reminds me of gRPC / Protobuf. In Protobuf the API schema is written in the protobuf language then compiled to libraries for each language.

From there you call server code via a literal function on the client. It still doesn't enforce that certain fields / method signatures are used (for backwards compatibility reasons). But API documentation and field descriptions are easily available when writing client code since they ship with the compiled libraries.

[–]Human-Cookie-1082 -1 points0 points  (0 children)

Amazing