apexdevkit 1.7.2__tar.gz → 1.8.2__tar.gz
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.
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/PKG-INFO +1 -1
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/http/fake.py +4 -1
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/http/fluent.py +4 -1
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/http/httpx.py +6 -1
- apexdevkit-1.8.2/apexdevkit/testing/rest.py +294 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/pyproject.toml +1 -1
- apexdevkit-1.7.2/apexdevkit/testing/rest.py +0 -366
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/LICENSE +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/README.md +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/error.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/fastapi/resource.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/fastapi/service.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/formatter.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/repository/base.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/repository/connector.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/repository/database.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/repository/in_memory.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.7.2 → apexdevkit-1.8.2}/apexdevkit/testing/fake.py +0 -0
|
@@ -48,6 +48,9 @@ class FakeHttp:
|
|
|
48
48
|
|
|
49
49
|
_request: InterceptedRequest = field(init=False)
|
|
50
50
|
|
|
51
|
+
def with_endpoint(self, value: str) -> Self: # pragma: no cover
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
51
54
|
def with_header(self, key: str, value: str) -> Self:
|
|
52
55
|
self.headers[key] = value
|
|
53
56
|
|
|
@@ -63,7 +66,7 @@ class FakeHttp:
|
|
|
63
66
|
|
|
64
67
|
return self
|
|
65
68
|
|
|
66
|
-
def request(self, method: HttpMethod, endpoint: str) -> HttpResponse:
|
|
69
|
+
def request(self, method: HttpMethod, endpoint: str = "") -> HttpResponse:
|
|
67
70
|
self._request = InterceptedRequest(method, endpoint)
|
|
68
71
|
|
|
69
72
|
return self.response
|
|
@@ -8,6 +8,9 @@ from apexdevkit.http.json import JsonDict
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Http(Protocol): # pragma: no cover
|
|
11
|
+
def with_endpoint(self, value: str) -> Http:
|
|
12
|
+
pass
|
|
13
|
+
|
|
11
14
|
def with_header(self, key: str, value: str) -> Http:
|
|
12
15
|
pass
|
|
13
16
|
|
|
@@ -17,7 +20,7 @@ class Http(Protocol): # pragma: no cover
|
|
|
17
20
|
def with_json(self, value: JsonDict) -> Http:
|
|
18
21
|
pass
|
|
19
22
|
|
|
20
|
-
def request(self, method: HttpMethod, endpoint: str) -> HttpResponse:
|
|
23
|
+
def request(self, method: HttpMethod, endpoint: str = "") -> HttpResponse:
|
|
21
24
|
pass
|
|
22
25
|
|
|
23
26
|
|
|
@@ -24,6 +24,11 @@ class Httpx:
|
|
|
24
24
|
def create_for(cls, url: str) -> Self:
|
|
25
25
|
return cls(Client(base_url=url), HttpxConfig())
|
|
26
26
|
|
|
27
|
+
def with_endpoint(self, value: str) -> Httpx:
|
|
28
|
+
self.client.base_url = self.client.base_url.join(value)
|
|
29
|
+
|
|
30
|
+
return self
|
|
31
|
+
|
|
27
32
|
def with_header(self, key: str, value: str) -> Httpx:
|
|
28
33
|
return Httpx(self.client, self.config.with_header(key, value))
|
|
29
34
|
|
|
@@ -33,7 +38,7 @@ class Httpx:
|
|
|
33
38
|
def with_json(self, value: JsonDict) -> Httpx:
|
|
34
39
|
return Httpx(self.client, self.config.with_json(value))
|
|
35
40
|
|
|
36
|
-
def request(self, method: HttpMethod, endpoint: str) -> HttpResponse:
|
|
41
|
+
def request(self, method: HttpMethod, endpoint: str = "") -> HttpResponse:
|
|
37
42
|
return _HttpxResponse(self.client.request(method.name, endpoint, **self.config))
|
|
38
43
|
|
|
39
44
|
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import Any, Iterable, Self
|
|
6
|
+
|
|
7
|
+
from apexdevkit.http import Http, HttpUrl, JsonDict
|
|
8
|
+
from apexdevkit.http.fluent import HttpMethod, HttpResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class RestResource:
|
|
13
|
+
http: Http
|
|
14
|
+
name: RestfulName
|
|
15
|
+
|
|
16
|
+
def create_one(self) -> RestRequest:
|
|
17
|
+
return RestRequest(
|
|
18
|
+
self.name,
|
|
19
|
+
HttpRequest(
|
|
20
|
+
HttpMethod.post,
|
|
21
|
+
self.http.with_endpoint(self.name.plural),
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def create_many(self) -> RestRequest:
|
|
26
|
+
return RestRequest(
|
|
27
|
+
self.name,
|
|
28
|
+
HttpRequest(
|
|
29
|
+
HttpMethod.post,
|
|
30
|
+
self.http.with_endpoint(self.name.plural).with_endpoint("batch"),
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def read_one(self) -> RestRequest:
|
|
35
|
+
return RestRequest(
|
|
36
|
+
self.name,
|
|
37
|
+
HttpRequest(
|
|
38
|
+
HttpMethod.get,
|
|
39
|
+
self.http.with_endpoint(self.name.plural),
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def read_all(self) -> RestRequest:
|
|
44
|
+
return RestRequest(
|
|
45
|
+
self.name,
|
|
46
|
+
HttpRequest(
|
|
47
|
+
HttpMethod.get,
|
|
48
|
+
self.http.with_endpoint(self.name.plural),
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def update_one(self) -> RestRequest:
|
|
53
|
+
return RestRequest(
|
|
54
|
+
self.name,
|
|
55
|
+
HttpRequest(
|
|
56
|
+
HttpMethod.patch,
|
|
57
|
+
self.http.with_endpoint(self.name.plural),
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def update_many(self) -> RestRequest:
|
|
62
|
+
return RestRequest(
|
|
63
|
+
self.name,
|
|
64
|
+
HttpRequest(
|
|
65
|
+
HttpMethod.patch,
|
|
66
|
+
self.http.with_endpoint(self.name.plural),
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def replace_one(self) -> RestRequest:
|
|
71
|
+
return RestRequest(
|
|
72
|
+
self.name,
|
|
73
|
+
HttpRequest(
|
|
74
|
+
HttpMethod.put,
|
|
75
|
+
self.http.with_endpoint(self.name.plural),
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def replace_many(self) -> RestRequest:
|
|
80
|
+
return RestRequest(
|
|
81
|
+
self.name,
|
|
82
|
+
HttpRequest(
|
|
83
|
+
HttpMethod.put,
|
|
84
|
+
self.http.with_endpoint(self.name.plural).with_endpoint("batch"),
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def delete_one(self) -> RestRequest:
|
|
89
|
+
return RestRequest(
|
|
90
|
+
self.name,
|
|
91
|
+
HttpRequest(
|
|
92
|
+
HttpMethod.delete,
|
|
93
|
+
self.http.with_endpoint(self.name.plural),
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class RestCollection(RestResource):
|
|
100
|
+
def sub_resource(self, name: str) -> RestItem:
|
|
101
|
+
return RestItem(self.http.with_endpoint(self.name.plural), RestfulName(name))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class RestItem(RestResource):
|
|
106
|
+
def sub_resource(self, name: str) -> RestItem:
|
|
107
|
+
return RestItem(self.http.with_endpoint(self.name.singular), RestfulName(name))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class RestfulName:
|
|
112
|
+
singular: str
|
|
113
|
+
|
|
114
|
+
plural: str = ""
|
|
115
|
+
|
|
116
|
+
def __post_init__(self) -> None:
|
|
117
|
+
self.plural = self.plural or as_plural(self.singular)
|
|
118
|
+
|
|
119
|
+
def __add__(self, other: str) -> str:
|
|
120
|
+
return HttpUrl(self.plural) + other
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def as_plural(singular: str) -> str: # pragma: no cover
|
|
124
|
+
if singular.endswith("y"):
|
|
125
|
+
return singular[:-1] + "ies"
|
|
126
|
+
|
|
127
|
+
if singular.endswith("ch") or singular.endswith("sh") or singular.endswith("ss"):
|
|
128
|
+
return singular[:-2] + "es"
|
|
129
|
+
|
|
130
|
+
if singular.endswith("s") or singular.endswith("z") or singular.endswith("x"):
|
|
131
|
+
return singular[:-1] + "es"
|
|
132
|
+
|
|
133
|
+
if singular.endswith("fe"):
|
|
134
|
+
return singular[:-2] + "ves"
|
|
135
|
+
|
|
136
|
+
if singular.endswith("f"):
|
|
137
|
+
return singular[:-1] + "ves"
|
|
138
|
+
|
|
139
|
+
return singular + "s"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class HttpRequest:
|
|
144
|
+
method: HttpMethod
|
|
145
|
+
http: Http
|
|
146
|
+
|
|
147
|
+
def with_endpoint(self, value: Any) -> HttpRequest:
|
|
148
|
+
self.http = self.http.with_endpoint(str(value))
|
|
149
|
+
|
|
150
|
+
return self
|
|
151
|
+
|
|
152
|
+
def with_param(self, name: str, value: Any) -> HttpRequest:
|
|
153
|
+
self.http = self.http.with_param(name, value)
|
|
154
|
+
|
|
155
|
+
return self
|
|
156
|
+
|
|
157
|
+
def with_json(self, value: JsonDict) -> HttpRequest:
|
|
158
|
+
self.http = self.http.with_json(value)
|
|
159
|
+
|
|
160
|
+
return self
|
|
161
|
+
|
|
162
|
+
def __call__(self) -> HttpResponse:
|
|
163
|
+
return self.http.request(self.method)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@dataclass
|
|
167
|
+
class RestRequest:
|
|
168
|
+
resource: RestfulName
|
|
169
|
+
request: HttpRequest
|
|
170
|
+
|
|
171
|
+
def with_id(self, value: Any) -> Self:
|
|
172
|
+
self.request = self.request.with_endpoint(value)
|
|
173
|
+
|
|
174
|
+
return self
|
|
175
|
+
|
|
176
|
+
def from_collection(self, value: list[JsonDict]) -> Self:
|
|
177
|
+
return self.with_data(
|
|
178
|
+
JsonDict({self.resource.plural: [dict(item) for item in value]})
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def and_data(self, value: JsonDict) -> Self:
|
|
182
|
+
return self.with_data(value)
|
|
183
|
+
|
|
184
|
+
def from_data(self, value: JsonDict) -> Self:
|
|
185
|
+
return self.with_data(value)
|
|
186
|
+
|
|
187
|
+
def with_data(self, value: JsonDict) -> Self:
|
|
188
|
+
self.request = self.request.with_json(value)
|
|
189
|
+
|
|
190
|
+
return self
|
|
191
|
+
|
|
192
|
+
def and_param(self, name: str, value: Any) -> Self:
|
|
193
|
+
return self.with_param(name, value)
|
|
194
|
+
|
|
195
|
+
def with_param(self, name: str, value: Any) -> Self:
|
|
196
|
+
self.request = self.request.with_param(name, str(value))
|
|
197
|
+
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
@cached_property
|
|
201
|
+
def response(self) -> HttpResponse:
|
|
202
|
+
return self.request()
|
|
203
|
+
|
|
204
|
+
def unpack(self) -> JsonDict:
|
|
205
|
+
return JsonDict(self.response.json()["data"][self.resource.singular])
|
|
206
|
+
|
|
207
|
+
def unpack_many(self) -> Iterable[JsonDict]:
|
|
208
|
+
items = self.response.json()["data"][self.resource.plural]
|
|
209
|
+
|
|
210
|
+
return [JsonDict(item) for item in items]
|
|
211
|
+
|
|
212
|
+
def ensure(self) -> RestResponse:
|
|
213
|
+
return RestResponse(
|
|
214
|
+
resource=self.resource,
|
|
215
|
+
json=JsonDict(self.response.json()),
|
|
216
|
+
http_code=self.response.code(),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@dataclass
|
|
221
|
+
class RestResponse:
|
|
222
|
+
resource: RestfulName
|
|
223
|
+
json: JsonDict
|
|
224
|
+
http_code: int
|
|
225
|
+
|
|
226
|
+
def fail(self) -> Self:
|
|
227
|
+
if self.http_code == 422:
|
|
228
|
+
return self
|
|
229
|
+
|
|
230
|
+
return self.with_status("fail")
|
|
231
|
+
|
|
232
|
+
def success(self) -> Self:
|
|
233
|
+
return self.with_status("success")
|
|
234
|
+
|
|
235
|
+
def with_status(self, value: str) -> Self:
|
|
236
|
+
assert self.json.value_of("status").to(str) == value
|
|
237
|
+
|
|
238
|
+
return self
|
|
239
|
+
|
|
240
|
+
def with_code(self, value: int) -> Self:
|
|
241
|
+
assert self.http_code == value
|
|
242
|
+
|
|
243
|
+
if self.http_code != 422:
|
|
244
|
+
assert self.json.value_of("code").to(int) == value
|
|
245
|
+
|
|
246
|
+
return self
|
|
247
|
+
|
|
248
|
+
def message(self, value: str) -> Self:
|
|
249
|
+
return self.with_message(value)
|
|
250
|
+
|
|
251
|
+
def and_message(self, value: str) -> Self:
|
|
252
|
+
return self.with_message(value)
|
|
253
|
+
|
|
254
|
+
def with_message(self, value: str) -> Self:
|
|
255
|
+
assert self.json.value_of("error").to(dict) == {"message": value}, self.json
|
|
256
|
+
|
|
257
|
+
return self
|
|
258
|
+
|
|
259
|
+
def and_item(self, value: Any) -> Self:
|
|
260
|
+
return self.with_item(value)
|
|
261
|
+
|
|
262
|
+
def with_item(self, value: Any) -> Self:
|
|
263
|
+
return self.with_data(
|
|
264
|
+
**{
|
|
265
|
+
self.resource.singular: dict(value)
|
|
266
|
+
if isinstance(value, JsonDict)
|
|
267
|
+
else value
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def and_collection(self, value: list[Any]) -> Self:
|
|
272
|
+
return self.with_collection(value)
|
|
273
|
+
|
|
274
|
+
def with_collection(self, values: list[Any]) -> Self:
|
|
275
|
+
return self.with_data(
|
|
276
|
+
**{
|
|
277
|
+
self.resource.plural: [
|
|
278
|
+
dict(value) if isinstance(value, JsonDict) else value
|
|
279
|
+
for value in values
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
count=len(values),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def and_no_data(self) -> Self:
|
|
286
|
+
return self.no_data()
|
|
287
|
+
|
|
288
|
+
def no_data(self) -> Self:
|
|
289
|
+
return self.with_data()
|
|
290
|
+
|
|
291
|
+
def with_data(self, **kwargs: Any) -> Self:
|
|
292
|
+
assert self.json.value_of("data").to(dict) == {**kwargs}, self.json
|
|
293
|
+
|
|
294
|
+
return self
|
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from functools import cached_property
|
|
5
|
-
from typing import Any, Iterable, Self
|
|
6
|
-
|
|
7
|
-
from fastapi.testclient import TestClient
|
|
8
|
-
|
|
9
|
-
from apexdevkit.annotation import deprecated
|
|
10
|
-
from apexdevkit.http import Http, HttpUrl, JsonDict
|
|
11
|
-
from apexdevkit.http.fluent import HttpMethod, HttpResponse
|
|
12
|
-
from apexdevkit.http.httpx import Httpx
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class RestResource:
|
|
17
|
-
http: TestClient | Http
|
|
18
|
-
name: RestfulName
|
|
19
|
-
|
|
20
|
-
def __post_init__(self) -> None:
|
|
21
|
-
if isinstance(self.http, TestClient):
|
|
22
|
-
self.http = Httpx(self.http)
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def _http(self) -> Http:
|
|
26
|
-
assert not isinstance(self.http, TestClient)
|
|
27
|
-
|
|
28
|
-
return self.http
|
|
29
|
-
|
|
30
|
-
def create_one(self) -> RestRequest:
|
|
31
|
-
return RestRequest(self.name, self._http, method=HttpMethod.post)
|
|
32
|
-
|
|
33
|
-
def create_many(self) -> CreateMany:
|
|
34
|
-
return CreateMany(self.name, self._http)
|
|
35
|
-
|
|
36
|
-
def read_one(self) -> RestRequest:
|
|
37
|
-
return RestRequest(self.name, self._http, method=HttpMethod.get)
|
|
38
|
-
|
|
39
|
-
def read_all(self) -> RestRequest:
|
|
40
|
-
return RestRequest(self.name, self._http, method=HttpMethod.get)
|
|
41
|
-
|
|
42
|
-
def update_one(self) -> RestRequest:
|
|
43
|
-
return RestRequest(self.name, self._http, method=HttpMethod.patch)
|
|
44
|
-
|
|
45
|
-
def update_many(self) -> UpdateMany:
|
|
46
|
-
return UpdateMany(self.name, self._http)
|
|
47
|
-
|
|
48
|
-
def replace_one(self) -> RestRequest:
|
|
49
|
-
return RestRequest(self.name, self._http, method=HttpMethod.put)
|
|
50
|
-
|
|
51
|
-
def replace_many(self) -> ReplaceMany:
|
|
52
|
-
return ReplaceMany(self.name, self._http)
|
|
53
|
-
|
|
54
|
-
def delete_one(self) -> RestRequest:
|
|
55
|
-
return RestRequest(self.name, self._http, method=HttpMethod.delete)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
@dataclass
|
|
59
|
-
class RestCollection(RestResource):
|
|
60
|
-
def sub_resource(self, name: str) -> RestItem:
|
|
61
|
-
assert isinstance(self.http, Httpx), "sub resource only works with Httpx"
|
|
62
|
-
|
|
63
|
-
client = self.http.client
|
|
64
|
-
client.base_url = client.base_url.join(self.name.plural)
|
|
65
|
-
|
|
66
|
-
return RestItem(self.http, RestfulName(name))
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@dataclass
|
|
70
|
-
class RestItem(RestResource):
|
|
71
|
-
def sub_resource(self, name: str) -> RestItem:
|
|
72
|
-
assert isinstance(self.http, Httpx), "sub resource only works with Httpx"
|
|
73
|
-
|
|
74
|
-
client = self.http.client
|
|
75
|
-
client.base_url = client.base_url.join(self.name.singular)
|
|
76
|
-
|
|
77
|
-
return RestItem(self.http, RestfulName(name))
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@dataclass
|
|
81
|
-
class RestfulName:
|
|
82
|
-
singular: str
|
|
83
|
-
|
|
84
|
-
plural: str = ""
|
|
85
|
-
|
|
86
|
-
def __post_init__(self) -> None:
|
|
87
|
-
self.plural = self.plural or as_plural(self.singular)
|
|
88
|
-
|
|
89
|
-
def __add__(self, other: str) -> str:
|
|
90
|
-
return HttpUrl(self.plural) + other
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def as_plural(singular: str) -> str:
|
|
94
|
-
if singular.endswith("y"):
|
|
95
|
-
return singular[:-1] + "ies"
|
|
96
|
-
|
|
97
|
-
if singular.endswith("ch") or singular.endswith("sh") or singular.endswith("ss"):
|
|
98
|
-
return singular[:-2] + "es"
|
|
99
|
-
|
|
100
|
-
if singular.endswith("s") or singular.endswith("z") or singular.endswith("x"):
|
|
101
|
-
return singular[:-1] + "es"
|
|
102
|
-
|
|
103
|
-
if singular.endswith("fe"):
|
|
104
|
-
return singular[:-2] + "ves"
|
|
105
|
-
|
|
106
|
-
if singular.endswith("f"):
|
|
107
|
-
return singular[:-1] + "ves"
|
|
108
|
-
|
|
109
|
-
return singular + "s"
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
@dataclass
|
|
113
|
-
class RestRequest:
|
|
114
|
-
resource: RestfulName
|
|
115
|
-
http: Http
|
|
116
|
-
|
|
117
|
-
method: HttpMethod = field(default=HttpMethod.delete)
|
|
118
|
-
|
|
119
|
-
_endpoint: str = field(init=False, default_factory=str)
|
|
120
|
-
|
|
121
|
-
@property
|
|
122
|
-
def endpoint(self) -> str:
|
|
123
|
-
return self.resource + self._endpoint
|
|
124
|
-
|
|
125
|
-
def with_id(self, value: Any) -> Self:
|
|
126
|
-
self._endpoint = str(value)
|
|
127
|
-
|
|
128
|
-
return self
|
|
129
|
-
|
|
130
|
-
def and_data(self, value: JsonDict) -> Self:
|
|
131
|
-
return self.with_data(value)
|
|
132
|
-
|
|
133
|
-
def from_data(self, value: JsonDict) -> Self:
|
|
134
|
-
return self.with_data(value)
|
|
135
|
-
|
|
136
|
-
def with_data(self, value: JsonDict) -> Self:
|
|
137
|
-
self.http = self.http.with_json(value)
|
|
138
|
-
|
|
139
|
-
return self
|
|
140
|
-
|
|
141
|
-
def and_param(self, name: str, value: Any) -> Self:
|
|
142
|
-
return self.with_param(name, value)
|
|
143
|
-
|
|
144
|
-
def with_param(self, name: str, value: Any) -> Self:
|
|
145
|
-
self.http = self.http.with_param(name, str(value))
|
|
146
|
-
|
|
147
|
-
return self
|
|
148
|
-
|
|
149
|
-
@cached_property
|
|
150
|
-
def response(self) -> HttpResponse: # pragma: no cover
|
|
151
|
-
return self.http.request(method=self.method, endpoint=self.endpoint)
|
|
152
|
-
|
|
153
|
-
def unpack(self) -> JsonDict:
|
|
154
|
-
return JsonDict(self.response.json()["data"][self.resource.singular])
|
|
155
|
-
|
|
156
|
-
def unpack_many(self) -> Iterable[JsonDict]:
|
|
157
|
-
items = self.response.json()["data"][self.resource.plural]
|
|
158
|
-
|
|
159
|
-
return [JsonDict(item) for item in items]
|
|
160
|
-
|
|
161
|
-
def ensure(self) -> RestResponse:
|
|
162
|
-
return RestResponse(
|
|
163
|
-
resource=self.resource,
|
|
164
|
-
json=JsonDict(self.response.json()),
|
|
165
|
-
http_code=self.response.code(),
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
@dataclass
|
|
170
|
-
class CreateMany(RestRequest):
|
|
171
|
-
data: list[JsonDict] = field(default_factory=list)
|
|
172
|
-
|
|
173
|
-
@cached_property
|
|
174
|
-
def response(self) -> HttpResponse:
|
|
175
|
-
if self.data:
|
|
176
|
-
return self.from_collection(self.data).http.request(
|
|
177
|
-
method=HttpMethod.post,
|
|
178
|
-
endpoint=self.resource + "batch",
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
return self.http.request(
|
|
182
|
-
method=HttpMethod.post,
|
|
183
|
-
endpoint=self.resource + "batch",
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
def from_collection(self, value: list[JsonDict]) -> Self:
|
|
187
|
-
return self.with_data(
|
|
188
|
-
JsonDict({self.resource.plural: [dict(item) for item in value]})
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
@deprecated(
|
|
192
|
-
"""
|
|
193
|
-
.from_data is deprecated, use .from_collection instead
|
|
194
|
-
"""
|
|
195
|
-
)
|
|
196
|
-
def from_data(self, value: JsonDict) -> Self:
|
|
197
|
-
self.data.append(value)
|
|
198
|
-
|
|
199
|
-
return self
|
|
200
|
-
|
|
201
|
-
@deprecated(
|
|
202
|
-
"""
|
|
203
|
-
.and_data is deprecated, use .from_collection instead
|
|
204
|
-
"""
|
|
205
|
-
)
|
|
206
|
-
def and_data(self, value: JsonDict) -> Self:
|
|
207
|
-
return self.from_data(value)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
@dataclass
|
|
211
|
-
class UpdateMany(RestRequest):
|
|
212
|
-
data: list[JsonDict] = field(default_factory=list)
|
|
213
|
-
|
|
214
|
-
@cached_property
|
|
215
|
-
def response(self) -> HttpResponse:
|
|
216
|
-
if self.data:
|
|
217
|
-
return self.from_collection(self.data).http.request(
|
|
218
|
-
method=HttpMethod.patch,
|
|
219
|
-
endpoint=self.resource + "",
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
return self.http.request(
|
|
223
|
-
method=HttpMethod.patch,
|
|
224
|
-
endpoint=self.resource + "",
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
def from_collection(self, value: list[JsonDict]) -> Self:
|
|
228
|
-
return self.with_data(
|
|
229
|
-
JsonDict({self.resource.plural: [dict(item) for item in value]})
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
@deprecated(
|
|
233
|
-
"""
|
|
234
|
-
.from_data is deprecated, use .from_collection instead
|
|
235
|
-
"""
|
|
236
|
-
)
|
|
237
|
-
def from_data(self, value: JsonDict) -> Self:
|
|
238
|
-
self.data.append(value)
|
|
239
|
-
|
|
240
|
-
return self
|
|
241
|
-
|
|
242
|
-
@deprecated(
|
|
243
|
-
"""
|
|
244
|
-
.and_data is deprecated, use .from_collection instead
|
|
245
|
-
"""
|
|
246
|
-
)
|
|
247
|
-
def and_data(self, value: JsonDict) -> Self:
|
|
248
|
-
return self.from_data(value)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
@dataclass
|
|
252
|
-
class ReplaceMany(RestRequest):
|
|
253
|
-
data: list[JsonDict] = field(default_factory=list)
|
|
254
|
-
|
|
255
|
-
@cached_property
|
|
256
|
-
def response(self) -> HttpResponse:
|
|
257
|
-
if self.data:
|
|
258
|
-
return self.from_collection(self.data).http.request(
|
|
259
|
-
method=HttpMethod.put,
|
|
260
|
-
endpoint=self.resource + "batch",
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
return self.http.request(
|
|
264
|
-
method=HttpMethod.put,
|
|
265
|
-
endpoint=self.resource + "batch",
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
def from_collection(self, value: list[JsonDict]) -> Self:
|
|
269
|
-
return self.with_data(
|
|
270
|
-
JsonDict({self.resource.plural: [dict(item) for item in value]})
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
@deprecated(
|
|
274
|
-
"""
|
|
275
|
-
.from_data is deprecated, use .from_collection instead
|
|
276
|
-
"""
|
|
277
|
-
)
|
|
278
|
-
def from_data(self, value: JsonDict) -> Self:
|
|
279
|
-
self.data.append(value)
|
|
280
|
-
|
|
281
|
-
return self
|
|
282
|
-
|
|
283
|
-
@deprecated(
|
|
284
|
-
"""
|
|
285
|
-
.and_data is deprecated, use .from_collection instead
|
|
286
|
-
"""
|
|
287
|
-
)
|
|
288
|
-
def and_data(self, value: JsonDict) -> Self:
|
|
289
|
-
return self.from_data(value)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
@dataclass
|
|
293
|
-
class RestResponse:
|
|
294
|
-
resource: RestfulName
|
|
295
|
-
json: JsonDict
|
|
296
|
-
http_code: int
|
|
297
|
-
|
|
298
|
-
def fail(self) -> Self:
|
|
299
|
-
if self.http_code == 422:
|
|
300
|
-
return self
|
|
301
|
-
|
|
302
|
-
return self.with_status("fail")
|
|
303
|
-
|
|
304
|
-
def success(self) -> Self:
|
|
305
|
-
return self.with_status("success")
|
|
306
|
-
|
|
307
|
-
def with_status(self, value: str) -> Self:
|
|
308
|
-
assert self.json.value_of("status").to(str) == value
|
|
309
|
-
|
|
310
|
-
return self
|
|
311
|
-
|
|
312
|
-
def with_code(self, value: int) -> Self:
|
|
313
|
-
assert self.http_code == value
|
|
314
|
-
|
|
315
|
-
if self.http_code != 422:
|
|
316
|
-
assert self.json.value_of("code").to(int) == value
|
|
317
|
-
|
|
318
|
-
return self
|
|
319
|
-
|
|
320
|
-
def message(self, value: str) -> Self:
|
|
321
|
-
return self.with_message(value)
|
|
322
|
-
|
|
323
|
-
def and_message(self, value: str) -> Self:
|
|
324
|
-
return self.with_message(value)
|
|
325
|
-
|
|
326
|
-
def with_message(self, value: str) -> Self:
|
|
327
|
-
assert self.json.value_of("error").to(dict) == {"message": value}, self.json
|
|
328
|
-
|
|
329
|
-
return self
|
|
330
|
-
|
|
331
|
-
def and_item(self, value: Any) -> Self:
|
|
332
|
-
return self.with_item(value)
|
|
333
|
-
|
|
334
|
-
def with_item(self, value: Any) -> Self:
|
|
335
|
-
return self.with_data(
|
|
336
|
-
**{
|
|
337
|
-
self.resource.singular: dict(value)
|
|
338
|
-
if isinstance(value, JsonDict)
|
|
339
|
-
else value
|
|
340
|
-
}
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
def and_collection(self, value: list[Any]) -> Self:
|
|
344
|
-
return self.with_collection(value)
|
|
345
|
-
|
|
346
|
-
def with_collection(self, values: list[Any]) -> Self:
|
|
347
|
-
return self.with_data(
|
|
348
|
-
**{
|
|
349
|
-
self.resource.plural: [
|
|
350
|
-
dict(value) if isinstance(value, JsonDict) else value
|
|
351
|
-
for value in values
|
|
352
|
-
],
|
|
353
|
-
},
|
|
354
|
-
count=len(values),
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
def and_no_data(self) -> Self:
|
|
358
|
-
return self.no_data()
|
|
359
|
-
|
|
360
|
-
def no_data(self) -> Self:
|
|
361
|
-
return self.with_data()
|
|
362
|
-
|
|
363
|
-
def with_data(self, **kwargs: Any) -> Self:
|
|
364
|
-
assert self.json.value_of("data").to(dict) == {**kwargs}, self.json
|
|
365
|
-
|
|
366
|
-
return self
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|