Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ dmypy.json
htmlcov/

# Generated end to end test data
my-test-api-client
tmp/my-test-api-client
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ get an error.

> For more usage details run `openapi-python-client --help` or read [usage](usage.md)


### Using custom templates

This feature leverages Jinja2's [ChoiceLoader](https://jinja.palletsprojects.com/en/2.11.x/api/#jinja2.ChoiceLoader) and [FileSystemLoader](https://jinja.palletsprojects.com/en/2.11.x/api/#jinja2.FileSystemLoader). This means you do _not_ need to customize every template. Simply copy the template(s) you want to customize from [the default template directory](openapi_python_client/templates) to your own custom template directory (file names _must_ match exactly) and pass the template directory through the `custom_template_path` flag to the `generate` and `update` commands. For instance,

```
openapi-python-client update \
--url https://my.api.com/openapi.json \
--custom-template-path=relative/path/to/mytemplates
```

_Be forewarned, this is a beta-level feature in the sense that the API exposed in the templates is undocumented and unstable._

## What You Get

1. A `pyproject.toml` file with some basic metadata intended to be used with [Poetry].
Expand Down
23 changes: 23 additions & 0 deletions end_to_end_tests/golden-record-custom/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
__pycache__/
build/
dist/
*.egg-info/
.pytest_cache/

# pyenv
.python-version

# Environments
.env
.venv

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# JetBrains
.idea/

/coverage.xml
/.coverage
61 changes: 61 additions & 0 deletions end_to_end_tests/golden-record-custom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# my-test-api-client
A client library for accessing My Test API

## Usage
First, create a client:

```python
from my_test_api_client import Client

client = Client(base_url="https://api.example.com")
```

If the endpoints you're going to hit require authentication, use `AuthenticatedClient` instead:

```python
from my_test_api_client import AuthenticatedClient

client = AuthenticatedClient(base_url="https://api.example.com", token="SuperSecretToken")
```

Now call your endpoint and use your models:

```python
from my_test_api_client.models import MyDataModel
from my_test_api_client.api.my_tag import get_my_data_model

my_data: MyDataModel = get_my_data_model(client=client)
```

Or do the same thing with an async version:

```python
from my_test_api_client.models import MyDataModel
from my_test_api_client.async_api.my_tag import get_my_data_model

my_data: MyDataModel = await get_my_data_model(client=client)
```

Things to know:
1. Every path/method combo becomes a Python function with type annotations.
1. All path/query params, and bodies become method arguments.
1. If your endpoint had any tags on it, the first tag will be used as a module name for the function (my_tag above)
1. Any endpoint which did not have a tag will be in `my_test_api_client.api.default`
1. If the API returns a response code that was not declared in the OpenAPI document, a
`my_test_api_client.api.errors.ApiResponseError` wil be raised
with the `response` attribute set to the `httpx.Response` that was received.


## Building / publishing this Client
This project uses [Poetry](https://python-poetry.org/) to manage dependencies and packaging. Here are the basics:
1. Update the metadata in pyproject.toml (e.g. authors, version)
1. If you're using a private repository, configure it with Poetry
1. `poetry config repositories.<your-repository-name> <url-to-your-repository>`
1. `poetry config http-basic.<your-repository-name> <username> <password>`
1. Publish the client with `poetry publish --build -r <your-repository-name>` or, if for public PyPI, just `poetry publish --build`

If you want to install this client into another project without publishing it (e.g. for development) then:
1. If that project **is using Poetry**, you can simply do `poetry add <path-to-this-client>` from that project
1. If that project is not using Poetry:
1. Build a wheel with `poetry build -f wheel`
1. Install that wheel from the other project `pip install <path-to-wheel>`
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
""" A client library for accessing My Test API """
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
""" Contains methods for accessing the API """
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import Optional

import httpx

Client = httpx.Client


def _parse_response(*, response: httpx.Response) -> Optional[bool]:
if response.status_code == 200:
return bool(response.text)
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[bool]:
return httpx.Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
parsed=_parse_response(response=response),
)


def httpx_request(
*,
client: Client,
) -> httpx.Response[bool]:

response = client.request(
"get",
"/ping",
)

return _build_response(response=response)
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from typing import Optional

import httpx

Client = httpx.Client

import datetime
from typing import Dict, List, Optional, Union, cast

from dateutil.parser import isoparse

from ...models.an_enum import AnEnum
from ...models.http_validation_error import HTTPValidationError


