Skip to content

Commit 29500ba

Browse files
author
Robert Segal
committed
Updated render options to work with all necessary http functions
1 parent 8e273bc commit 29500ba

18 files changed

Lines changed: 330 additions & 31 deletions

mpt_api_client/http/async_client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from mpt_api_client.constants import APPLICATION_JSON
1212
from mpt_api_client.exceptions import MPTError, transform_http_status_exception
1313
from mpt_api_client.http.client import json_to_file_payload
14-
from mpt_api_client.http.client_utils import validate_base_url
14+
from mpt_api_client.http.client_utils import get_query_params, validate_base_url
15+
from mpt_api_client.http.query_options import QueryOptions
1516
from mpt_api_client.http.types import (
1617
HeaderTypes,
1718
QueryParam,
@@ -63,6 +64,7 @@ async def request( # noqa: WPS211
6364
headers: HeaderTypes | None = None,
6465
json_file_key: str = "_attachment_data",
6566
force_multipart: bool = False,
67+
options: QueryOptions | None = None,
6668
) -> Response:
6769
"""Perform an HTTP request.
6870
@@ -75,6 +77,7 @@ async def request( # noqa: WPS211
7577
headers: Request headers.
7678
json_file_key: json file name for data when sending a multipart request.
7779
force_multipart: force multipart request even if file is not provided.
80+
options: Additional options for the request.
7881
7982
Returns:
8083
Response object.
@@ -88,13 +91,14 @@ async def request( # noqa: WPS211
8891
if force_multipart or (files and json):
8992
files[json_file_key] = (None, json_to_file_payload(json), APPLICATION_JSON)
9093
json = None
94+
params_str = get_query_params(query_params, options)
9195
try:
9296
response = await self.httpx_client.request(
9397
method,
9498
url,
9599
files=files,
96100
json=json,
97-
params=query_params,
101+
params=params_str or None,
98102
headers=headers,
99103
)
100104
except HTTPError as err:

mpt_api_client/http/client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
MPTError,
1515
transform_http_status_exception,
1616
)
17-
from mpt_api_client.http.client_utils import validate_base_url
17+
from mpt_api_client.http.client_utils import get_query_params, validate_base_url
18+
from mpt_api_client.http.query_options import QueryOptions
1819
from mpt_api_client.http.types import (
1920
HeaderTypes,
2021
QueryParam,
@@ -76,6 +77,7 @@ def request( # noqa: WPS211
7677
headers: HeaderTypes | None = None,
7778
json_file_key: str = "_attachment_data",
7879
force_multipart: bool = False,
80+
options: QueryOptions | None = None,
7981
) -> Response:
8082
"""Perform an HTTP request.
8183
@@ -88,6 +90,7 @@ def request( # noqa: WPS211
8890
headers: Request headers.
8991
json_file_key: json file name for data when sending a multipart request.
9092
force_multipart: force multipart request even if file is not provided.
93+
options: Additional options for the request.
9194
9295
Returns:
9396
Response object.
@@ -101,13 +104,14 @@ def request( # noqa: WPS211
101104
if force_multipart or (files and json):
102105
files[json_file_key] = (None, json_to_file_payload(json), APPLICATION_JSON)
103106
json = None
107+
params_str = get_query_params(query_params, options)
104108
try:
105109
response = self.httpx_client.request(
106110
method,
107111
url,
108112
files=files,
109113
json=json,
110-
params=query_params,
114+
params=params_str or None,
111115
headers=headers,
112116
)
113117
except HTTPError as err:

mpt_api_client/http/client_utils.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import re
2-
from urllib.parse import SplitResult, urlsplit, urlunparse
2+
from typing import Any
3+
from urllib.parse import SplitResult, urlencode, urlsplit, urlunparse
4+
5+
from mpt_api_client.http.query_options import QueryOptions
36

47
INVALID_ENV_URL_MESSAGE = (
58
"Base URL is required. "
@@ -45,3 +48,20 @@ def validate_base_url(base_url: str | None) -> str:
4548
raise ValueError(INVALID_ENV_URL_MESSAGE)
4649

4750
return _build_sanitized_base_url(split_result)
51+
52+
53+
def get_query_params(
54+
query_params: dict[str, Any] | None, options: QueryOptions | None = None
55+
) -> str:
56+
"""Get query params string from dict."""
57+
filtered_params = {
58+
query_param: query_value
59+
for query_param, query_value in (query_params or {}).items()
60+
if query_value is not None
61+
}
62+
63+
query_params_str = urlencode(filtered_params) if filtered_params else ""
64+
if options and options.render:
65+
query_params_str += "&render()" if query_params_str else "render()"
66+
67+
return query_params_str

mpt_api_client/http/mixins/get_mixin.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
class GetMixin[Model]:
22
"""Get resource mixin."""
33

4-
def get(self, resource_id: str, select: list[str] | str | None = None) -> Model:
4+
def get(
5+
self,
6+
resource_id: str,
7+
select: list[str] | str | None = None,
8+
) -> Model:
59
"""Fetch a specific resource using `GET /endpoint/{resource_id}`.
610
711
Args:
@@ -13,14 +17,20 @@ def get(self, resource_id: str, select: list[str] | str | None = None) -> Model:
1317
"""
1418
if isinstance(select, list):
1519
select = ",".join(select) if select else None
16-
17-
return self._resource(resource_id).get(query_params={"select": select}) # type: ignore[attr-defined, no-any-return]
20+
return self._resource(resource_id).get( # type: ignore[attr-defined, no-any-return]
21+
query_params={"select": select},
22+
options=self.query_state.options, # type: ignore[attr-defined]
23+
)
1824

1925

2026
class AsyncGetMixin[Model]:
2127
"""Async get resource mixin."""
2228

23-
async def get(self, resource_id: str, select: list[str] | str | None = None) -> Model:
29+
async def get(
30+
self,
31+
resource_id: str,
32+
select: list[str] | str | None = None,
33+
) -> Model:
2434
"""Fetch a specific resource using `GET /endpoint/{resource_id}`.
2535
2636
Args:
@@ -32,4 +42,7 @@ async def get(self, resource_id: str, select: list[str] | str | None = None) ->
3242
"""
3343
if isinstance(select, list):
3444
select = ",".join(select) if select else None
35-
return await self._resource(resource_id).get(query_params={"select": select}) # type: ignore[attr-defined, no-any-return]
45+
return await self._resource(resource_id).get( # type: ignore[attr-defined, no-any-return]
46+
query_params={"select": select},
47+
options=self.query_state.options, # type: ignore[attr-defined]
48+
)

mpt_api_client/http/mixins/queryable_mixin.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def order_by(self, *fields: str) -> Self:
2323
rql=self.query_state.filter, # type: ignore[attr-defined]
2424
order_by=list(fields),
2525
select=self.query_state.select, # type: ignore[attr-defined]
26-
render=self.query_state.render, # type: ignore[attr-defined]
26+
options=self.query_state.options, # type: ignore[attr-defined]
2727
)
2828
)
2929

@@ -40,7 +40,7 @@ def filter(self, rql: RQLQuery) -> Self:
4040
rql=combined_filter,
4141
order_by=self.query_state.order_by, # type: ignore[attr-defined]
4242
select=self.query_state.select, # type: ignore[attr-defined]
43-
render=self.query_state.render, # type: ignore[attr-defined]
43+
options=self.query_state.options, # type: ignore[attr-defined]
4444
)
4545
)
4646

