I also wrote a 10-line mock API wrapper that you can call as mockApi<SomeService["operationName"]>((request) => response), and it will type-check that your mock function implements the API correctly and return a function that looks exactly like the real API function.
[0]: https://github.com/ferdikoomen/openapi-typescript-codegen
I really like that the openAPI approach is language agnostic, and makes it relatively simple to support SDKs for many other languages if needed. For any company where the API itself is a product, OpenAPI is great.
Apologies if I've misunderstood your comment
I'm basically wondering what would help us produce (automatically?) a Python HTTP client based an OpenAPI spec, and achieve a development experience similar to using drwpow/openapi-typescript.
drwpow/openapi-typescript will generate the whole schema, along with paths, request and response models, as well as path and query parameters. In the IDE, all client methods will be type-safe and autocompleted.
Example:
client = createClient<GeneratedSchema>();
client.get("/my/autocompleted/endpoint/{some_param}", { params: { path: { some_param: "foobar" } } })
koxudaxi/datamodel-code-generator seems to generate the response models, but not much else. It seems we would have to manually write wrapper methods for each endpoint, manually specify the parameters and request models, and use a type hint for the return value of each method with the generated response model.Example:
def my_wrapped_endpoint(...) -> ResponseModel:
pass
client.my_wrapped_endpoint(some_param="foobar")
I'd like to avoid any manual wrapping or at least minimize the amount of it. Basically: to achieve a similar experience to what we have in TypeScript.I hope my question is a bit clearer now. I'm not that familiar with Python, so I will appreciate any guidance regarding this problem. :)
For all its issues, the JS world typically provides an excellent developer experience and takes types far more seriously than python.
Hope you find something that helps, and if you do I’d love to hear about it.
I’ve also spent some time on a Typescript type to X compiler. My first prototype is open source and targets Thrift, Proto3, Python, and JSON schema: https://github.com/justjake/ts-simple-type/tree/main/src/com...
I’m not happy with the design decision in that codebase to try to “simplify” Typescript types before compiling, and probably won’t continue that implementation, but we have a few internal code generators that consume TS types and output test data builders and model clases we use in production.
I want to open source some of those bits but haven’t found the time.
What happens if the Deepkit guy retires? What if I want to run my code without waiting for 11 minutes of typechecking? What if there’s a bug somewhere in there?
There’s way too much risk for me to consider Deepkit for production.
For those wanting less talk, moar code: https://github.com/fern-api/fern-java/blob/0.4.2-rc3/example... -> https://github.com/fern-api/fern-java/blob/0.4.2-rc3/example...
Regrettably, I wasn't able to readily find a matching openapi example, likely cause they really seem to believe their kid-gloves format is the future (or lock in, depending on how bitter one is feeling)
---
confusingly, their repo has a "YCombinator 2023" badge on it that just links to the badge itself. Some Algolia for Launch HN didn't cough up anything, but there was a Show HN I found: https://news.ycombinator.com/item?id=34346428
This is good feedback that we need to provide more examples starting with OpenAPI instead of with the Fern Definition. For some context, we convert the OpenAPI spec into a Fern definition and then pass that into the code generators.
If you want to see some real world examples, check out these links:
- https://github.com/vellum-ai/vellum-client-generator/tree/ma... -> https://github.com/vellum-ai/vellum-client-python
- https://github.com/Squidex/sdk-fern/tree/main/fern/api/opena... -> https://github.com/Squidex/sdk-node
Worth calling out that you can go from the Fern Definition -> OpenAPI anytime to prevent lock in.
https://github.com/transmission/transmission/blob/main/docs/...
Seems like you're just inserting yourself and ranting about typescript where it's not really applicable.
There's others expressing the same concerns, for example https://news.ycombinator.com/item?id=37101393
To me, if the server has updated it's schema and the client has old code, the server responds with an error, the user sees "something went wrong".
And if the validation fails on the client side, the user sees "something went wrong"
And if the client isn't doing validation of what's returned, and an error gets thrown because it tried to access a now missing field, the user sees ... "something went wrong"
Intentionally maintaining multiple versions of an API, or making sure your API changes are backwards compatible (like adding new fields and marking old ones as deprecated), those are solutions but definitely require organizational effort.
All totally expressible via Typescript though!
The benefit from validation is that the error is easily recognizable & can be handled nicely, even in just one place. Without validation, you get things that Typescript claims are impossible, for example exceptions on code paths that look like they cannot throw, and you can't easily tell why any of that happened -- that's a recipe for miserable debugging. Of course, with less busy, less critical, sites you might not notice or care.
[1]: Refresh could cause loss of user input (e.g. text just entered), depending on the app that might matter and need browser-side storage or something along the lines of the "local first" manifesto. Easy kludge for simple apps with smart users is to make the user copy the text, click refresh, paste.
One solution I thought was clever, was a friend's single-page app would hard-reload on navigation, if there was an update to the assets. Not sure how feasible that would be for schema errors though.
SvelteKit can poll to check server version and refresh, both on timer and if assets are 404. But as you implied, that doesn't help with API evolution and e.g. users interacting with a form (apart from increasing the likelihood the page will refresh soonish due to navigation).
The requests and responses are inferred from the schema defined in Zod (including the errors that are following HTTP-like conventions: 401, 409…).
It also includes “hooks” for pre and post processing on the server side (effectively middlewares)
One thing that worked surprisingly well: codegen TypeScript types from your database and use those in your API schema.
You can build this kind of thing yourself easily using Typescript’s mapped types, by building an object type where the keys are your API names, and the values are { request, response } types. Structure your “server” bits to define each API handler as a function taking APIs[“addUser”][“request”] and returning Promise<APIs[“addUser”][“response”]>. Then build a client that exposes each API as an async function with those same args and return type.
We use this strategy for our HTTPS internal API (transport over POST w/ JSON bodies), real-time APIs (transport over Websockets), Electron<>Webview APIs (transport over electron IPC), and Native (iOS, Android)<>Webview APIs (transport over OS webview ipc).
For native APIs, the “server” side of things is Swift or Kotlin, and in those cases we rewrite the request and response types from Typescript by hand. I’m sure at some point we’ll switch to a binary based format with its own IDL, but for a single cross-language API that grows slowly the developer experience overhead of Protobuf or similar hasn’t seemed worth it.