def _parse_response(*, response: httpx.Response) -> Optional[Union[None, HTTPValidationError]]:
if response.status_code == 200:
return None
if response.status_code == 422:
return HTTPValidationError.from_dict(cast(Dict[str, Any], response.json()))
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[Union[None, HTTPValidationError]]:
return httpx.Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
parsed=_parse_response(response=response),
)


def httpx_request(
*,
client: Client,
json_body: Dict[Any, Any],
string_prop: Optional[str] = "the default string",
datetime_prop: Optional[datetime.datetime] = isoparse("1010-10-10T00:00:00"),
date_prop: Optional[datetime.date] = isoparse("1010-10-10").date(),
float_prop: Optional[float] = 3.14,
int_prop: Optional[int] = 7,
boolean_prop: Optional[bool] = False,
list_prop: Optional[List[AnEnum]] = None,
union_prop: Optional[Union[Optional[float], Optional[str]]] = "not a float",
enum_prop: Optional[AnEnum] = None,
) -> httpx.Response[Union[None, HTTPValidationError]]:

json_datetime_prop = datetime_prop.isoformat() if datetime_prop else None

json_date_prop = date_prop.isoformat() if date_prop else None

if list_prop is None:
json_list_prop = None
else:
json_list_prop = []
for list_prop_item_data in list_prop:
list_prop_item = list_prop_item_data.value

json_list_prop.append(list_prop_item)

if union_prop is None:
json_union_prop: Optional[Union[Optional[float], Optional[str]]] = None
elif isinstance(union_prop, float):
json_union_prop = union_prop
else:
json_union_prop = union_prop

json_enum_prop = enum_prop.value if enum_prop else None

params: Dict[str, Any] = {}
if string_prop is not None:
params["string_prop"] = string_prop
if datetime_prop is not None:
params["datetime_prop"] = json_datetime_prop
if date_prop is not None:
params["date_prop"] = json_date_prop
if float_prop is not None:
params["float_prop"] = float_prop
if int_prop is not None:
params["int_prop"] = int_prop
if boolean_prop is not None:
params["boolean_prop"] = boolean_prop
if list_prop is not None:
params["list_prop"] = json_list_prop
if union_prop is not None:
params["union_prop"] = json_union_prop
if enum_prop is not None:
params["enum_prop"] = json_enum_prop

json_json_body = json_body

response = client.request(
"post",
"/tests/defaults",
json=json_json_body,
params=params,
)

return _build_response(response=response)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import Optional

import httpx

Client = httpx.Client


def _parse_response(*, response: httpx.Response) -> Optional[List[bool]]:
if response.status_code == 200:
return [bool(item) for item in cast(List[bool], response.json())]
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[List[bool]]:
return httpx.Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
parsed=_parse_response(response=response),
)


def httpx_request(
*,
client: Client,
) -> httpx.Response[List[bool]]:

response = client.request(
"get",
"/tests/basic_lists/booleans",
)

return _build_response(response=response)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import Optional

import httpx

Client = httpx.Client


def _parse_response(*, response: httpx.Response) -> Optional[List[float]]:
if response.status_code == 200:
return [float(item) for item in cast(List[float], response.json())]
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[List[float]]:
return httpx.Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
parsed=_parse_response(response=response),
)


def httpx_request(
*,
client: Client,
) -> httpx.Response[List[float]]:

response = client.request(
"get",
"/tests/basic_lists/floats",
)

return _build_response(response=response)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import Optional

import httpx

Client = httpx.Client


def _parse_response(*, response: httpx.Response) -> Optional[List[int]]:
if response.status_code == 200:
return [int(item) for item in cast(List[int], response.json())]
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[List[int]]:
return httpx.Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
parsed=_parse_response(response=response),
)


def httpx_request(
*,
client: Client,
) -> httpx.Response[List[int]]:

response = client.request(
"get",
"/tests/basic_lists/integers",
)

return _build_response(response=response)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import Optional

import httpx

Client = httpx.Client


def _parse_response(*, response: httpx.Response) -> Optional[List[str]]:
if response.status_code == 200:
return [str(item) for item in cast(List[str], response.json())]
return None


def _build_response(*, response: httpx.Response) -> httpx.Response[List[str]]:
return httpx.Response(
status_code=response.status_code,
content=response.content,
headers=response.headers,
parsed=_parse_response(response=response),
)


def httpx_request(
*,
client: Client,
) -> httpx.Response[List[str]]:

response = client.request(
"get",
"/tests/basic_lists/strings",
)

return _build_response(response=response)
Loading