@@ -62,7 +62,7 @@ def select(self, *fields: str) -> Self:
6262
rql=self.query_state.filter, # type: ignore[attr-defined]
6363
order_by=self.query_state.order_by, # type: ignore[attr-defined]
6464
select=list(fields),
65-
render=self.query_state.render, # type: ignore[attr-defined]
65+
options=self.query_state.options, # type: ignore[attr-defined]
6666
),
6767
)
6868

@@ -77,7 +77,7 @@ def options(self, *, render: bool = False) -> Self:
7777
rql=self.query_state.filter, # type: ignore[attr-defined]
7878
order_by=self.query_state.order_by, # type: ignore[attr-defined]
7979
select=self.query_state.select, # type: ignore[attr-defined]
80-
render=render,
80+
options=self.query_state.options._replace(render=render), # type: ignore[attr-defined]
8181
),
8282
)
8383

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from typing import NamedTuple
2+
3+
4+
class QueryOptions(NamedTuple):
5+
"""Options for query state."""
6+
7+
render: bool = False

mpt_api_client/http/query_state.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Any
22

3+
from mpt_api_client.http.query_options import QueryOptions
34
from mpt_api_client.rql import RQLQuery
45

56

@@ -18,20 +19,20 @@ def __init__(
1819
order_by: list[str] | None = None,
1920
select: list[str] | None = None,
2021
*,
21-
render: bool = False,
22+
options: QueryOptions | None = None,
2223
) -> None:
2324
"""Initialize the query state with optional filter, ordering, and selection criteria.
2425
2526
Args:
2627
rql: RQL query for filtering data.
2728
order_by: List of fields to order by (prefix with '-' for descending).
2829
select: List of fields to select in the response.
29-
render: Whether to include the render() parameter in the query string.
30+
options: Query options for the request.
3031
"""
3132
self._filter = rql
3233
self._order_by = order_by
3334
self._select = select
34-
self._render = render
35+
self._options = options or QueryOptions()
3536

3637
@property
3738
def filter(self) -> RQLQuery | None:
@@ -49,9 +50,9 @@ def select(self) -> list[str] | None:
4950
return self._select
5051

