ul-api-utils 9.0.0a5__py3-none-any.whl → 9.0.0a7__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.
Potentially problematic release.
This version of ul-api-utils might be problematic. Click here for more details.
- ul_api_utils/api_resource/api_resource.py +4 -3
- ul_api_utils/api_resource/api_resource_fn_typing.py +9 -10
- ul_api_utils/api_resource/api_response.py +9 -10
- ul_api_utils/debug/debugger.py +3 -3
- ul_api_utils/debug/stat.py +2 -7
- ul_api_utils/internal_api/internal_api.py +1 -1
- ul_api_utils/internal_api/internal_api_response.py +6 -6
- ul_api_utils/modules/api_sdk.py +1 -1
- ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_three_specifier.py +33 -12
- ul_api_utils/utils/flask_swagger_generator/utils/replace_in_dict.py +1 -1
- ul_api_utils/utils/json_encoder.py +3 -1
- ul_api_utils/utils/unwrap_typing.py +4 -1
- ul_api_utils/validators/__tests__/test_custom_fields.py +32 -32
- ul_api_utils/validators/custom_fields.py +28 -52
- {ul_api_utils-9.0.0a5.dist-info → ul_api_utils-9.0.0a7.dist-info}/METADATA +3 -3
- {ul_api_utils-9.0.0a5.dist-info → ul_api_utils-9.0.0a7.dist-info}/RECORD +20 -20
- {ul_api_utils-9.0.0a5.dist-info → ul_api_utils-9.0.0a7.dist-info}/LICENSE +0 -0
- {ul_api_utils-9.0.0a5.dist-info → ul_api_utils-9.0.0a7.dist-info}/WHEEL +0 -0
- {ul_api_utils-9.0.0a5.dist-info → ul_api_utils-9.0.0a7.dist-info}/entry_points.txt +0 -0
- {ul_api_utils-9.0.0a5.dist-info → ul_api_utils-9.0.0a7.dist-info}/top_level.txt +0 -0
|
@@ -16,8 +16,9 @@ from ul_api_utils.api_resource.api_resource_error_handling import WEB_EXCEPTION_
|
|
|
16
16
|
ProcessingExceptionsParams, WEB_UNKNOWN_ERROR_PARAMS
|
|
17
17
|
from ul_api_utils.api_resource.api_resource_fn_typing import ApiResourceFnTyping
|
|
18
18
|
from ul_api_utils.api_resource.api_resource_type import ApiResourceType
|
|
19
|
-
from ul_api_utils.api_resource.api_response import JsonApiResponse, HtmlApiResponse, FileApiResponse,
|
|
20
|
-
|
|
19
|
+
from ul_api_utils.api_resource.api_response import JsonApiResponse, HtmlApiResponse, FileApiResponse, \
|
|
20
|
+
JsonApiResponsePayload, EmptyJsonApiResponse, \
|
|
21
|
+
RedirectApiResponse, ProxyJsonApiResponse, RootJsonApiResponse, RootJsonApiResponsePayload
|
|
21
22
|
from ul_api_utils.conf import APPLICATION_ENV, APPLICATION_JWT_PUBLIC_KEY, APPLICATION_DEBUG
|
|
22
23
|
from ul_api_utils.const import REQUEST_HEADER__X_FORWARDED_FOR, \
|
|
23
24
|
RESPONSE_HEADER__WWW_AUTH, OOPS, REQUEST_HEADER__USER_AGENT
|
|
@@ -38,7 +39,7 @@ TPayload = TypeVar('TPayload')
|
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
T = TypeVar('T')
|
|
41
|
-
TResp = TypeVar('TResp', bound=JsonApiResponsePayload)
|
|
42
|
+
TResp = TypeVar('TResp', bound=Union[JsonApiResponsePayload, RootJsonApiResponsePayload[Any]])
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
class ApiResource:
|
|
@@ -2,7 +2,7 @@ import inspect
|
|
|
2
2
|
from typing import NamedTuple, Any, Callable, Optional, List, Dict, Type, Tuple, TYPE_CHECKING, Union, get_origin, get_args
|
|
3
3
|
|
|
4
4
|
from flask import request
|
|
5
|
-
from pydantic import BaseModel, ValidationError, validate_call, TypeAdapter
|
|
5
|
+
from pydantic import BaseModel, ValidationError, validate_call, TypeAdapter, RootModel
|
|
6
6
|
from pydantic.v1.utils import deep_update
|
|
7
7
|
from pydantic_core import ErrorDetails
|
|
8
8
|
|
|
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|
|
25
25
|
FN_SYSTEM_PROPS = {"api_resource", "query", "body", "return", "body_validation_error", "query_validation_error"}
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def _is_complex_type(annotation):
|
|
28
|
+
def _is_complex_type(annotation: Any) -> bool:
|
|
29
29
|
origin = get_origin(annotation)
|
|
30
30
|
|
|
31
31
|
# Optional[type_] is typing.Union
|
|
@@ -82,18 +82,18 @@ class ApiResourceFnTyping(NamedTuple):
|
|
|
82
82
|
|
|
83
83
|
if self.request_body_optional:
|
|
84
84
|
if self.request_body_many:
|
|
85
|
-
class BodyTypingOptList(
|
|
86
|
-
|
|
85
|
+
class BodyTypingOptList(RootModel[List[body_typing] | None]): # type: ignore
|
|
86
|
+
pass
|
|
87
87
|
|
|
88
88
|
return BodyTypingOptList
|
|
89
89
|
|
|
90
|
-
class BodyTypingOpt(
|
|
91
|
-
|
|
90
|
+
class BodyTypingOpt(RootModel[Optional[body_typing]]): # type: ignore
|
|
91
|
+
pass
|
|
92
92
|
return BodyTypingOpt
|
|
93
93
|
|
|
94
94
|
if self.request_body_many:
|
|
95
|
-
class BodyTypingList(
|
|
96
|
-
|
|
95
|
+
class BodyTypingList(RootModel[List[body_typing]]): # type: ignore
|
|
96
|
+
pass
|
|
97
97
|
|
|
98
98
|
return BodyTypingList
|
|
99
99
|
return body_typing
|
|
@@ -165,8 +165,7 @@ class ApiResourceFnTyping(NamedTuple):
|
|
|
165
165
|
return kwargs, errors
|
|
166
166
|
body = self._get_body()
|
|
167
167
|
try:
|
|
168
|
-
expected_typing: Type[BaseModel | List[BaseModel]] = List[self.body_typing]
|
|
169
|
-
if self.request_body_many else self.body_typing
|
|
168
|
+
expected_typing: Type[BaseModel | List[BaseModel]] = List[self.body_typing] if self.request_body_many else self.body_typing # type: ignore
|
|
170
169
|
kwargs['body'] = TypeAdapter(expected_typing).validate_python(body)
|
|
171
170
|
except ValidationError as ve:
|
|
172
171
|
if self.has_body_validation_error:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import io
|
|
2
2
|
from datetime import datetime
|
|
3
|
-
from types import NoneType
|
|
4
3
|
from typing import TypeVar, Generic, List, Optional, Dict, Any, Callable, Union, BinaryIO, Tuple, Type
|
|
5
4
|
|
|
6
5
|
import msgpack
|
|
@@ -64,8 +63,8 @@ class HtmlApiResponse(ApiResponse):
|
|
|
64
63
|
|
|
65
64
|
@classmethod
|
|
66
65
|
def _internal_use__mk_schema(cls, inner_type: Optional[Type[BaseModel]]) -> Type[BaseModel]:
|
|
67
|
-
class _ResponseNoneType(
|
|
68
|
-
|
|
66
|
+
class _ResponseNoneType(RootModel[None]):
|
|
67
|
+
pass
|
|
69
68
|
return _ResponseNoneType
|
|
70
69
|
|
|
71
70
|
def to_flask_response(self, debugger: Optional[Debugger] = None) -> BaseResponse:
|
|
@@ -100,8 +99,8 @@ class FileApiResponse(ApiResponse):
|
|
|
100
99
|
|
|
101
100
|
@classmethod
|
|
102
101
|
def _internal_use__mk_schema(cls, inner_type: Optional[Type[BaseModel]]) -> Type[BaseModel]:
|
|
103
|
-
class _ResponseNoneType(
|
|
104
|
-
|
|
102
|
+
class _ResponseNoneType(RootModel[None]):
|
|
103
|
+
pass
|
|
105
104
|
return _ResponseNoneType
|
|
106
105
|
|
|
107
106
|
def to_flask_response(self, debugger: Optional[Debugger] = None) -> BaseResponse:
|
|
@@ -125,8 +124,8 @@ class EmptyJsonApiResponse(ApiResponse):
|
|
|
125
124
|
|
|
126
125
|
@classmethod
|
|
127
126
|
def _internal_use__mk_schema(cls, inner_type: Optional[Type[BaseModel]]) -> Type[BaseModel]:
|
|
128
|
-
class _ResponseNoneType(
|
|
129
|
-
|
|
127
|
+
class _ResponseNoneType(RootModel[None]):
|
|
128
|
+
pass
|
|
130
129
|
return _ResponseNoneType
|
|
131
130
|
|
|
132
131
|
def to_flask_response(self, debugger: Optional[Debugger] = None) -> BaseResponse:
|
|
@@ -162,7 +161,7 @@ class DictJsonApiResponsePayload(RootJsonApiResponsePayload[Dict[str, Any]]):
|
|
|
162
161
|
pass
|
|
163
162
|
|
|
164
163
|
|
|
165
|
-
TProxyPayload = TypeVar('TProxyPayload', bound=Union[JsonApiResponsePayload, List[JsonApiResponsePayload], None])
|
|
164
|
+
TProxyPayload = TypeVar('TProxyPayload', bound=Union[JsonApiResponsePayload, List[JsonApiResponsePayload], RootJsonApiResponsePayload[Any], List[RootJsonApiResponsePayload[Any]], None])
|
|
166
165
|
|
|
167
166
|
|
|
168
167
|
class ProxyJsonApiResponse(Generic[TProxyPayload], EmptyJsonApiResponse):
|
|
@@ -193,8 +192,8 @@ class RootJsonApiResponse(Generic[TJsonObjApiResponsePayload], EmptyJsonApiRespo
|
|
|
193
192
|
|
|
194
193
|
@classmethod
|
|
195
194
|
def _internal_use__mk_schema(cls, inner_type: Optional[Type[BaseModel]]) -> Type[BaseModel]:
|
|
196
|
-
class _ResponseStd(
|
|
197
|
-
|
|
195
|
+
class _ResponseStd(RootModel[inner_type]): # type: ignore
|
|
196
|
+
pass
|
|
198
197
|
return _ResponseStd
|
|
199
198
|
|
|
200
199
|
def to_flask_response(self, debugger: Optional[Debugger] = None) -> BaseResponse:
|
ul_api_utils/debug/debugger.py
CHANGED
|
@@ -79,7 +79,7 @@ class Debugger:
|
|
|
79
79
|
return
|
|
80
80
|
|
|
81
81
|
started_at = stat.get_request_started_at()
|
|
82
|
-
ended_at = time.
|
|
82
|
+
ended_at = time.perf_counter()
|
|
83
83
|
name = self.name
|
|
84
84
|
|
|
85
85
|
stats = stat.get_stat(code_spans=True, started_at=started_at, ended_at=ended_at)
|
|
@@ -90,7 +90,7 @@ class Debugger:
|
|
|
90
90
|
def render_dict(self, status_code: int) -> Dict[str, Any]:
|
|
91
91
|
if not collecting_enabled():
|
|
92
92
|
return {}
|
|
93
|
-
stats = [st.unwrap() for st in stat.get_stat(started_at=stat.get_request_started_at(), ended_at=time.
|
|
93
|
+
stats = [st.unwrap() for st in stat.get_stat(started_at=stat.get_request_started_at(), ended_at=time.perf_counter())]
|
|
94
94
|
|
|
95
95
|
return {
|
|
96
96
|
RESPONSE_PROP_DEBUG_STATS: stats,
|
|
@@ -107,7 +107,7 @@ class Debugger:
|
|
|
107
107
|
return script
|
|
108
108
|
|
|
109
109
|
started_at = stat.get_request_started_at()
|
|
110
|
-
ended_at = time.
|
|
110
|
+
ended_at = time.perf_counter()
|
|
111
111
|
|
|
112
112
|
stats = stat.get_stat(code_spans=True, started_at=started_at, ended_at=ended_at)
|
|
113
113
|
|
ul_api_utils/debug/stat.py
CHANGED
|
@@ -18,7 +18,7 @@ INDENT = ' '
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def time_now() -> float:
|
|
21
|
-
return time.
|
|
21
|
+
return time.perf_counter()
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def mark_request_started() -> None:
|
|
@@ -439,11 +439,6 @@ def mk_stat_string(
|
|
|
439
439
|
header = f'{IND}{" " * (i_size * 2 + 2)}{IND}Request{" " * MAX_LEN_OF_TYPE}{INDENT * max_lvl}{col_duration(started_at, ended_at, cm=cm)}{INDENT}{INDENT}{total_str}'
|
|
440
440
|
|
|
441
441
|
short_identifier = uuid.uuid4().hex[:8]
|
|
442
|
-
|
|
443
|
-
ended_dt = datetime.fromtimestamp(ended_at)
|
|
444
|
-
date_str = f'{cm[C_FG_GRAY]}{started_dt.date().isoformat()}{cm[C_NC]}' \
|
|
445
|
-
f'{IND}{started_dt.strftime("%H:%M:%S")}{cm[C_FG_GRAY]}.{started_dt.microsecond // 1000:0<3}{cm[C_NC]}' \
|
|
446
|
-
f' - {ended_dt.strftime("%H:%M:%S")}{cm[C_FG_GRAY]}.{ended_dt.microsecond // 1000:0<3}{cm[C_NC]}'
|
|
447
|
-
return f'▀▀▀▀▀ {name} {cm[C_FG_GRAY]}{short_identifier}{cm[C_NC]}{IND}{date_str} {"▀" * 55}' \
|
|
442
|
+
return f'▀▀▀▀▀ {name} {cm[C_FG_GRAY]}{short_identifier}{cm[C_NC]}{IND}{"▀" * 55}' \
|
|
448
443
|
f'\n{header}\n' \
|
|
449
444
|
f'{result_s}▄▄▄▄▄ {name} {cm[C_FG_GRAY]}{short_identifier}{cm[C_NC]} {"▄" * 96}'
|
|
@@ -7,7 +7,7 @@ from typing import Any, Optional, List, Generic, Type, TypeVar, Union, Dict, Nam
|
|
|
7
7
|
import requests
|
|
8
8
|
from pydantic import ValidationError
|
|
9
9
|
|
|
10
|
-
from ul_api_utils.api_resource.api_response import JsonApiResponsePayload
|
|
10
|
+
from ul_api_utils.api_resource.api_response import JsonApiResponsePayload, RootJsonApiResponsePayload
|
|
11
11
|
from ul_api_utils.api_resource.signature_check import set_model
|
|
12
12
|
from ul_api_utils.const import RESPONSE_PROP_DEBUG_STATS, RESPONSE_PROP_PAYLOAD, RESPONSE_PROP_OK, RESPONSE_PROP_ERRORS, RESPONSE_PROP_TOTAL, RESPONSE_PROP_COUNT, MIME__JSON, \
|
|
13
13
|
RESPONSE_HEADER__CONTENT_TYPE
|
|
@@ -18,7 +18,7 @@ from ul_api_utils.utils.api_format import ApiFormat
|
|
|
18
18
|
from ul_api_utils.internal_api.internal_api_error import InternalApiResponseErrorObj
|
|
19
19
|
from ul_api_utils.utils.unwrap_typing import UnwrappedOptionalObjOrListOfObj
|
|
20
20
|
|
|
21
|
-
TPyloadType = TypeVar('TPyloadType', bound=Union[JsonApiResponsePayload, List[JsonApiResponsePayload], None])
|
|
21
|
+
TPyloadType = TypeVar('TPyloadType', bound=Union[JsonApiResponsePayload, List[JsonApiResponsePayload], RootJsonApiResponsePayload[Any], List[RootJsonApiResponsePayload[Any]], None])
|
|
22
22
|
TResp = TypeVar('TResp', bound='InternalApiResponse[Any]')
|
|
23
23
|
|
|
24
24
|
|
|
@@ -73,17 +73,17 @@ class InternalApiResponse(Generic[TPyloadType]):
|
|
|
73
73
|
|
|
74
74
|
self._parsed_schema: Optional[InternalApiResponseSchema] = None
|
|
75
75
|
self._parsed_json: Any = None
|
|
76
|
-
self._parsed_payload: Union[None, JsonApiResponsePayload, List[JsonApiResponsePayload]] = None
|
|
76
|
+
self._parsed_payload: Union[None, JsonApiResponsePayload, List[JsonApiResponsePayload], RootJsonApiResponsePayload[Any], List[RootJsonApiResponsePayload[Any]]] = None
|
|
77
77
|
|
|
78
78
|
self._status_checked = False
|
|
79
79
|
self._info = info
|
|
80
80
|
|
|
81
81
|
if payload_type is not None:
|
|
82
|
-
self._payload_type = UnwrappedOptionalObjOrListOfObj.parse(payload_type, JsonApiResponsePayload)
|
|
82
|
+
self._payload_type = UnwrappedOptionalObjOrListOfObj.parse(payload_type, JsonApiResponsePayload) or UnwrappedOptionalObjOrListOfObj.parse(payload_type, RootJsonApiResponsePayload)
|
|
83
83
|
if self._payload_type is None:
|
|
84
|
-
|
|
84
|
+
tn1, tn2 = JsonApiResponsePayload.__name__, RootJsonApiResponsePayload.__name__
|
|
85
85
|
raise ValueError(
|
|
86
|
-
f'payload_typing is invalid. must be Union[Type[{
|
|
86
|
+
f'payload_typing is invalid. must be Union[Type[{tn1} | {tn2}], Optional[Type[{tn1} | {tn2}]], List[Type[{tn1} | {tn2}]], Optional[List[Type[{tn1} | {tn2}]]]]. {payload_type} was given',
|
|
87
87
|
)
|
|
88
88
|
|
|
89
89
|
def typed(self, payload_type: Type[TPyloadType]) -> 'InternalApiResponse[TPyloadType]':
|
ul_api_utils/modules/api_sdk.py
CHANGED
|
@@ -117,7 +117,7 @@ class ApiSdk:
|
|
|
117
117
|
|
|
118
118
|
self._config = config
|
|
119
119
|
self._routes_loaded = False
|
|
120
|
-
self._request_started_at = time.
|
|
120
|
+
self._request_started_at = time.perf_counter()
|
|
121
121
|
self._initialized_flask_name: Optional[str] = None
|
|
122
122
|
self._flask_app_cache: Optional[Flask] = None
|
|
123
123
|
self._limiter_enabled = False
|
|
@@ -68,8 +68,8 @@ class SwaggerSchema(SwaggerModel):
|
|
|
68
68
|
if '$ref' in json.dumps(json_schema):
|
|
69
69
|
while '$ref' in json.dumps(json_schema):
|
|
70
70
|
json_schema = replace_value_in_dict(json_schema.copy(), json_schema.copy())
|
|
71
|
-
if '
|
|
72
|
-
del json_schema['
|
|
71
|
+
if '$defs' in json_schema:
|
|
72
|
+
del json_schema['$defs']
|
|
73
73
|
self.type = json_schema['type']
|
|
74
74
|
if json_schema['type'] == 'object':
|
|
75
75
|
self.properties = json_schema.get('properties', dict())
|
|
@@ -508,17 +508,29 @@ class SwaggerPath(SwaggerModel):
|
|
|
508
508
|
swagger_request_type.add_swagger_model(parameters)
|
|
509
509
|
query_schema = query_model.model_json_schema()
|
|
510
510
|
query_required_fields = set(query_schema.get('required')) if query_schema.get('required') is not None else set()
|
|
511
|
-
query_definitions = query_schema.get('
|
|
511
|
+
query_definitions = query_schema.get('$defs')
|
|
512
512
|
for parameter_name, parameter_spec in query_schema.get('properties').items():
|
|
513
|
-
if parameter_spec.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
513
|
+
if 'anyOf' in parameter_spec.keys():
|
|
514
|
+
if parameter_spec.get('anyOf')[0].get('$ref') is not None:
|
|
515
|
+
definition = query_definitions.get(parameter_spec.get('$ref', '').split('/')[-1], {})
|
|
516
|
+
parameter_models.add(SwaggerQueryParameter(
|
|
517
|
+
input_type=definition.get('type', 'string'),
|
|
518
|
+
input_format=definition.get('format'),
|
|
519
|
+
default_value=definition.get('default'),
|
|
520
|
+
name=parameter_name,
|
|
521
|
+
description=definition.get('description'),
|
|
522
|
+
enum=definition.get('enum'),
|
|
523
|
+
required=parameter_name in query_required_fields,
|
|
524
|
+
))
|
|
525
|
+
if parameter_spec.get('anyOf')[0].get('type') is not None:
|
|
526
|
+
parameter_models.add(SwaggerQueryParameter(
|
|
527
|
+
input_type=parameter_spec.get('anyOf')[0].get('type'),
|
|
528
|
+
input_format=parameter_spec.get('format'),
|
|
529
|
+
default_value=parameter_spec.get('default'),
|
|
530
|
+
name=parameter_name,
|
|
531
|
+
description=parameter_spec.get('description'),
|
|
532
|
+
required=parameter_name in query_required_fields,
|
|
533
|
+
))
|
|
522
534
|
elif parameter_spec.get('$ref') is not None:
|
|
523
535
|
definition = query_definitions.get(parameter_spec.get('$ref', '').split('/')[-1], {})
|
|
524
536
|
parameter_models.add(SwaggerQueryParameter(
|
|
@@ -530,6 +542,15 @@ class SwaggerPath(SwaggerModel):
|
|
|
530
542
|
enum=definition.get('enum'),
|
|
531
543
|
required=parameter_name in query_required_fields,
|
|
532
544
|
))
|
|
545
|
+
elif parameter_spec.get('type') is not None:
|
|
546
|
+
parameter_models.add(SwaggerQueryParameter(
|
|
547
|
+
input_type=parameter_spec.get('type') or parameter_spec.get('anyOf')[0].get('type'),
|
|
548
|
+
input_format=parameter_spec.get('format'),
|
|
549
|
+
default_value=parameter_spec.get('default'),
|
|
550
|
+
name=parameter_name,
|
|
551
|
+
description=parameter_spec.get('description'),
|
|
552
|
+
required=parameter_name in query_required_fields,
|
|
553
|
+
))
|
|
533
554
|
if isinstance(parameters, SwaggerModel):
|
|
534
555
|
parameters.add_swagger_models(parameter_models)
|
|
535
556
|
elif isinstance(parameters, list):
|
|
@@ -5,7 +5,7 @@ def replace_value_in_dict(item: Union[List, Dict], original_schema): # type: ig
|
|
|
5
5
|
if isinstance(item, list):
|
|
6
6
|
return [replace_value_in_dict(i, original_schema) for i in item]
|
|
7
7
|
elif isinstance(item, dict):
|
|
8
|
-
if list(item.keys())
|
|
8
|
+
if '$ref' in list(item.keys()):
|
|
9
9
|
definitions = item['$ref'][2:].split('/')
|
|
10
10
|
res = original_schema.copy()
|
|
11
11
|
for definition in definitions:
|
|
@@ -2,7 +2,7 @@ import dataclasses
|
|
|
2
2
|
import decimal
|
|
3
3
|
import json
|
|
4
4
|
from base64 import b64encode
|
|
5
|
-
from datetime import date, datetime
|
|
5
|
+
from datetime import date, datetime, time
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from json import JSONEncoder
|
|
8
8
|
from typing import Dict, Any, Union, List, Optional, TYPE_CHECKING
|
|
@@ -66,6 +66,8 @@ class CustomJSONEncoder(JSONEncoder):
|
|
|
66
66
|
return str(obj.isoformat())
|
|
67
67
|
if isinstance(obj, date):
|
|
68
68
|
return str(obj.isoformat())
|
|
69
|
+
if isinstance(obj, time):
|
|
70
|
+
return str(obj.isoformat())
|
|
69
71
|
if isinstance(obj, UUID):
|
|
70
72
|
return str(obj)
|
|
71
73
|
if isinstance(obj, Enum):
|
|
@@ -2,6 +2,8 @@ from dataclasses import dataclass
|
|
|
2
2
|
from types import NoneType
|
|
3
3
|
from typing import NamedTuple, Type, Tuple, _GenericAlias, _UnionGenericAlias, Any, Union, Optional, TypeVar, Generic, Callable # type: ignore
|
|
4
4
|
|
|
5
|
+
from ul_api_utils.api_resource.api_response import RootJsonApiResponsePayload
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class TypingType(NamedTuple):
|
|
7
9
|
value: Type[Any]
|
|
@@ -47,6 +49,7 @@ def unwrap_typing(t: Type[Any]) -> Union[TypingGeneric, TypingUnion, TypingType,
|
|
|
47
49
|
|
|
48
50
|
T = TypeVar('T')
|
|
49
51
|
TVal = TypeVar('TVal')
|
|
52
|
+
TValRoot = TypeVar('TValRoot', bound=RootJsonApiResponsePayload[Any])
|
|
50
53
|
|
|
51
54
|
|
|
52
55
|
def default_constructor(value_type: Type[TVal], data: Any) -> TVal:
|
|
@@ -72,7 +75,7 @@ class UnwrappedOptionalObjOrListOfObj(Generic[TVal]):
|
|
|
72
75
|
return constructor(self.value_type, payload) # type: ignore
|
|
73
76
|
|
|
74
77
|
@staticmethod
|
|
75
|
-
def parse(t: Type[Any], type_constraint: Optional[Type[TVal]]) -> 'Optional[UnwrappedOptionalObjOrListOfObj[TVal]]':
|
|
78
|
+
def parse(t: Type[Any], type_constraint: Optional[Type[TVal | TValRoot]]) -> 'Optional[UnwrappedOptionalObjOrListOfObj[TVal]]':
|
|
76
79
|
unwrapped_t = unwrap_typing(t)
|
|
77
80
|
|
|
78
81
|
many = False
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from typing import Union, List
|
|
5
|
+
from ul_api_utils.validators.custom_fields import QueryParamsSeparatedList
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ModelStr(BaseModel):
|
|
9
|
+
param: QueryParamsSeparatedList[str]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ModelInt(BaseModel):
|
|
13
|
+
param: QueryParamsSeparatedList[int]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize(
|
|
17
|
+
"model, input_data, expected_output",
|
|
18
|
+
[
|
|
19
|
+
pytest.param(ModelStr, "first_array_element,second,third,this", ["first_array_element", "second", "third", "this"]),
|
|
20
|
+
pytest.param(ModelStr, ["first_array_element,second,third,this"], ["first_array_element", "second", "third", "this"]),
|
|
21
|
+
pytest.param(ModelInt, ["1,2,3,4,5"], [1, 2, 3, 4, 5]),
|
|
22
|
+
pytest.param(ModelStr, 'first_array_element,"second,third",this', ["first_array_element", "second,third", "this"]),
|
|
23
|
+
pytest.param(ModelStr, ['first_array_element,"second,third",this'], ["first_array_element", "second,third", "this"]),
|
|
24
|
+
pytest.param(ModelStr, '"first_array_element,second,third",this, "1,2"', ["first_array_element,second,third", "this", "1,2"]),
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
def test__query_params_separated_list(
|
|
28
|
+
model: Union[ModelStr, ModelInt], input_data: Union[List[str], str], expected_output: List[Union[str, int]],
|
|
29
|
+
) -> None:
|
|
30
|
+
instance = model(param=input_data) # type: ignore
|
|
31
|
+
assert isinstance(instance.param, list)
|
|
32
|
+
assert instance.param == expected_output
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import csv
|
|
2
|
-
from typing import TypeVar, Generic, List, Union,
|
|
2
|
+
from typing import TypeVar, Generic, List, Union, Annotated, Any, get_args
|
|
3
3
|
from uuid import UUID
|
|
4
4
|
|
|
5
|
-
from pydantic import Field, StringConstraints,
|
|
6
|
-
from pydantic_core import ValidationError, InitErrorDetails
|
|
7
|
-
from pydantic_core.core_schema import ValidationInfo
|
|
5
|
+
from pydantic import Field, StringConstraints, BeforeValidator
|
|
8
6
|
|
|
9
7
|
from ul_api_utils.const import CRON_EXPRESSION_VALIDATION_REGEX, MIN_UTC_OFFSET_SECONDS, MAX_UTC_OFFSET_SECONDS
|
|
10
8
|
|
|
@@ -29,56 +27,34 @@ PgTypePositiveInt64Annotation = Annotated[int, Field(ge=0, le=922337203685477580
|
|
|
29
27
|
QueryParamsSeparatedListValueType = TypeVar('QueryParamsSeparatedListValueType')
|
|
30
28
|
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
that it is a list.
|
|
36
|
-
|
|
37
|
-
F.E. Query string is ?foo=1,2
|
|
30
|
+
def validate_query_params(value: Union[str, List[str]], type_: type) -> List[QueryParamsSeparatedListValueType]:
|
|
31
|
+
def process_item(item: str) -> QueryParamsSeparatedListValueType:
|
|
32
|
+
return type_(item.strip())
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
if isinstance(value, list):
|
|
35
|
+
result: list[QueryParamsSeparatedListValueType] = []
|
|
36
|
+
for item in value:
|
|
37
|
+
if isinstance(item, str):
|
|
38
|
+
reader = csv.reader([item], skipinitialspace=True)
|
|
39
|
+
result.extend(process_item(sub_item) for row in reader for sub_item in row)
|
|
40
|
+
else:
|
|
41
|
+
raise ValueError("List items must be strings")
|
|
42
|
+
elif isinstance(value, str):
|
|
43
|
+
reader = csv.reader([value], skipinitialspace=True)
|
|
44
|
+
result = [process_item(item) for row in reader for item in row]
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError("Value must be a string or a list of strings")
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
def __class_getitem__(cls, item: Any) -> QueryParamsSeparatedListValueType:
|
|
46
|
-
new_cls = super().__class_getitem__(item) # type: ignore
|
|
47
|
-
new_cls._contains_type = item
|
|
48
|
-
return new_cls
|
|
48
|
+
return result
|
|
49
49
|
|
|
50
|
-
@classmethod
|
|
51
|
-
def __get_validators__(cls) -> Generator[Callable[[Union[List[str], str], ValidationInfo], List[QueryParamsSeparatedListValueType]], None, None]:
|
|
52
|
-
yield cls.validate
|
|
53
50
|
|
|
51
|
+
class QueryParamsSeparatedList(Generic[QueryParamsSeparatedListValueType]):
|
|
54
52
|
@classmethod
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if not isinstance(query_param, list):
|
|
65
|
-
query_param = [query_param]
|
|
66
|
-
|
|
67
|
-
reader = csv.reader(query_param, skipinitialspace=True)
|
|
68
|
-
splitted = next(reader)
|
|
69
|
-
|
|
70
|
-
validated_items = []
|
|
71
|
-
errors: List[InitErrorDetails] = []
|
|
72
|
-
|
|
73
|
-
for idx, value in enumerate(splitted):
|
|
74
|
-
try:
|
|
75
|
-
validated_items.append(adapter.validate_python(value))
|
|
76
|
-
except ValidationError as e:
|
|
77
|
-
for error in e.errors(include_url=False):
|
|
78
|
-
error['loc'] = ('param', idx)
|
|
79
|
-
errors.append(error) # type: ignore
|
|
80
|
-
|
|
81
|
-
if errors:
|
|
82
|
-
raise ValidationError.from_exception_data("List validation error", errors)
|
|
83
|
-
|
|
84
|
-
return validated_items
|
|
53
|
+
def __get_pydantic_core_schema__(cls, source_type: type, handler: Any) -> Any:
|
|
54
|
+
inner_type = get_args(source_type)[0]
|
|
55
|
+
return handler(
|
|
56
|
+
Annotated[
|
|
57
|
+
List[inner_type], # type: ignore
|
|
58
|
+
BeforeValidator(lambda x: validate_query_params(x, inner_type))
|
|
59
|
+
]
|
|
60
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ul-api-utils
|
|
3
|
-
Version: 9.0.
|
|
3
|
+
Version: 9.0.0a7
|
|
4
4
|
Summary: Python api utils
|
|
5
5
|
Author: Unic-lab
|
|
6
6
|
Author-email:
|
|
@@ -45,8 +45,8 @@ Requires-Dist: types-jinja2 (==2.11.9)
|
|
|
45
45
|
Requires-Dist: xlsxwriter (==3.2.2)
|
|
46
46
|
Requires-Dist: werkzeug (==3.1.3)
|
|
47
47
|
Requires-Dist: frozendict (==2.4.4)
|
|
48
|
-
Requires-Dist: wtforms (==3.
|
|
49
|
-
Requires-Dist: wtforms-alchemy (==0.
|
|
48
|
+
Requires-Dist: wtforms (==3.0.1)
|
|
49
|
+
Requires-Dist: wtforms-alchemy (==0.18.0)
|
|
50
50
|
Requires-Dist: pathvalidate (==3.2.3)
|
|
51
51
|
|
|
52
52
|
# Generic library api-utils
|
|
@@ -26,12 +26,12 @@ ul_api_utils/sentry.py,sha256=UH_SwZCAoKH-Nw5B9CVQMoF-b1BJOp-ZTzwqUZ3Oq84,1801
|
|
|
26
26
|
ul_api_utils/access/__init__.py,sha256=NUyRNvCVwfePrfdn5ATFVfHeSO3iq4-Syeup4IAZGzs,4526
|
|
27
27
|
ul_api_utils/api_resource/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
ul_api_utils/api_resource/api_request.py,sha256=l3UNeENPq5pr6ICEF5iPKHT6Hy0bgxjGoKzhKmMyJVk,3486
|
|
29
|
-
ul_api_utils/api_resource/api_resource.py,sha256=
|
|
29
|
+
ul_api_utils/api_resource/api_resource.py,sha256=dc-4eLsj_-2ylcMWp4DBPO0S-JhzZ_IOJtkZVIkBFlo,17649
|
|
30
30
|
ul_api_utils/api_resource/api_resource_config.py,sha256=l9OYJy75UZLshOkEQDO5jlhXeb5H4HDPu-nLOjuoexw,769
|
|
31
31
|
ul_api_utils/api_resource/api_resource_error_handling.py,sha256=E0SWpjFSIP-4SumbgzrHtFuFiGe9q38WsvLROt0YcPE,1168
|
|
32
|
-
ul_api_utils/api_resource/api_resource_fn_typing.py,sha256=
|
|
32
|
+
ul_api_utils/api_resource/api_resource_fn_typing.py,sha256=gyWEnYuepvaVW4O6ljpoPA5S4i9EeKsYBaAN2mHXDcw,18963
|
|
33
33
|
ul_api_utils/api_resource/api_resource_type.py,sha256=mgjSQI3swGpgpLI6y35LYtFrdN-kXyV5cQorwGW7h6g,462
|
|
34
|
-
ul_api_utils/api_resource/api_response.py,sha256=
|
|
34
|
+
ul_api_utils/api_resource/api_response.py,sha256=kGoYJHa51_vDlCG492AdEqMMmLtdRJIoym5Nwp3MqmY,9773
|
|
35
35
|
ul_api_utils/api_resource/api_response_db.py,sha256=ucY6ANPlHZml7JAbvq-PL85z0bvERTjEJKvz-REPyok,888
|
|
36
36
|
ul_api_utils/api_resource/api_response_payload_alias.py,sha256=FoD0LhQGZ2T8A5-VKRX5ADyzSgm7_dd3qxU2BgCVXkA,587
|
|
37
37
|
ul_api_utils/api_resource/db_types.py,sha256=NxHBYetUogWZow7Vhd3e00Y3L62-dxjwRzJlXywYlV4,439
|
|
@@ -50,22 +50,22 @@ ul_api_utils/commands/start/wsgi.py,sha256=ZBFx66XP8iNliK3vkB6yWRkCq2-ItgieweBmW
|
|
|
50
50
|
ul_api_utils/conf/ul-debugger-main.js,sha256=XgzaH1AWAG4_PW9mRaoJGJOKeEA5T61j7yIouZjU65s,999747
|
|
51
51
|
ul_api_utils/conf/ul-debugger-ui.js,sha256=bNwv6ntu8RjCrH33H5eTUtFXdBoMrgFt3P87ujbBmRU,2043
|
|
52
52
|
ul_api_utils/debug/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
-
ul_api_utils/debug/debugger.py,sha256=
|
|
53
|
+
ul_api_utils/debug/debugger.py,sha256=dXl7xTMKEvYNKOmo6EG2CK69KrMNGnq7s25yDRttev8,3740
|
|
54
54
|
ul_api_utils/debug/malloc.py,sha256=OvESxpn8sQMyAb64DxnYUAofRZdnJ1I199IUBWiIoa4,3274
|
|
55
|
-
ul_api_utils/debug/stat.py,sha256=
|
|
55
|
+
ul_api_utils/debug/stat.py,sha256=Txn-NCeF_edeCuFb7h8o_tI2boeQm92fz9o8M7iifg8,14066
|
|
56
56
|
ul_api_utils/encrypt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
57
|
ul_api_utils/encrypt/encrypt_decrypt_abstract.py,sha256=V2lFKoBxUKO5AYvHWLYv-rTvYXiGDPj3nOkU1noebDI,334
|
|
58
58
|
ul_api_utils/encrypt/encrypt_decrypt_aes_xtea.py,sha256=Gj-X_CoYY2PPrczTcG9Ho_dgordsh9jKB_cVnVEE3XU,2356
|
|
59
59
|
ul_api_utils/internal_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
-
ul_api_utils/internal_api/internal_api.py,sha256=
|
|
60
|
+
ul_api_utils/internal_api/internal_api.py,sha256=45fHQdNXLAhmw1w-cAQJglJ8b8ZDL8_Ibm_9sG4zeO4,13967
|
|
61
61
|
ul_api_utils/internal_api/internal_api_check_context.py,sha256=OEDkI06wArjBNSjRSzARvEIPDhTzmfvYThgpvFJgUZU,1490
|
|
62
62
|
ul_api_utils/internal_api/internal_api_error.py,sha256=sdm3V2VLSfFVBmxaeo2Wy2wkhmxWTXGsCCR-u08ChMg,471
|
|
63
|
-
ul_api_utils/internal_api/internal_api_response.py,sha256=
|
|
63
|
+
ul_api_utils/internal_api/internal_api_response.py,sha256=TyS7iLbvTFb0oAcElK3fUAB5jNGQDtiJUrESn5BTTHM,11917
|
|
64
64
|
ul_api_utils/internal_api/__tests__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
65
|
ul_api_utils/internal_api/__tests__/internal_api.py,sha256=X2iopeso6vryszeeA__lcqXQVtz3Nwt3ngH7M4OuN1U,1116
|
|
66
66
|
ul_api_utils/internal_api/__tests__/internal_api_content_type.py,sha256=mfiYPkzKtfZKFpi4RSnWAoCd6mRijr6sFsa2TF-s5t8,749
|
|
67
67
|
ul_api_utils/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
|
-
ul_api_utils/modules/api_sdk.py,sha256=
|
|
68
|
+
ul_api_utils/modules/api_sdk.py,sha256=w2STpA6znKrrsD3iSTs07vfV_K2w_gSEWNGpz_Q9bZc,26438
|
|
69
69
|
ul_api_utils/modules/api_sdk_config.py,sha256=ZUR48tIJeFlPJTSjyXzKfXaCKPtfqeaA0mlLX42SSFY,2137
|
|
70
70
|
ul_api_utils/modules/api_sdk_jwt.py,sha256=2XRfb0LxHUnldSL67S60v1uyoDpVPNaq4zofUtkeg88,15112
|
|
71
71
|
ul_api_utils/modules/intermediate_state.py,sha256=7ZZ3Sypbb8LaSfrVhaXaWRDnj8oyy26NUbmFK7vr-y4,1270
|
|
@@ -111,11 +111,11 @@ ul_api_utils/utils/deprecated.py,sha256=xR3ELgoDj7vJEY4CAYeEhdbtSJTfkukbjxcULtpM
|
|
|
111
111
|
ul_api_utils/utils/flags.py,sha256=AYN5nKWp4-uu6PSlPptL7ZiLqr3Pu-x5dffF6SBsqfg,957
|
|
112
112
|
ul_api_utils/utils/imports.py,sha256=i8PhoD0c_jnWTeXt_VxW_FihynwXSL_dHRT7jQiFyXE,376
|
|
113
113
|
ul_api_utils/utils/instance_checks.py,sha256=9punTfY5uabuJhmSGLfTgBqRderoFysCXBSI8rvbPco,467
|
|
114
|
-
ul_api_utils/utils/json_encoder.py,sha256=
|
|
114
|
+
ul_api_utils/utils/json_encoder.py,sha256=k8LlEvac3nXpk30S8t5YJir9HipcRHPVVMbYqnDYWV4,4791
|
|
115
115
|
ul_api_utils/utils/load_modules.py,sha256=_CPmQuB6o_33FE6zFl_GyO5xS5gmjfNffB6k-cglKAA,685
|
|
116
116
|
ul_api_utils/utils/token_check.py,sha256=-Quuh8gOs9fNE1shYhdiMpQedafsLN7MB2ilSxG_F8E,489
|
|
117
117
|
ul_api_utils/utils/token_check_through_request.py,sha256=OyyObu6Btk9br7auIYvWcMULhNznNSD5T0mWOwZX7Uk,663
|
|
118
|
-
ul_api_utils/utils/unwrap_typing.py,sha256=
|
|
118
|
+
ul_api_utils/utils/unwrap_typing.py,sha256=uObDLxYcRni6ieN9FuvWTa350HjSUIpgTb7FrIUzsVI,4349
|
|
119
119
|
ul_api_utils/utils/uuid_converter.py,sha256=OZMuySkoALrQQOe312_BHVWN20Sz5frKuH9KYziAGsU,565
|
|
120
120
|
ul_api_utils/utils/__tests__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
121
121
|
ul_api_utils/utils/__tests__/api_path_version.py,sha256=n7VFVdwFqP_FS6PE3OUCS68oU3tG78xM4HxrKShLhNw,898
|
|
@@ -126,12 +126,12 @@ ul_api_utils/utils/flask_swagger_generator/exceptions.py,sha256=yA4IsUyxh5puyoYz
|
|
|
126
126
|
ul_api_utils/utils/flask_swagger_generator/specifiers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
127
127
|
ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_models.py,sha256=P52azB25Ncpp6I5j5bq4LLAJHDLSORFVJp9bHoXvpQk,1698
|
|
128
128
|
ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_specifier.py,sha256=9Cf1ijk90IDYUDG8kTjK4cxjdxpYjWZz1tKJHPCeDAA,1513
|
|
129
|
-
ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_three_specifier.py,sha256=
|
|
129
|
+
ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_three_specifier.py,sha256=XxQ0IdC-WRs_qgS0tFcD_yqApdH_gLR--CB-uvB29JA,32355
|
|
130
130
|
ul_api_utils/utils/flask_swagger_generator/specifiers/swagger_version.py,sha256=A14IRG-e2KL2SlFbHep2FH0uMRIHPhfd7KLkYdtWrfA,1312
|
|
131
131
|
ul_api_utils/utils/flask_swagger_generator/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
132
132
|
ul_api_utils/utils/flask_swagger_generator/utils/input_type.py,sha256=Ynp3zI5q1F0Tl_eTdNbWoCxRKPwBCJkwJOoeHE2pTRE,2533
|
|
133
133
|
ul_api_utils/utils/flask_swagger_generator/utils/parameter_type.py,sha256=bTE_kDUPNYL7Qr1AV_h4pXEWiLeiVYVFBxAWMkLYzPU,1557
|
|
134
|
-
ul_api_utils/utils/flask_swagger_generator/utils/replace_in_dict.py,sha256=
|
|
134
|
+
ul_api_utils/utils/flask_swagger_generator/utils/replace_in_dict.py,sha256=xGF78881vi8xAWbJ9eaqBzsuMFHSJGZNYlm6bmC-5jk,742
|
|
135
135
|
ul_api_utils/utils/flask_swagger_generator/utils/request_type.py,sha256=fx4ltfODfYmXx3i31BTZGAtwzrMqYbk48VQavQirFa8,1504
|
|
136
136
|
ul_api_utils/utils/flask_swagger_generator/utils/schema_type.py,sha256=NqFRHjSWZgg6fNYE-CG7vwLa57ie9WjooB2YfVTs4UM,294
|
|
137
137
|
ul_api_utils/utils/flask_swagger_generator/utils/security_type.py,sha256=AcWOQZBbUPXZyyTbmGFRt5p4Jx1uwETuxNRtXXht16I,1148
|
|
@@ -143,14 +143,14 @@ ul_api_utils/utils/memory_db/errors.py,sha256=Bw1O1Y_WTCeCRNgb9iwrGT1fst6iHORhrN
|
|
|
143
143
|
ul_api_utils/utils/memory_db/repository.py,sha256=0AI0-VA4Md54BrAuZLX0Lg7rxyyGv54aWiiNUPycGSA,3760
|
|
144
144
|
ul_api_utils/utils/memory_db/__tests__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
145
145
|
ul_api_utils/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
146
|
-
ul_api_utils/validators/custom_fields.py,sha256=
|
|
146
|
+
ul_api_utils/validators/custom_fields.py,sha256=6pNvwHMhup9pqGOvqp57UsPAfmgvVyLujBKooBY-C1I,2998
|
|
147
147
|
ul_api_utils/validators/validate_empty_object.py,sha256=3Ck_iwyJE_M5e7l6s1i88aqb73zIt06uaLrMG2PAb0A,299
|
|
148
148
|
ul_api_utils/validators/validate_uuid.py,sha256=EfvlRirv2EW0Z6w3s8E8rUa9GaI8qXZkBWhnPs8NFrA,257
|
|
149
149
|
ul_api_utils/validators/__tests__/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
150
|
-
ul_api_utils/validators/__tests__/test_custom_fields.py,sha256=
|
|
151
|
-
ul_api_utils-9.0.
|
|
152
|
-
ul_api_utils-9.0.
|
|
153
|
-
ul_api_utils-9.0.
|
|
154
|
-
ul_api_utils-9.0.
|
|
155
|
-
ul_api_utils-9.0.
|
|
156
|
-
ul_api_utils-9.0.
|
|
150
|
+
ul_api_utils/validators/__tests__/test_custom_fields.py,sha256=omXI_PPefDfCehEVJxEevep8phY6aySjLnpW_usT85U,1385
|
|
151
|
+
ul_api_utils-9.0.0a7.dist-info/LICENSE,sha256=6Qo8OdcqI8aGrswJKJYhST-bYqxVQBQ3ujKdTSdq-80,1062
|
|
152
|
+
ul_api_utils-9.0.0a7.dist-info/METADATA,sha256=R2-BjpDjq_LDuDxY0K1b_ZCPFKXeFgvwBm_i7tGRB08,14714
|
|
153
|
+
ul_api_utils-9.0.0a7.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
|
154
|
+
ul_api_utils-9.0.0a7.dist-info/entry_points.txt,sha256=8tL3ySHWTyJMuV1hx1fHfN8zumDVOCOm63w3StphkXg,53
|
|
155
|
+
ul_api_utils-9.0.0a7.dist-info/top_level.txt,sha256=1XsW8iOSFaH4LOzDcnNyxHpHrbKU3fSn-aIAxe04jmw,21
|
|
156
|
+
ul_api_utils-9.0.0a7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|