Skip to content

Commit

Permalink
0.6.2
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Hallett committed Sep 26, 2023
1 parent 02e01ef commit 3038f5e
Show file tree
Hide file tree
Showing 16 changed files with 193 additions and 15 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change log

## 0.6.2

* Ignore optional URL query parameters if they are `None`.

## 0.6.1

* Added `from __future__ import annotations` in files to help with typing evaluation.
Expand Down
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change log

## 0.6.2

* Ignore optional URL query parameters if they are `None`.

## 0.6.1

* Added `from __future__ import annotations` in files to help with typing evaluation.
Expand Down
52 changes: 52 additions & 0 deletions example_openapi_specs/best.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,18 @@
"title": "SimpleQueryParametersResponse",
"type": "object"
},
"OptionalQueryParametersResponse": {
"description": "A response for query parameters request that has an optional parameter",
"properties": {
"your_query": {
"title": "Your Query",
"type": "string"
}
},
"required": ["your_query"],
"title": "OptionalQueryParametersResponse",
"type": "object"
},
"SimpleResponse": {
"description": "A simple response",
"properties": {
Expand Down Expand Up @@ -575,6 +587,46 @@
"summary": "Query Request"
}
},
"/optional-query": {
"get": {
"description": "A request with a query parameters that are optional",
"operationId": "query_request_optional_query_get",
"parameters": [
{
"in": "query",
"name": "your_input",
"required": false,
"schema": {
"title": "Your Input",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OptionalQueryParametersResponse"
}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Optional Query Request"
}
},
"/simple-request": {
"get": {
"description": "A simple API request with no parameters.",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "clientele"
version = "0.6.1"
version = "0.6.2"
description = "Generate loveable Python HTTP API Clients"
authors = ["Paul Hallett <paulandrewhallett@gmail.com>"]
license = "MIT"
Expand Down
21 changes: 18 additions & 3 deletions src/client_template/http.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import typing
from urllib.parse import urlparse
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse

import httpx # noqa

Expand All @@ -20,11 +20,26 @@ def __init__(self, response: httpx.Response, reason: str, *args: object) -> None

def parse_url(url: str) -> str:
"""
Returns the base API URL for this service
Returns the full URL from a string.
Will omit any optional query parameters passed.
"""
api_url = f"{c.api_base_url()}{url}"
url_parts = urlparse(url=api_url)
return url_parts.geturl()
# Filter out "None" optional query parameters
filtered_query_params = {
k: v for k, v in parse_qs(url_parts.query).items() if v[0] not in ["None", ""]
}
filtered_query_string = urlencode(filtered_query_params, doseq=True)
return urlunparse(
(
url_parts.scheme,
url_parts.netloc,
url_parts.path,
url_parts.params,
filtered_query_string,
url_parts.fragment,
)
)


def handle_response(func, response):
Expand Down
2 changes: 1 addition & 1 deletion src/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
CLIENT_TEMPLATE_ROOT = dirname(dirname(abspath(__file__))) + "/src/client_template/"
TEMPLATES_ROOT = dirname(dirname(abspath(__file__))) + "/src/templates/"
CONSTANTS_ROOT = dirname(dirname(abspath(__file__))) + "/src/"
VERSION = "0.6.1"
VERSION = "0.6.2"

templates = Environment(loader=PackageLoader("src", "templates"))
2 changes: 1 addition & 1 deletion tests/async_test_client/MANIFEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pipx install clientele

API VERSION: 0.1.0
OPENAPI VERSION: 3.0.2
CLIENTELE VERSION: 0.6.1
CLIENTELE VERSION: 0.6.2

Generated using this command:

Expand Down
9 changes: 9 additions & 0 deletions tests/async_test_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ async def query_request_simple_query_get(
return http.handle_response(query_request_simple_query_get, response)


async def query_request_optional_query_get(
your_input: typing.Optional[str],
) -> typing.Union[schemas.HTTPValidationError, schemas.OptionalQueryParametersResponse]:
"""Optional Query Request"""

response = await http.get(url=f"/optional-query?your_input={your_input}")
return http.handle_response(query_request_optional_query_get, response)


async def simple_request_simple_request_get() -> schemas.SimpleResponse:
"""Simple Request"""

Expand Down
25 changes: 22 additions & 3 deletions tests/async_test_client/http.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import typing
from urllib.parse import urlparse
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse

import httpx # noqa

Expand All @@ -20,11 +20,26 @@ def __init__(self, response: httpx.Response, reason: str, *args: object) -> None

def parse_url(url: str) -> str:
"""
Returns the base API URL for this service
Returns the full URL from a string.
Will omit any optional query parameters passed.
"""
api_url = f"{c.api_base_url()}{url}"
url_parts = urlparse(url=api_url)
return url_parts.geturl()
# Filter out "None" optional query parameters
filtered_query_params = {
k: v for k, v in parse_qs(url_parts.query).items() if v[0] not in ["None", ""]
}
filtered_query_string = urlencode(filtered_query_params, doseq=True)
return urlunparse(
(
url_parts.scheme,
url_parts.netloc,
url_parts.path,
url_parts.params,
filtered_query_string,
url_parts.fragment,
)
)


def handle_response(func, response):
Expand Down Expand Up @@ -86,6 +101,10 @@ def handle_response(func, response):
"200": "SimpleQueryParametersResponse",
"422": "HTTPValidationError",
},
"query_request_optional_query_get": {
"200": "OptionalQueryParametersResponse",
"422": "HTTPValidationError",
},
"simple_request_simple_request_get": {"200": "SimpleResponse"},
"parameter_request_simple_request": {
"200": "ParameterResponse",
Expand Down
4 changes: 4 additions & 0 deletions tests/async_test_client/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ class SimpleQueryParametersResponse(pydantic.BaseModel):
your_query: str


class OptionalQueryParametersResponse(pydantic.BaseModel):
your_query: str


class SimpleResponse(pydantic.BaseModel):
status: str

Expand Down
22 changes: 21 additions & 1 deletion tests/test_async_generated_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ async def test_query_request_simple_query_get(respx_mock: MockRouter):
# Given
your_input = "hello world"
mocked_response = {"your_query": your_input}
mock_path = f"/simple-query?your_input={your_input}"
mock_path = "/simple-query?your_input=hello+world"
respx_mock.get(mock_path).mock(
return_value=Response(json=mocked_response, status_code=200)
)
Expand All @@ -111,6 +111,26 @@ async def test_query_request_simple_query_get(respx_mock: MockRouter):
assert call.request.url == BASE_URL + mock_path


@pytest.mark.asyncio
@pytest.mark.respx(base_url=BASE_URL)
async def test_query_request_optional_query_get(respx_mock: MockRouter):
# Given
your_input = None
mocked_response = {"your_query": "test"}
# NOTE: omits None query parameter
mock_path = "/optional-query"
respx_mock.get(mock_path).mock(
return_value=Response(json=mocked_response, status_code=200)
)
# When
response = await client.query_request_optional_query_get(your_input=your_input)
# Then
assert isinstance(response, schemas.OptionalQueryParametersResponse)
assert len(respx_mock.calls) == 1
call = respx_mock.calls[0]
assert call.request.url == BASE_URL + mock_path


@pytest.mark.asyncio
@pytest.mark.respx(base_url=BASE_URL)
async def test_complex_model_request_complex_model_request_get(respx_mock: MockRouter):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_client/MANIFEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pipx install clientele

API VERSION: 0.1.0
OPENAPI VERSION: 3.0.2
CLIENTELE VERSION: 0.6.1
CLIENTELE VERSION: 0.6.2

Generated using this command:

Expand Down
9 changes: 9 additions & 0 deletions tests/test_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ def query_request_simple_query_get(
return http.handle_response(query_request_simple_query_get, response)


def query_request_optional_query_get(
your_input: typing.Optional[str],
) -> typing.Union[schemas.HTTPValidationError, schemas.OptionalQueryParametersResponse]:
"""Optional Query Request"""

response = http.get(url=f"/optional-query?your_input={your_input}")
return http.handle_response(query_request_optional_query_get, response)


def simple_request_simple_request_get() -> schemas.SimpleResponse:
"""Simple Request"""

Expand Down
25 changes: 22 additions & 3 deletions tests/test_client/http.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import typing
from urllib.parse import urlparse
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse

import httpx # noqa

Expand All @@ -20,11 +20,26 @@ def __init__(self, response: httpx.Response, reason: str, *args: object) -> None

def parse_url(url: str) -> str:
"""
Returns the base API URL for this service
Returns the full URL from a string.
Will omit any optional query parameters passed.
"""
api_url = f"{c.api_base_url()}{url}"
url_parts = urlparse(url=api_url)
return url_parts.geturl()
# Filter out "None" optional query parameters
filtered_query_params = {
k: v for k, v in parse_qs(url_parts.query).items() if v[0] not in ["None", ""]
}
filtered_query_string = urlencode(filtered_query_params, doseq=True)
return urlunparse(
(
url_parts.scheme,
url_parts.netloc,
url_parts.path,
url_parts.params,
filtered_query_string,
url_parts.fragment,
)
)


def handle_response(func, response):
Expand Down Expand Up @@ -86,6 +101,10 @@ def handle_response(func, response):
"200": "SimpleQueryParametersResponse",
"422": "HTTPValidationError",
},
"query_request_optional_query_get": {
"200": "OptionalQueryParametersResponse",
"422": "HTTPValidationError",
},
"simple_request_simple_request_get": {"200": "SimpleResponse"},
"parameter_request_simple_request": {
"200": "ParameterResponse",
Expand Down
4 changes: 4 additions & 0 deletions tests/test_client/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ class SimpleQueryParametersResponse(pydantic.BaseModel):
your_query: str


class OptionalQueryParametersResponse(pydantic.BaseModel):
your_query: str


class SimpleResponse(pydantic.BaseModel):
status: str

Expand Down
21 changes: 20 additions & 1 deletion tests/test_generated_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def test_query_request_simple_query_get(respx_mock: MockRouter):
# Given
your_input = "hello world"
mocked_response = {"your_query": your_input}
mock_path = f"/simple-query?your_input={your_input}"
mock_path = "/simple-query?your_input=hello+world"
respx_mock.get(mock_path).mock(
return_value=Response(json=mocked_response, status_code=200)
)
Expand All @@ -99,6 +99,25 @@ def test_query_request_simple_query_get(respx_mock: MockRouter):
assert call.request.url == BASE_URL + mock_path


@pytest.mark.respx(base_url=BASE_URL)
def test_query_request_optional_query_get(respx_mock: MockRouter):
# Given
your_input = None
mocked_response = {"your_query": "test"}
# NOTE: omits None query parameter
mock_path = "/optional-query"
respx_mock.get(mock_path).mock(
return_value=Response(json=mocked_response, status_code=200)
)
# When
response = client.query_request_optional_query_get(your_input=your_input)
# Then
assert isinstance(response, schemas.OptionalQueryParametersResponse)
assert len(respx_mock.calls) == 1
call = respx_mock.calls[0]
assert call.request.url == BASE_URL + mock_path


@pytest.mark.respx(base_url=BASE_URL)
def test_complex_model_request_complex_model_request_get(respx_mock: MockRouter):
# Given
Expand Down

0 comments on commit 3038f5e

Please sign in to comment.