5152
@property
52-
def render(self) -> bool:
53-
"""Get the current render state."""
54-
return self._render
53+
def options(self) -> QueryOptions:
54+
"""Get the current query options."""
55+
return self._options
5556

5657
def build(self, query_params: dict[str, Any] | None = None) -> str:
5758
"""Build a query string from the current state and additional parameters.
@@ -75,7 +76,7 @@ def build(self, query_params: dict[str, Any] | None = None) -> str:
7576
if self._filter:
7677
query_parts.append(str(self._filter))
7778

78-
if self._render:
79+
if self._options.render:
7980
query_parts.append("render()")
8081

8182
if query_parts:

mpt_api_client/http/resource_accessor.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from mpt_api_client.constants import APPLICATION_JSON
22
from mpt_api_client.http.async_client import AsyncHTTPClient
33
from mpt_api_client.http.client import HTTPClient
4+
from mpt_api_client.http.query_options import QueryOptions
45
from mpt_api_client.http.types import QueryParam, Response
56
from mpt_api_client.http.url_utils import join_url_path
67
from mpt_api_client.models.collection import ResourceList
@@ -37,6 +38,7 @@ def do_request( # noqa: WPS211
3738
json: _JsonPayload = None,
3839
query_params: QueryParam | None = None,
3940
headers: dict[str, str] | None = None,
41+
options: QueryOptions | None = None,
4042
) -> Response:
4143
"""Perform an HTTP request and return the raw ``Response``.
4244
@@ -46,10 +48,11 @@ def do_request( # noqa: WPS211
4648
json: JSON body payload.
4749
query_params: Query-string parameters.
4850
headers: Extra HTTP headers.
51+
options: Query options.
4952
"""
5053
url = join_url_path(self._resource_url, action) if action else self._resource_url
5154
return self._http_client.request(
52-
method, url, json=json, query_params=query_params, headers=headers
55+
method, url, json=json, query_params=query_params, headers=headers, options=options
5356
)
5457

5558
# -- model-returning helpers ---------------------------------------------
@@ -59,9 +62,10 @@ def get(
5962
action: str | None = None,
6063
*,
6164
query_params: QueryParam | None = None,
65+
options: QueryOptions | None = None,
6266
) -> ResourceModel:
6367
"""``GET`` the resource (optionally with a sub-action)."""
64-
return self._action("GET", action, query_params=query_params)
68+
return self._action("GET", action, query_params=query_params, options=options)
6569

6670
def post(
6771
self,
@@ -94,13 +98,15 @@ def _action(
9498
*,
9599
json: _JsonPayload = None,
96100
query_params: QueryParam | None = None,
101+
options: QueryOptions | None = None,
97102
) -> ResourceModel:
98103
response = self.do_request(
99104
method,
100105
action,
101106
json=json,
102107
query_params=query_params,
103108
headers={"Accept": APPLICATION_JSON},
109+
options=options,
104110
)
105111
return self._model_class.from_response(response)
106112

@@ -131,6 +137,7 @@ async def do_request( # noqa: WPS211
131137
json: _JsonPayload = None,
132138
query_params: QueryParam | None = None,
133139
headers: dict[str, str] | None = None,
140+
options: QueryOptions | None = None,
134141
) -> Response:
135142
"""Perform an HTTP request and return the raw ``Response``.
136143
@@ -140,10 +147,11 @@ async def do_request( # noqa: WPS211
140147
json: JSON body payload.
141148
query_params: Query-string parameters.
142149
headers: Extra HTTP headers.
150+
options: Additional options for the request.
143151
"""
144152
url = join_url_path(self._resource_url, action) if action else self._resource_url
145153
return await self._http_client.request(
146-
method, url, json=json, query_params=query_params, headers=headers
154+
method, url, json=json, query_params=query_params, headers=headers, options=options
147155
)
148156

149157
# -- model-returning helpers ---------------------------------------------
@@ -153,9 +161,10 @@ async def get(
153161
action: str | None = None,
154162
*,
155163
query_params: QueryParam | None = None,
164+
options: QueryOptions | None = None,
156165
) -> ResourceModel:
157166
"""``GET`` the resource (optionally with a sub-action)."""
158-
return await self._action("GET", action, query_params=query_params)
167+
return await self._action("GET", action, query_params=query_params, options=options)
159168

160169
async def post(
161170
self,
@@ -188,12 +197,14 @@ async def _action(
188197
*,
189198
json: _JsonPayload = None,
190199
query_params: QueryParam | None = None,
200+
options: QueryOptions | None = None,
191201
) -> ResourceModel:
192202
response = await self.do_request(
193203
method,
194204
action,
195205
json=json,
196206
query_params=query_params,
197207
headers={"Accept": APPLICATION_JSON},
208+
options=options,
198209
)
199210
return self._model_class.from_response(response)

0 commit comments

Comments
 (0)