diff --git a/CHANGELOG/index.html b/CHANGELOG/index.html index 32519ff..91bdedd 100644 --- a/CHANGELOG/index.html +++ b/CHANGELOG/index.html @@ -298,6 +298,13 @@
Clientele lets you generate fully-typed, functional, API Clients from OpenAPI schemas.
It uses modern tools to be blazing fast and type safe.
Plus - there is no complex boilerplate and the generated code is very small.
"},{"location":"#features","title":"Features","text":"httpx
and pydantic
.We're built on:
lower_case_snake_case
).MANIFEST
file with information about the build versionsconstants.py
file to the output if one does not exist yet, which can be used to store values that you do not want to change between subsequent re-generations of the clientele client, such as the API base url.constants.py
for constants values.ipython
from package dependencies and moved to dev dependencies.Microsoft.OpenApi.Models
)When we were building Clientele, we discovered that, despite a fantastic specification, OpenAPI has a lot of poor implementations.
As pythonistas, we started with the auto-generated OpenAPI schemas provided by FastAPI, and then we branched out to large APIs like Twilio to test what we built.
Despite the effort, we still keep finding subtly different OpenAPI implementations. Because of this we cannot guarentee 100% compatability with an API, but we can give you a good indication of what we've tested.
"},{"location":"compatibility/#works-well-with","title":"Works well with","text":"Any bog-standard 3.0.x
implementation works very well.
We do not support 2.x
aka \"Swagger\" - this format is quite different and deprecated.
Let's build an API Client using clientele and an example OpenAPI schema.
Our GitHub has a bunch of schemas that are proven to work with clientele, so let's use one of those!
"},{"location":"examples/#generate-the-client","title":"Generate the client","text":"Very simply:
clientele generate -u https://raw.githubusercontent.com/beckett-software/clientele/main/example_openapi_specs/simple.json -o my_client/\n
The -u
parameter expects a URL, you can provide a path to a file with -f
instead if you download the file.
The -o
parameter is the output directory of the generated client.
Run it now and you will see this output:
my_client/\n __init__.py\n client.py\n constants.py\n http.py\n MANIFEST\n schemas.py\n
"},{"location":"examples/#client-directory","title":"Client directory","text":"Let's go over each file and talk about what it does
"},{"location":"examples/#clientpy","title":"client.py","text":"This file provides all the client functions.
my_client/client.pyimport typing # noqa\nfrom . import schemas # noqa\nfrom . import http # noqa\ndef HealthCheckHealthCheckGet() -> schemas.HealthCheckResponse:\nresponse = http.get(\"/health-check\")\nreturn http.handle_response(HealthCheckHealthCheckGet, response)\ndef TestInputTestInputPost(\ndata: schemas.TestInputData,\n) -> typing.Union[schemas.HTTPValidationError, schemas.TestInputResponse]:\nresponse = http.post(\"/test-input\", data=data and data.model_dump())\nreturn http.handle_response(TestInputTestInputPost, response) \n
We can see one of the functions here, HealthCheckHealthCheckGet
, is for a straight-forward HTTP GET request without any input arguments, and it returns a schema object. If the endpoint has url parameters or query parameters, they will appear as input arguments to the function.
def HealthCheckHealthCheckGet() -> schemas.HealthCheckResponse:\nresponse = http.get(\"/health-check\")\nreturn http.handle_response(HealthCheckHealthCheckGet, response)\n
Here is how you might use it:
from my_client import client\nclient.HealthCheckHealthCheckGet()\n>>> HealthCheckResponse(status='ok')\n
A more complex example is shown just below - TestInputTestInputPost
, this is for an HTTP POST method, and it requires an input property called data
that is an instance of a schema, and returns a union of responses. If the endpoint has url parameters or query parameters, they will appear as input arguments to the function alongside the data
argument.
def TestInputTestInputPost(\ndata: schemas.TestInputData,\n) -> typing.Union[schemas.HTTPValidationError, schemas.TestInputResponse]:\nresponse = http.post(\"/test-input\", data=data and data.model_dump())\nreturn http.handle_response(TestInputTestInputPost, response) \n
Here is how you might use it:
from my_client import client, schemas\ndata = schemas.TestInputData(my_title=\"Hello, world\")\nclient.TestInputTestInputPost(data=data)\n>>> TestInputResponse(title='Hello, world')\n
Because we're using Pydantic to manage the input data, we get a strongly-typed interface for us.
"},{"location":"examples/#schemaspy","title":"schemas.py","text":"This file has all the possible schemas, request and response, for the API.
They are all subclassed from pydantic's BaseModel
. Here are a few examples:
import typing # noqa\nfrom pydantic import BaseModel # noqa\nfrom enum import Enum # noqa\nclass HTTPValidationError(BaseModel):\ndetail: typing.List[typing.Any]\nclass HealthCheckResponse(BaseModel):\nstatus: str\nclass TestInputData(BaseModel):\nmy_title: str\nclass TestInputResponse(BaseModel):\ntitle: str\nclass ValidationError(BaseModel):\nloc: typing.List[typing.Any]\nmsg: str\ntype: str\n
"},{"location":"examples/#httppy","title":"http.py","text":"This file manages the HTTP layer of the client.
You should never need to touch the code here, but feel free to have a browse.
This file will have either a sync, or an async instance of a client from httpx
.
It also manages the coersion of response bodies into schema objects.
"},{"location":"examples/#constantspy","title":"constants.py","text":"This manages all the configuration your client might need.
This file is only ever generated once, and will never be overwritten (unless you delete it yourself).
You can modify the contents of the functions in this file to provide things like the base url and authentication keys for your client.
"},{"location":"examples/#manifest","title":"MANIFEST","text":"The MANIFEST
file provides some information on how the client was generated.
We recommend installing with pip globally.
sudo pip install clientele\n
"},{"location":"usage/","title":"\ud83d\udcdd Usage","text":"Clientele provides a single command, generate
, for generating your API Clients.
Assuming the OpenAPI schema is available on the internet somewhere, you can query it to generate your client.
clientele generate -u https://raw.githubusercontent.com/beckett-software/clientele/main/example_openapi_specs/simple.json -o my_client/\n
Note
The example above uses a test OpenAPI format, and will work if you copy/paste it!
"},{"location":"usage/#from-a-file","title":"From a file","text":"Alternatively, if you have a local file you can use it to generate your client.
clientele generate -f path/to/file.json -o my_client/\n
"},{"location":"usage/#async-client","title":"Async Client","text":"If you prefer an asyncio client, just pass --asyncio t
to your command.
clientele generate -f path/to/file.json -o my_client/ --asyncio t\n
Note
You can use this command later to swap between a sync and async client so long as the OpenAPI schema remains the same, so don't worry about making a hard decision now.
"},{"location":"usage/#authentication","title":"Authentication","text":"If your OpenAPI spec provides security information for the following authentication methods:
Then clientele will provide you information on the environment variables you need to set to make this work during the generation. For example:
Please see my_client/constants.py to set authentication variables\n
The constants.py
file will have entry points for you to configure, for example, HTTP Bearer authentication will need the get_bearer_token
function to be updated, something like this:
def get_bearer_token() -> str:\n\"\"\"\n HTTP Bearer authentication.\n Used by many authentication methods - token, jwt, etc.\n Does not require the \"Bearer\" content, just the key as a string.\n \"\"\"\nfrom os import environ\nreturn environ.get(\"MY_AUTHENTICATION_TOKEN\")\n
"},{"location":"usage/#configuration","title":"Configuration","text":"One of the problems with auto-generated clients is that you often need to configure them, and if you try and regenerate the client at some point (say because you've added new endpoints or fixed a bug) then your configuration gets wiped clean and you have to do it all over again.
Clientele solves this problem by providing an entry point for configuration that will never be overwritten - constants.py
.
When you first generate the project, you will see a file called my_client/constants.py
(assuming your -o
was my_client/
), and it'll look a bit like this:
\"\"\"\nThis file will never be updated on subsequent clientele runs.\nUse it as a space to store configuration and constants.\nDO NOT CHANGE THE FUNCTION NAMES\n\"\"\"\ndef api_base_url() -> str:\n\"\"\"\n Modify this function to provide the current api_base_url.\n \"\"\"\nreturn \"http://localhost\"\n
Subsequent runs of the generate
command will not change this file the first time is made, so you are free to modify the defaults to suit your needs, for example, if you need to source the base url of your API for different configurations, you can modify the api_base_url
function like this:
from my_project import my_config\ndef api_base_url() -> str:\n\"\"\"\n Modify this function to provide the current api_base_url.\n \"\"\"\nif my_config.debug:\nreturn \"http://localhost:8000\"\nelif my_config.production:\nreturn \"http://my-production-url.com\"\n
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"\u269c\ufe0f Clientele","text":""},{"location":"#typed-api-clients-from-openapi-schemas","title":"Typed API Clients from OpenAPI schemas","text":"Clientele lets you generate fully-typed, functional, API Clients from OpenAPI schemas.
It uses modern tools to be blazing fast and type safe.
Plus - there is no complex boilerplate and the generated code is very small.
"},{"location":"#features","title":"Features","text":"httpx
and pydantic
.We're built on:
lower_case_snake_case
).MANIFEST
file with information about the build versionsconstants.py
file to the output if one does not exist yet, which can be used to store values that you do not want to change between subsequent re-generations of the clientele client, such as the API base url.constants.py
for constants values.ipython
from package dependencies and moved to dev dependencies.Microsoft.OpenApi.Models
)When we were building Clientele, we discovered that, despite a fantastic specification, OpenAPI has a lot of poor implementations.
As pythonistas, we started with the auto-generated OpenAPI schemas provided by FastAPI, and then we branched out to large APIs like Twilio to test what we built.
Despite the effort, we still keep finding subtly different OpenAPI implementations. Because of this we cannot guarentee 100% compatability with an API, but we can give you a good indication of what we've tested.
"},{"location":"compatibility/#works-well-with","title":"Works well with","text":"Any bog-standard 3.0.x
implementation works very well.
We do not support 2.x
aka \"Swagger\" - this format is quite different and deprecated.
Let's build an API Client using clientele and an example OpenAPI schema.
Our GitHub has a bunch of schemas that are proven to work with clientele, so let's use one of those!
"},{"location":"examples/#generate-the-client","title":"Generate the client","text":"Very simply:
clientele generate -u https://raw.githubusercontent.com/beckett-software/clientele/main/example_openapi_specs/simple.json -o my_client/\n
The -u
parameter expects a URL, you can provide a path to a file with -f
instead if you download the file.
The -o
parameter is the output directory of the generated client.
Run it now and you will see this output:
my_client/\n __init__.py\n client.py\n constants.py\n http.py\n MANIFEST\n schemas.py\n
"},{"location":"examples/#client-directory","title":"Client directory","text":"Let's go over each file and talk about what it does
"},{"location":"examples/#clientpy","title":"client.py","text":"This file provides all the client functions.
my_client/client.pyimport typing # noqa\nfrom . import schemas # noqa\nfrom . import http # noqa\ndef HealthCheckHealthCheckGet() -> schemas.HealthCheckResponse:\nresponse = http.get(\"/health-check\")\nreturn http.handle_response(HealthCheckHealthCheckGet, response)\ndef TestInputTestInputPost(\ndata: schemas.TestInputData,\n) -> typing.Union[schemas.HTTPValidationError, schemas.TestInputResponse]:\nresponse = http.post(\"/test-input\", data=data and data.model_dump())\nreturn http.handle_response(TestInputTestInputPost, response) \n
We can see one of the functions here, HealthCheckHealthCheckGet
, is for a straight-forward HTTP GET request without any input arguments, and it returns a schema object. If the endpoint has url parameters or query parameters, they will appear as input arguments to the function.
def HealthCheckHealthCheckGet() -> schemas.HealthCheckResponse:\nresponse = http.get(\"/health-check\")\nreturn http.handle_response(HealthCheckHealthCheckGet, response)\n
Here is how you might use it:
from my_client import client\nclient.HealthCheckHealthCheckGet()\n>>> HealthCheckResponse(status='ok')\n
A more complex example is shown just below - TestInputTestInputPost
, this is for an HTTP POST method, and it requires an input property called data
that is an instance of a schema, and returns a union of responses. If the endpoint has url parameters or query parameters, they will appear as input arguments to the function alongside the data
argument.
def TestInputTestInputPost(\ndata: schemas.TestInputData,\n) -> typing.Union[schemas.HTTPValidationError, schemas.TestInputResponse]:\nresponse = http.post(\"/test-input\", data=data and data.model_dump())\nreturn http.handle_response(TestInputTestInputPost, response) \n
Here is how you might use it:
from my_client import client, schemas\ndata = schemas.TestInputData(my_title=\"Hello, world\")\nclient.TestInputTestInputPost(data=data)\n>>> TestInputResponse(title='Hello, world')\n
Because we're using Pydantic to manage the input data, we get a strongly-typed interface for us.
"},{"location":"examples/#schemaspy","title":"schemas.py","text":"This file has all the possible schemas, request and response, for the API.
They are all subclassed from pydantic's BaseModel
. Here are a few examples:
import typing # noqa\nfrom pydantic import BaseModel # noqa\nfrom enum import Enum # noqa\nclass HTTPValidationError(BaseModel):\ndetail: typing.List[typing.Any]\nclass HealthCheckResponse(BaseModel):\nstatus: str\nclass TestInputData(BaseModel):\nmy_title: str\nclass TestInputResponse(BaseModel):\ntitle: str\nclass ValidationError(BaseModel):\nloc: typing.List[typing.Any]\nmsg: str\ntype: str\n
"},{"location":"examples/#httppy","title":"http.py","text":"This file manages the HTTP layer of the client.
You should never need to touch the code here, but feel free to have a browse.
This file will have either a sync, or an async instance of a client from httpx
.
It also manages the coersion of response bodies into schema objects.
"},{"location":"examples/#constantspy","title":"constants.py","text":"This manages all the configuration your client might need.
This file is only ever generated once, and will never be overwritten (unless you delete it yourself).
You can modify the contents of the functions in this file to provide things like the base url and authentication keys for your client.
"},{"location":"examples/#manifest","title":"MANIFEST","text":"The MANIFEST
file provides some information on how the client was generated.
We recommend installing with pip globally.
sudo pip install clientele\n
"},{"location":"usage/","title":"\ud83d\udcdd Usage","text":"Clientele provides a single command, generate
, for generating your API Clients.
Assuming the OpenAPI schema is available on the internet somewhere, you can query it to generate your client.
clientele generate -u https://raw.githubusercontent.com/beckett-software/clientele/main/example_openapi_specs/simple.json -o my_client/\n
Note
The example above uses a test OpenAPI format, and will work if you copy/paste it!
"},{"location":"usage/#from-a-file","title":"From a file","text":"Alternatively, if you have a local file you can use it to generate your client.
clientele generate -f path/to/file.json -o my_client/\n
"},{"location":"usage/#async-client","title":"Async Client","text":"If you prefer an asyncio client, just pass --asyncio t
to your command.
clientele generate -f path/to/file.json -o my_client/ --asyncio t\n
Note
You can use this command later to swap between a sync and async client so long as the OpenAPI schema remains the same, so don't worry about making a hard decision now.
"},{"location":"usage/#authentication","title":"Authentication","text":"If your OpenAPI spec provides security information for the following authentication methods:
Then clientele will provide you information on the environment variables you need to set to make this work during the generation. For example:
Please see my_client/constants.py to set authentication variables\n
The constants.py
file will have entry points for you to configure, for example, HTTP Bearer authentication will need the get_bearer_token
function to be updated, something like this:
def get_bearer_token() -> str:\n\"\"\"\n HTTP Bearer authentication.\n Used by many authentication methods - token, jwt, etc.\n Does not require the \"Bearer\" content, just the key as a string.\n \"\"\"\nfrom os import environ\nreturn environ.get(\"MY_AUTHENTICATION_TOKEN\")\n
"},{"location":"usage/#configuration","title":"Configuration","text":"One of the problems with auto-generated clients is that you often need to configure them, and if you try and regenerate the client at some point (say because you've added new endpoints or fixed a bug) then your configuration gets wiped clean and you have to do it all over again.
Clientele solves this problem by providing an entry point for configuration that will never be overwritten - constants.py
.
When you first generate the project, you will see a file called my_client/constants.py
(assuming your -o
was my_client/
), and it'll look a bit like this:
\"\"\"\nThis file will never be updated on subsequent clientele runs.\nUse it as a space to store configuration and constants.\nDO NOT CHANGE THE FUNCTION NAMES\n\"\"\"\ndef api_base_url() -> str:\n\"\"\"\n Modify this function to provide the current api_base_url.\n \"\"\"\nreturn \"http://localhost\"\n
Subsequent runs of the generate
command will not change this file the first time is made, so you are free to modify the defaults to suit your needs, for example, if you need to source the base url of your API for different configurations, you can modify the api_base_url
function like this:
from my_project import my_config\ndef api_base_url() -> str:\n\"\"\"\n Modify this function to provide the current api_base_url.\n \"\"\"\nif my_config.debug:\nreturn \"http://localhost:8000\"\nelif my_config.production:\nreturn \"http://my-production-url.com\"\n
"}]}
\ No newline at end of file
diff --git a/sitemap.xml b/sitemap.xml
index 611667e..a22517e 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -2,32 +2,32 @@