udl-sdk 0.1.0a11__py3-none-any.whl → 0.1.0a13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {udl_sdk-0.1.0a11.dist-info → udl_sdk-0.1.0a13.dist-info}/METADATA +7 -6
- {udl_sdk-0.1.0a11.dist-info → udl_sdk-0.1.0a13.dist-info}/RECORD +15 -15
- unifieddatalibrary/_base_client.py +28 -2
- unifieddatalibrary/_models.py +8 -5
- unifieddatalibrary/_utils/_transform.py +2 -2
- unifieddatalibrary/_utils/_utils.py +1 -1
- unifieddatalibrary/_version.py +1 -1
- unifieddatalibrary/lib/common.py +0 -1
- unifieddatalibrary/lib/model_based_query.py +6 -2
- unifieddatalibrary/lib/query_field_names.py +6 -4
- unifieddatalibrary/lib/util.py +2 -1
- unifieddatalibrary/pagination.py +129 -15
- unifieddatalibrary/resources/secure_messaging.py +10 -6
- {udl_sdk-0.1.0a11.dist-info → udl_sdk-0.1.0a13.dist-info}/WHEEL +0 -0
- {udl_sdk-0.1.0a11.dist-info → udl_sdk-0.1.0a13.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: udl-sdk
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.0a13
|
4
4
|
Summary: The official Python library for the unifieddatalibrary API
|
5
5
|
Project-URL: Homepage, https://github.com/Bluestaq/udl-python-sdk
|
6
6
|
Project-URL: Repository, https://github.com/Bluestaq/udl-python-sdk
|
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.10
|
19
19
|
Classifier: Programming Language :: Python :: 3.11
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
21
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
22
23
|
Classifier: Typing :: Typed
|
23
24
|
Requires-Python: >=3.8
|
@@ -29,12 +30,13 @@ Requires-Dist: sniffio
|
|
29
30
|
Requires-Dist: typing-extensions<5,>=4.10
|
30
31
|
Provides-Extra: aiohttp
|
31
32
|
Requires-Dist: aiohttp; extra == 'aiohttp'
|
32
|
-
Requires-Dist: httpx-aiohttp>=0.1.
|
33
|
+
Requires-Dist: httpx-aiohttp>=0.1.8; extra == 'aiohttp'
|
33
34
|
Description-Content-Type: text/markdown
|
34
35
|
|
35
36
|
# Unifieddatalibrary Python API library
|
36
37
|
|
37
|
-
|
38
|
+
<!-- prettier-ignore -->
|
39
|
+
[)](https://pypi.org/project/udl-sdk/)
|
38
40
|
|
39
41
|
The Unifieddatalibrary Python library provides convenient access to the Unifieddatalibrary REST API from any Python 3.8+
|
40
42
|
application. The library includes type definitions for all request params and response fields,
|
@@ -114,7 +116,6 @@ pip install --pre udl-sdk[aiohttp]
|
|
114
116
|
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
|
115
117
|
|
116
118
|
```python
|
117
|
-
import os
|
118
119
|
import asyncio
|
119
120
|
from unifieddatalibrary import DefaultAioHttpClient
|
120
121
|
from unifieddatalibrary import AsyncUnifieddatalibrary
|
@@ -122,8 +123,8 @@ from unifieddatalibrary import AsyncUnifieddatalibrary
|
|
122
123
|
|
123
124
|
async def main() -> None:
|
124
125
|
async with AsyncUnifieddatalibrary(
|
125
|
-
username=
|
126
|
-
password=
|
126
|
+
username="My Username",
|
127
|
+
password="My Password",
|
127
128
|
http_client=DefaultAioHttpClient(),
|
128
129
|
) as client:
|
129
130
|
page = await client.elsets.current.list()
|
@@ -1,18 +1,18 @@
|
|
1
1
|
unifieddatalibrary/__init__.py,sha256=562u3LZ5X6bc29nGRt5uXC91XbddnlSfk7tSqKA4yVk,2727
|
2
|
-
unifieddatalibrary/_base_client.py,sha256=
|
2
|
+
unifieddatalibrary/_base_client.py,sha256=kv98LleyLFwecp-SyYKZVR-3Dt5OYNbmB_SQLIen6Yo,67579
|
3
3
|
unifieddatalibrary/_client.py,sha256=mrBWoR1advnC0xyt7nMU-eiLqRf8ZBFqU8xFsrP4grU,136998
|
4
4
|
unifieddatalibrary/_compat.py,sha256=VWemUKbj6DDkQ-O4baSpHVLJafotzeXmCQGJugfVTIw,6580
|
5
5
|
unifieddatalibrary/_constants.py,sha256=S14PFzyN9-I31wiV7SmIlL5Ga0MLHxdvegInGdXH7tM,462
|
6
6
|
unifieddatalibrary/_exceptions.py,sha256=rkk8r4oyqb4bxjMtx0OFWsh1m8gsMfrnIGnNPO6zRz0,3244
|
7
7
|
unifieddatalibrary/_files.py,sha256=FZ264pl2ebvWlVztvuAE8hl1yitTQIT-2Bi6GMwMReA,3619
|
8
|
-
unifieddatalibrary/_models.py,sha256=
|
8
|
+
unifieddatalibrary/_models.py,sha256=viD5E6aDMhxslcFHDYvkHaKzE8YLcNmsPsMe8STixvs,29294
|
9
9
|
unifieddatalibrary/_qs.py,sha256=AOkSz4rHtK4YI3ZU_kzea-zpwBUgEY8WniGmTPyEimc,4846
|
10
10
|
unifieddatalibrary/_resource.py,sha256=Ik-pULzkvFIY2OgB9Ra7mIQKCle38FSP36dWWCH9vxk,1172
|
11
11
|
unifieddatalibrary/_response.py,sha256=hdekMRMvxTvYdKfYIPvAxSpdiuRILRCYd5Dwcye-icg,28890
|
12
12
|
unifieddatalibrary/_streaming.py,sha256=LwKrocz7ZRmYd47TA3q-PLXwgdTgjANE-TCIRZB958s,10148
|
13
13
|
unifieddatalibrary/_types.py,sha256=mslWUKYM1Q3bMXxgq4Mr9fo3QvAwzQJrscV44EPbmGA,6209
|
14
|
-
unifieddatalibrary/_version.py,sha256=
|
15
|
-
unifieddatalibrary/pagination.py,sha256=
|
14
|
+
unifieddatalibrary/_version.py,sha256=4xoSZ-FMWPBL5-DynK0nN9F1JSHXcetMW2_4yc6quMk,179
|
15
|
+
unifieddatalibrary/pagination.py,sha256=jRNsKPCwBHKIHIIX5V5yB7NUsvfy-k7UqnpPGrGyFNU,5758
|
16
16
|
unifieddatalibrary/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
17
|
unifieddatalibrary/_utils/__init__.py,sha256=PNZ_QJuzZEgyYXqkO1HVhGkj5IU9bglVUcw7H-Knjzw,2062
|
18
18
|
unifieddatalibrary/_utils/_logs.py,sha256=mZd6C1F4ajWju5tJvYN_5ATHUi2zEzhcBQqJafS7Two,810
|
@@ -21,14 +21,14 @@ unifieddatalibrary/_utils/_reflection.py,sha256=ZmGkIgT_PuwedyNBrrKGbxoWtkpytJNU
|
|
21
21
|
unifieddatalibrary/_utils/_resources_proxy.py,sha256=IEVfHgsswOT5HDg1cmdTtY8oOs3fo74UtR1aO6yMTZU,649
|
22
22
|
unifieddatalibrary/_utils/_streams.py,sha256=SMC90diFFecpEg_zgDRVbdR3hSEIgVVij4taD-noMLM,289
|
23
23
|
unifieddatalibrary/_utils/_sync.py,sha256=TpGLrrhRNWTJtODNE6Fup3_k7zrWm1j2RlirzBwre-0,2862
|
24
|
-
unifieddatalibrary/_utils/_transform.py,sha256=
|
24
|
+
unifieddatalibrary/_utils/_transform.py,sha256=V2pcnNHKQvgqpShVjV1WjRtrWlCWWdIkZP_HduzypNg,15827
|
25
25
|
unifieddatalibrary/_utils/_typing.py,sha256=D0DbbNu8GnYQTSICnTSHDGsYXj8TcAKyhejb0XcnjtY,4602
|
26
|
-
unifieddatalibrary/_utils/_utils.py,sha256=
|
26
|
+
unifieddatalibrary/_utils/_utils.py,sha256=LLsABnJOF9OiH4aE5_nRwIya7lcn0b6BxBHY1Zgs_K8,12334
|
27
27
|
unifieddatalibrary/lib/.keep,sha256=wuNrz-5SXo3jJaJOJgz4vFHM41YH_g20F5cRQo0vLes,224
|
28
|
-
unifieddatalibrary/lib/common.py,sha256=
|
29
|
-
unifieddatalibrary/lib/model_based_query.py,sha256=
|
30
|
-
unifieddatalibrary/lib/query_field_names.py,sha256=
|
31
|
-
unifieddatalibrary/lib/util.py,sha256=
|
28
|
+
unifieddatalibrary/lib/common.py,sha256=nKvnmTVUrTib17inEUTUcRKCk7xcVPfuRDJdjmzwHBU,83
|
29
|
+
unifieddatalibrary/lib/model_based_query.py,sha256=xU9ixbT-sTejHNgLwJE8rcQ0KZA7V9iwV_7JxBtma9g,4373
|
30
|
+
unifieddatalibrary/lib/query_field_names.py,sha256=KSLSGM6mKMqNRpEL67xq83m9jCOscsZlEzG1F1kzmx0,5699
|
31
|
+
unifieddatalibrary/lib/util.py,sha256=9RfOZDHVPIAHh7Gp64zwZY0EFrHIldEgQ5rOhWFgGMI,604
|
32
32
|
unifieddatalibrary/resources/__init__.py,sha256=lH1bSmd-k2lILj5bvgQyopgXics8PU-XX_7N5syfaEw,86715
|
33
33
|
unifieddatalibrary/resources/air_events.py,sha256=mTbkac1Uttbqy6DWU0m_Xo4QQbjP6VkeMRA53XeC2Kc,83645
|
34
34
|
unifieddatalibrary/resources/air_load_plans.py,sha256=-uLt1LheEw8MYv_DUs4MtjEvlsFy6NtekEqBOqYMkRg,61618
|
@@ -102,7 +102,7 @@ unifieddatalibrary/resources/rf_emitter_details.py,sha256=j_aQc0CrkkBYScJGyg2LPh
|
|
102
102
|
unifieddatalibrary/resources/route_stats.py,sha256=tuKsGa6TVbKeS8xWFfKqTuXwi55CzZUZepXBaJIAhVU,71061
|
103
103
|
unifieddatalibrary/resources/scientific.py,sha256=3z3Opivmv8PRULmRHrRBnr7J1yvcaZYuLdUcDbRxuUo,48200
|
104
104
|
unifieddatalibrary/resources/scs_views.py,sha256=Eda3GPy4XyDVPzslQiyT3glccbM3n9xRSd03B2c7qKw,7308
|
105
|
-
unifieddatalibrary/resources/secure_messaging.py,sha256=
|
105
|
+
unifieddatalibrary/resources/secure_messaging.py,sha256=L6Gn0JKk6mGea1AecP2B7CDPBHZYrLMMvlrO4VM0lZU,18646
|
106
106
|
unifieddatalibrary/resources/sensor_observation_type.py,sha256=ZPc7MyrNehH8Zk__eXqf-NPYBUYLrS2jORYnrFMZveE,13935
|
107
107
|
unifieddatalibrary/resources/sensor_type.py,sha256=6x9ASy-L4IBsceELFtbdZU2kuInXblLAAyYlz-8rTPY,13577
|
108
108
|
unifieddatalibrary/resources/sera_data_comm_details.py,sha256=zEcbCknfEybPh-IqCHsMSfMPJRkZH5etYYYe_slusk4,65774
|
@@ -2610,7 +2610,7 @@ unifieddatalibrary/types/weather_report/history_count_params.py,sha256=KFXFsL7q4
|
|
2610
2610
|
unifieddatalibrary/types/weather_report/history_count_response.py,sha256=ZAHTF5IoOvMAg9GyvtMvgeowh-ythtIJhcWtMbcM8wk,202
|
2611
2611
|
unifieddatalibrary/types/weather_report/history_list_params.py,sha256=JUd53E70wjtn7HlQ7LOZ7lUJREDJkaDYHv2eYW60T3Q,1019
|
2612
2612
|
unifieddatalibrary/types/weather_report/weather_report_full.py,sha256=iv7fHg9aQh6ztvl0ztve5Z3_8Ww-efPMBgrS0n5gXvM,20751
|
2613
|
-
udl_sdk-0.1.
|
2614
|
-
udl_sdk-0.1.
|
2615
|
-
udl_sdk-0.1.
|
2616
|
-
udl_sdk-0.1.
|
2613
|
+
udl_sdk-0.1.0a13.dist-info/METADATA,sha256=c8rfYagi-Yyvx5QmrHo8w20z1Nf1SwowVLNvbkTf_2Y,17014
|
2614
|
+
udl_sdk-0.1.0a13.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
2615
|
+
udl_sdk-0.1.0a13.dist-info/licenses/LICENSE,sha256=YBMC8KbJHXtxIo1-d_G5WdWjtiwFyrcZ5jAsRwN4POI,11348
|
2616
|
+
udl_sdk-0.1.0a13.dist-info/RECORD,,
|
@@ -89,6 +89,7 @@ log: logging.Logger = logging.getLogger(__name__)
|
|
89
89
|
# TODO: make base page type vars covariant
|
90
90
|
SyncPageT = TypeVar("SyncPageT", bound="BaseSyncPage[Any]")
|
91
91
|
AsyncPageT = TypeVar("AsyncPageT", bound="BaseAsyncPage[Any]")
|
92
|
+
_BasePageT = TypeVar("_BasePageT", bound="BasePage[Any]")
|
92
93
|
|
93
94
|
|
94
95
|
_T = TypeVar("_T")
|
@@ -174,6 +175,7 @@ class BasePage(GenericModel, Generic[_T]):
|
|
174
175
|
next_page_info(): Get the necessary information to make a request for the next page
|
175
176
|
"""
|
176
177
|
|
178
|
+
_response: httpx.Response = PrivateAttr()
|
177
179
|
_options: FinalRequestOptions = PrivateAttr()
|
178
180
|
_model: Type[_T] = PrivateAttr()
|
179
181
|
|
@@ -222,6 +224,23 @@ class BasePage(GenericModel, Generic[_T]):
|
|
222
224
|
|
223
225
|
raise ValueError("Unexpected PageInfo state")
|
224
226
|
|
227
|
+
@classmethod
|
228
|
+
def build(cls: Type[_BasePageT], *, response: httpx.Response, data: object) -> _BasePageT: # noqa: ARG003
|
229
|
+
return cls._with_response(
|
230
|
+
cls.construct(
|
231
|
+
None,
|
232
|
+
**{
|
233
|
+
**(cast(Mapping[str, Any], data) if is_mapping(data) else {}),
|
234
|
+
},
|
235
|
+
),
|
236
|
+
response,
|
237
|
+
)
|
238
|
+
|
239
|
+
@classmethod
|
240
|
+
def _with_response(cls, inst: _BasePageT, response: httpx.Response) -> _BasePageT:
|
241
|
+
inst._response = response
|
242
|
+
return inst
|
243
|
+
|
225
244
|
|
226
245
|
class BaseSyncPage(BasePage[_T], Generic[_T]):
|
227
246
|
_client: SyncAPIClient = pydantic.PrivateAttr()
|
@@ -529,6 +548,15 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
|
|
529
548
|
# work around https://github.com/encode/httpx/discussions/2880
|
530
549
|
kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")}
|
531
550
|
|
551
|
+
is_body_allowed = options.method.lower() != "get"
|
552
|
+
|
553
|
+
if is_body_allowed:
|
554
|
+
kwargs["json"] = json_data if is_given(json_data) else None
|
555
|
+
kwargs["files"] = files
|
556
|
+
else:
|
557
|
+
headers.pop("Content-Type", None)
|
558
|
+
kwargs.pop("data", None)
|
559
|
+
|
532
560
|
# TODO: report this error to httpx
|
533
561
|
return self._client.build_request( # pyright: ignore[reportUnknownMemberType]
|
534
562
|
headers=headers,
|
@@ -540,8 +568,6 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
|
|
540
568
|
# so that passing a `TypedDict` doesn't cause an error.
|
541
569
|
# https://github.com/microsoft/pyright/issues/3526#event-6715453066
|
542
570
|
params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None,
|
543
|
-
json=json_data if is_given(json_data) else None,
|
544
|
-
files=files,
|
545
571
|
**kwargs,
|
546
572
|
)
|
547
573
|
|
unifieddatalibrary/_models.py
CHANGED
@@ -2,9 +2,10 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import os
|
4
4
|
import inspect
|
5
|
-
from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast
|
5
|
+
from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
|
6
6
|
from datetime import date, datetime
|
7
7
|
from typing_extensions import (
|
8
|
+
List,
|
8
9
|
Unpack,
|
9
10
|
Literal,
|
10
11
|
ClassVar,
|
@@ -366,7 +367,7 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object:
|
|
366
367
|
if type_ is None:
|
367
368
|
raise RuntimeError(f"Unexpected field type is None for {key}")
|
368
369
|
|
369
|
-
return construct_type(value=value, type_=type_)
|
370
|
+
return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))
|
370
371
|
|
371
372
|
|
372
373
|
def is_basemodel(type_: type) -> bool:
|
@@ -420,7 +421,7 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T:
|
|
420
421
|
return cast(_T, construct_type(value=value, type_=type_))
|
421
422
|
|
422
423
|
|
423
|
-
def construct_type(*, value: object, type_: object) -> object:
|
424
|
+
def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object:
|
424
425
|
"""Loose coercion to the expected type with construction of nested values.
|
425
426
|
|
426
427
|
If the given value does not match the expected type then it is returned as-is.
|
@@ -438,8 +439,10 @@ def construct_type(*, value: object, type_: object) -> object:
|
|
438
439
|
type_ = type_.__value__ # type: ignore[unreachable]
|
439
440
|
|
440
441
|
# unwrap `Annotated[T, ...]` -> `T`
|
441
|
-
if
|
442
|
-
meta: tuple[Any, ...] =
|
442
|
+
if metadata is not None:
|
443
|
+
meta: tuple[Any, ...] = tuple(metadata)
|
444
|
+
elif is_annotated_type(type_):
|
445
|
+
meta = get_args(type_)[1:]
|
443
446
|
type_ = extract_type_arg(type_, 0)
|
444
447
|
else:
|
445
448
|
meta = tuple()
|
@@ -231,7 +231,7 @@ def _format_data(data: object, format_: PropertyFormat, format_template: str | N
|
|
231
231
|
if isinstance(data, (date, datetime)):
|
232
232
|
if format_ == "iso8601":
|
233
233
|
if isinstance(data, datetime):
|
234
|
-
return data.strftime(
|
234
|
+
return data.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
235
235
|
return data.isoformat()
|
236
236
|
|
237
237
|
if format_ == "custom" and format_template is not None:
|
@@ -395,7 +395,7 @@ async def _async_format_data(data: object, format_: PropertyFormat, format_templ
|
|
395
395
|
if isinstance(data, (date, datetime)):
|
396
396
|
if format_ == "iso8601":
|
397
397
|
if isinstance(data, datetime):
|
398
|
-
return data.strftime(
|
398
|
+
return data.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
399
399
|
return data.isoformat()
|
400
400
|
|
401
401
|
if format_ == "custom" and format_template is not None:
|
unifieddatalibrary/_version.py
CHANGED
unifieddatalibrary/lib/common.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# Proposed approach 2 for supporting query parameters with dynamic method generation
|
2
2
|
|
3
|
-
from typing import
|
3
|
+
from typing import Any, Dict, Type, Generic, TypeVar, Callable
|
4
|
+
|
4
5
|
from pydantic import BaseModel
|
5
6
|
|
6
7
|
T = TypeVar("T", bound=BaseModel)
|
7
8
|
|
9
|
+
|
8
10
|
class Query(Generic[T]):
|
9
11
|
"""
|
10
12
|
A dynamic query builder that generates field-based filter methods at runtime.
|
@@ -48,7 +50,7 @@ class Query(Generic[T]):
|
|
48
50
|
}
|
49
51
|
|
50
52
|
# Dynamically create query methods for each field/operator combination
|
51
|
-
for field_name,
|
53
|
+
for field_name, _field_info in model.model_fields.items():
|
52
54
|
for suffix, operator in operators.items():
|
53
55
|
method_name = f"{field_name}{suffix}"
|
54
56
|
method = self._make_method(field_name, operator)
|
@@ -65,8 +67,10 @@ class Query(Generic[T]):
|
|
65
67
|
Returns:
|
66
68
|
A callable method that accepts a value and returns the updated Query object.
|
67
69
|
"""
|
70
|
+
|
68
71
|
def method(self: "Query[T]", value: Any) -> "Query[T]":
|
69
72
|
return self._add_filter(field_name, operator, value)
|
73
|
+
|
70
74
|
return method
|
71
75
|
|
72
76
|
def _add_filter(self, field_name: str, operator: str, value: Any) -> "Query[T]":
|
@@ -1,10 +1,11 @@
|
|
1
1
|
# Proposed approach 1 for supporting query parameters in a typed and composable way
|
2
2
|
|
3
|
-
from typing import
|
4
|
-
from pydantic import BaseModel
|
3
|
+
from typing import Any, Dict, Type, Tuple, Union, Generic, TypeVar, Protocol, cast
|
5
4
|
from datetime import datetime
|
6
|
-
from .util import sanitize_datetime
|
7
5
|
|
6
|
+
from pydantic import BaseModel
|
7
|
+
|
8
|
+
from .util import sanitize_datetime
|
8
9
|
|
9
10
|
T = TypeVar("T", bound=BaseModel)
|
10
11
|
|
@@ -16,6 +17,7 @@ class QueryField(Protocol):
|
|
16
17
|
Each method represents a filter operation that returns a modified Query object
|
17
18
|
with the new filter applied.
|
18
19
|
"""
|
20
|
+
|
19
21
|
def eq(self, value: Any) -> "Query[Any]": ...
|
20
22
|
def gte(self, value: Any) -> "Query[Any]": ...
|
21
23
|
def lte(self, value: Any) -> "Query[Any]": ...
|
@@ -91,7 +93,7 @@ class Query(Generic[T]):
|
|
91
93
|
if model_info is None:
|
92
94
|
raise KeyError(f"{self.field_name} does not exist in {self.query.model}")
|
93
95
|
|
94
|
-
key = getattr(model_info,
|
96
|
+
key = getattr(model_info, "alias", None) or self.field_name
|
95
97
|
|
96
98
|
if operator == "between":
|
97
99
|
if not isinstance(value, tuple) or len(value) != 2:
|
unifieddatalibrary/lib/util.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
|
2
1
|
from datetime import datetime, timezone
|
2
|
+
|
3
3
|
from .common import UDL_DATETIME_FORMAT
|
4
4
|
|
5
|
+
|
5
6
|
def sanitize_datetime(val: datetime) -> str:
|
6
7
|
"""Takes a datetime argument val and returns the same datetime converted to UTC.
|
7
8
|
If tzinfo is not set, assumes local time in conversion."""
|
unifieddatalibrary/pagination.py
CHANGED
@@ -1,17 +1,23 @@
|
|
1
1
|
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
2
2
|
|
3
|
-
from typing import Any, List, Type, Generic, Mapping, TypeVar, Optional, cast
|
3
|
+
from typing import Any, List, Type, Generic, Mapping, TypeVar, Callable, Optional, cast
|
4
4
|
from typing_extensions import override
|
5
5
|
|
6
|
-
from httpx import Response
|
6
|
+
from httpx import URL, Response
|
7
7
|
|
8
8
|
from ._utils import is_mapping
|
9
9
|
from ._models import BaseModel
|
10
10
|
from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
|
11
11
|
|
12
|
-
__all__ = [
|
12
|
+
__all__ = [
|
13
|
+
"SyncOffsetPage",
|
14
|
+
"AsyncOffsetPage",
|
15
|
+
"SyncKafkaOffsetPage",
|
16
|
+
"AsyncKafkaOffsetPage",
|
17
|
+
]
|
13
18
|
|
14
19
|
_BaseModelT = TypeVar("_BaseModelT", bound=BaseModel)
|
20
|
+
_BasePageT = TypeVar("_BasePageT", bound=BasePage[Any])
|
15
21
|
|
16
22
|
_T = TypeVar("_T")
|
17
23
|
|
@@ -38,12 +44,16 @@ class SyncOffsetPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
|
|
38
44
|
return PageInfo(params={"firstResult": current_count})
|
39
45
|
|
40
46
|
@classmethod
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
@override
|
48
|
+
def build(cls: Type[_BasePageT], *, response: Response, data: object) -> _BasePageT: # noqa: ARG003
|
49
|
+
return cls._with_response(
|
50
|
+
cls.construct(
|
51
|
+
None,
|
52
|
+
**{
|
53
|
+
**(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
|
54
|
+
},
|
55
|
+
),
|
56
|
+
response,
|
47
57
|
)
|
48
58
|
|
49
59
|
|
@@ -69,10 +79,114 @@ class AsyncOffsetPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
|
|
69
79
|
return PageInfo(params={"firstResult": current_count})
|
70
80
|
|
71
81
|
@classmethod
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
82
|
+
@override
|
83
|
+
def build(cls: Type[_BasePageT], *, response: Response, data: object) -> _BasePageT: # noqa: ARG003
|
84
|
+
return cls._with_response(
|
85
|
+
cls.construct(
|
86
|
+
None,
|
87
|
+
**{
|
88
|
+
**(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
|
89
|
+
},
|
90
|
+
),
|
91
|
+
response,
|
92
|
+
)
|
93
|
+
|
94
|
+
|
95
|
+
class SyncKafkaOffsetPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
|
96
|
+
"""Pagination for Kafka-style endpoints that return the next offset in a response header."""
|
97
|
+
|
98
|
+
items: List[_T]
|
99
|
+
url_builder: Callable[[int], str]
|
100
|
+
|
101
|
+
@staticmethod
|
102
|
+
def with_url_builder(fn: Callable[[int], str]) -> Type["SyncKafkaOffsetPage[object]"]:
|
103
|
+
"""Create a page class with a URL builder for constructing next page URLs."""
|
104
|
+
|
105
|
+
class PageWithBuilder(SyncKafkaOffsetPage[object]):
|
106
|
+
url_builder = fn
|
107
|
+
|
108
|
+
return PageWithBuilder
|
109
|
+
|
110
|
+
@override
|
111
|
+
def _get_page_items(self) -> List[_T]:
|
112
|
+
items = self.items
|
113
|
+
if not items:
|
114
|
+
return []
|
115
|
+
return items
|
116
|
+
|
117
|
+
@override
|
118
|
+
def next_page_info(self) -> Optional[PageInfo]:
|
119
|
+
next_offset_str = self._response.headers.get("KAFKA_NEXT_OFFSET")
|
120
|
+
if not next_offset_str:
|
121
|
+
return None
|
122
|
+
|
123
|
+
try:
|
124
|
+
next_offset = int(next_offset_str)
|
125
|
+
except ValueError:
|
126
|
+
return None
|
127
|
+
|
128
|
+
new_url = self.url_builder(next_offset)
|
129
|
+
return PageInfo(url=URL(new_url))
|
130
|
+
|
131
|
+
@classmethod
|
132
|
+
@override
|
133
|
+
def build(cls: Type[_BasePageT], *, response: Response, data: object) -> _BasePageT: # noqa: ARG003
|
134
|
+
return cls._with_response(
|
135
|
+
cls.construct(
|
136
|
+
None,
|
137
|
+
**{
|
138
|
+
**(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
|
139
|
+
},
|
140
|
+
),
|
141
|
+
response,
|
142
|
+
)
|
143
|
+
|
144
|
+
|
145
|
+
class AsyncKafkaOffsetPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
|
146
|
+
"""Async pagination for Kafka-style endpoints that return the next offset in a response header."""
|
147
|
+
|
148
|
+
items: List[_T]
|
149
|
+
url_builder: Callable[[int], str]
|
150
|
+
|
151
|
+
@staticmethod
|
152
|
+
def with_url_builder(fn: Callable[[int], str]) -> Type["AsyncKafkaOffsetPage[object]"]:
|
153
|
+
"""Create a page class with a URL builder for constructing next page URLs."""
|
154
|
+
|
155
|
+
class PageWithBuilder(AsyncKafkaOffsetPage[object]):
|
156
|
+
url_builder = fn
|
157
|
+
|
158
|
+
return PageWithBuilder
|
159
|
+
|
160
|
+
@override
|
161
|
+
def _get_page_items(self) -> List[_T]:
|
162
|
+
items = self.items
|
163
|
+
if not items:
|
164
|
+
return []
|
165
|
+
return items
|
166
|
+
|
167
|
+
@override
|
168
|
+
def next_page_info(self) -> Optional[PageInfo]:
|
169
|
+
next_offset_str = self._response.headers.get("KAFKA_NEXT_OFFSET")
|
170
|
+
if not next_offset_str:
|
171
|
+
return None
|
172
|
+
|
173
|
+
try:
|
174
|
+
next_offset = int(next_offset_str)
|
175
|
+
except ValueError:
|
176
|
+
return None
|
177
|
+
|
178
|
+
new_url = self.url_builder(next_offset)
|
179
|
+
return PageInfo(url=URL(new_url))
|
180
|
+
|
181
|
+
@classmethod
|
182
|
+
@override
|
183
|
+
def build(cls: Type[_BasePageT], *, response: Response, data: object) -> _BasePageT: # noqa: ARG003
|
184
|
+
return cls._with_response(
|
185
|
+
cls.construct(
|
186
|
+
None,
|
187
|
+
**{
|
188
|
+
**(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
|
189
|
+
},
|
190
|
+
),
|
191
|
+
response,
|
78
192
|
)
|
@@ -4,6 +4,8 @@ from __future__ import annotations
|
|
4
4
|
|
5
5
|
import httpx
|
6
6
|
|
7
|
+
from unifieddatalibrary.pagination import SyncKafkaOffsetPage, AsyncKafkaOffsetPage
|
8
|
+
|
7
9
|
from ..types import (
|
8
10
|
secure_messaging_get_messages_params,
|
9
11
|
secure_messaging_describe_topic_params,
|
@@ -150,7 +152,7 @@ class SecureMessagingResource(SyncAPIResource):
|
|
150
152
|
extra_query: Query | None = None,
|
151
153
|
extra_body: Body | None = None,
|
152
154
|
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
|
153
|
-
) ->
|
155
|
+
) -> SyncKafkaOffsetPage[object]:
|
154
156
|
"""Retrieve a set of messages from the given topic at the given offset.
|
155
157
|
|
156
158
|
See Help >
|
@@ -168,8 +170,9 @@ class SecureMessagingResource(SyncAPIResource):
|
|
168
170
|
if not topic:
|
169
171
|
raise ValueError(f"Expected a non-empty value for `topic` but received {topic!r}")
|
170
172
|
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
|
171
|
-
return self.
|
173
|
+
return self._get_api_list(
|
172
174
|
f"/sm/getMessages/{topic}/{offset}",
|
175
|
+
page=SyncKafkaOffsetPage.with_url_builder(lambda next_offset: f"/sm/getMessages/{topic}/{next_offset}"),
|
173
176
|
options=make_request_options(
|
174
177
|
extra_headers=extra_headers,
|
175
178
|
extra_query=extra_query,
|
@@ -183,7 +186,7 @@ class SecureMessagingResource(SyncAPIResource):
|
|
183
186
|
secure_messaging_get_messages_params.SecureMessagingGetMessagesParams,
|
184
187
|
),
|
185
188
|
),
|
186
|
-
|
189
|
+
model=object,
|
187
190
|
)
|
188
191
|
|
189
192
|
def list_topics(
|
@@ -330,7 +333,7 @@ class AsyncSecureMessagingResource(AsyncAPIResource):
|
|
330
333
|
extra_query: Query | None = None,
|
331
334
|
extra_body: Body | None = None,
|
332
335
|
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
|
333
|
-
) ->
|
336
|
+
) -> AsyncKafkaOffsetPage[object]:
|
334
337
|
"""Retrieve a set of messages from the given topic at the given offset.
|
335
338
|
|
336
339
|
See Help >
|
@@ -348,8 +351,9 @@ class AsyncSecureMessagingResource(AsyncAPIResource):
|
|
348
351
|
if not topic:
|
349
352
|
raise ValueError(f"Expected a non-empty value for `topic` but received {topic!r}")
|
350
353
|
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
|
351
|
-
return await self.
|
354
|
+
return await self._get_api_list(
|
352
355
|
f"/sm/getMessages/{topic}/{offset}",
|
356
|
+
page=AsyncKafkaOffsetPage.with_url_builder(lambda next_offset: f"/sm/getMessages/{topic}/{next_offset}"),
|
353
357
|
options=make_request_options(
|
354
358
|
extra_headers=extra_headers,
|
355
359
|
extra_query=extra_query,
|
@@ -363,7 +367,7 @@ class AsyncSecureMessagingResource(AsyncAPIResource):
|
|
363
367
|
secure_messaging_get_messages_params.SecureMessagingGetMessagesParams,
|
364
368
|
),
|
365
369
|
),
|
366
|
-
|
370
|
+
model=object,
|
367
371
|
)
|
368
372
|
|
369
373
|
async def list_topics(
|
File without changes
|
File without changes
|