apexdevkit 0.2.1__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.
- apexdevkit/__init__.py +0 -0
- apexdevkit/annotation/__init__.py +3 -0
- apexdevkit/annotation/deprecate.py +48 -0
- apexdevkit/error.py +40 -0
- apexdevkit/fastapi/__init__.py +26 -0
- apexdevkit/fastapi/builder.py +39 -0
- apexdevkit/fastapi/dependable.py +11 -0
- apexdevkit/fastapi/docs.py +15 -0
- apexdevkit/fastapi/response.py +64 -0
- apexdevkit/http/__init__.py +18 -0
- apexdevkit/http/fake.py +83 -0
- apexdevkit/http/fluent.py +115 -0
- apexdevkit/http/httpx.py +97 -0
- apexdevkit/http/json.py +38 -0
- apexdevkit/http/url.py +9 -0
- apexdevkit/py.typed +0 -0
- apexdevkit/repository/__init__.py +17 -0
- apexdevkit/repository/connector.py +32 -0
- apexdevkit/repository/database.py +90 -0
- apexdevkit/repository/in_memory.py +106 -0
- apexdevkit/testing/__init__.py +9 -0
- apexdevkit/testing/database.py +42 -0
- apexdevkit/testing/rest.py +310 -0
- apexdevkit-0.2.1.dist-info/LICENSE +674 -0
- apexdevkit-0.2.1.dist-info/METADATA +15 -0
- apexdevkit-0.2.1.dist-info/RECORD +27 -0
- apexdevkit-0.2.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, ContextManager, Iterable, Protocol, Self
|
|
5
|
+
|
|
6
|
+
_RawData = dict[str, Any]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Database:
|
|
11
|
+
connector: Connector
|
|
12
|
+
|
|
13
|
+
command: DatabaseCommand = field(init=False)
|
|
14
|
+
|
|
15
|
+
def execute(self, command: DatabaseCommand) -> Self:
|
|
16
|
+
self.command = command
|
|
17
|
+
|
|
18
|
+
return self
|
|
19
|
+
|
|
20
|
+
def fetch_none(self) -> None:
|
|
21
|
+
with self.connector.connect() as connection:
|
|
22
|
+
cursor: Cursor = connection.cursor()
|
|
23
|
+
cursor.execute(self.command.value, self.command.payload)
|
|
24
|
+
cursor.close()
|
|
25
|
+
|
|
26
|
+
def fetch_one(self) -> _RawData:
|
|
27
|
+
with self.connector.connect() as connection:
|
|
28
|
+
cursor: Cursor = connection.cursor()
|
|
29
|
+
cursor.execute(self.command.value, self.command.payload)
|
|
30
|
+
raw = cursor.fetchone()
|
|
31
|
+
cursor.close()
|
|
32
|
+
|
|
33
|
+
return dict(raw or {})
|
|
34
|
+
|
|
35
|
+
def fetch_all(self) -> Iterable[_RawData]:
|
|
36
|
+
with self.connector.connect() as connection:
|
|
37
|
+
cursor: Cursor = connection.cursor()
|
|
38
|
+
cursor.execute(self.command.value, self.command.payload)
|
|
39
|
+
raw = cursor.fetchall()
|
|
40
|
+
cursor.close()
|
|
41
|
+
|
|
42
|
+
return list(raw)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Connector(Protocol): # pragma: no cover
|
|
46
|
+
def connect(self) -> ContextManager[Connection]:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Connection(Protocol): # pragma: no cover
|
|
51
|
+
def cursor(self) -> Cursor:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Cursor(Protocol): # pragma: no cover
|
|
56
|
+
def execute(self, *args: Any, **kwargs: Any) -> Any:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
def executemany(self, *args: Any, **kwargs: Any) -> Any:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
def fetchone(self, *args: Any, **kwargs: Any) -> Any:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def fetchall(self, *args: Any, **kwargs: Any) -> Any:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
def close(self) -> None:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class DatabaseCommand:
|
|
74
|
+
value: str
|
|
75
|
+
payload: _RawData | list[_RawData] = field(init=False, default_factory=dict)
|
|
76
|
+
|
|
77
|
+
def with_data(self, value: _RawData | None = None, **fields: Any) -> Self:
|
|
78
|
+
assert isinstance(self.payload, dict)
|
|
79
|
+
self.payload.update(value or {})
|
|
80
|
+
self.payload.update(fields)
|
|
81
|
+
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
def with_collection(self, value: list[_RawData]) -> Self:
|
|
85
|
+
self.payload = value
|
|
86
|
+
|
|
87
|
+
return self
|
|
88
|
+
|
|
89
|
+
def __str__(self) -> str: # pragma: no cover
|
|
90
|
+
return self.value
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Any, Generic, Iterable, Iterator, Protocol, Self, TypeVar
|
|
4
|
+
|
|
5
|
+
from apexdevkit.error import Criteria, DoesNotExistError, ExistsError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _Item(Protocol):
|
|
9
|
+
@property
|
|
10
|
+
def id(self) -> Any:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
ItemT = TypeVar("ItemT", bound=_Item)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _Formatter(Protocol[ItemT]):
|
|
18
|
+
def load(self, raw: dict[str, Any]) -> ItemT:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def dump(self, item: ItemT) -> dict[str, Any]:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class InMemoryRepository(Generic[ItemT]):
|
|
27
|
+
formatter: _Formatter[ItemT]
|
|
28
|
+
items: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
_uniques: list[Criteria] = field(init=False, default_factory=list)
|
|
31
|
+
_search_by: list[str] = field(init=False, default_factory=list)
|
|
32
|
+
|
|
33
|
+
def __post_init__(self) -> None:
|
|
34
|
+
self._search_by = ["id", *self._search_by]
|
|
35
|
+
|
|
36
|
+
def with_searchable(self, attribute: str) -> Self:
|
|
37
|
+
self._search_by.append(attribute)
|
|
38
|
+
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
def with_unique(self, criteria: Criteria) -> Self:
|
|
42
|
+
self._uniques.append(criteria)
|
|
43
|
+
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
def with_seeded(self, *items: ItemT) -> Self:
|
|
47
|
+
for item in items:
|
|
48
|
+
self.create(item)
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def create_many(self, items: list[ItemT]) -> None:
|
|
52
|
+
for item in items:
|
|
53
|
+
self.create(item)
|
|
54
|
+
|
|
55
|
+
def create(self, item: ItemT) -> None:
|
|
56
|
+
self._ensure_does_not_exist(item)
|
|
57
|
+
self.items[str(item.id)] = deepcopy(self.formatter.dump(item))
|
|
58
|
+
|
|
59
|
+
def _ensure_does_not_exist(self, new: ItemT) -> None:
|
|
60
|
+
for existing in self:
|
|
61
|
+
error = ExistsError(existing)
|
|
62
|
+
|
|
63
|
+
for criteria in self._uniques:
|
|
64
|
+
if criteria(new) == criteria(existing):
|
|
65
|
+
error.with_duplicate(criteria)
|
|
66
|
+
|
|
67
|
+
error.fire()
|
|
68
|
+
|
|
69
|
+
assert str(new.id) not in self.items, f"Item with id<{new.id}> already exists"
|
|
70
|
+
|
|
71
|
+
def read(self, item_id: Any) -> ItemT:
|
|
72
|
+
for item in self:
|
|
73
|
+
for attribute in self._search_by:
|
|
74
|
+
if getattr(item, attribute) == item_id:
|
|
75
|
+
return item
|
|
76
|
+
|
|
77
|
+
raise DoesNotExistError(item_id)
|
|
78
|
+
|
|
79
|
+
def update(self, item: ItemT) -> None:
|
|
80
|
+
self.delete(item.id)
|
|
81
|
+
self.create(item)
|
|
82
|
+
|
|
83
|
+
def update_many(self, items: list[ItemT]) -> None:
|
|
84
|
+
for item in items:
|
|
85
|
+
self.update(item)
|
|
86
|
+
|
|
87
|
+
def delete(self, item_id: Any) -> None:
|
|
88
|
+
try:
|
|
89
|
+
del self.items[str(item_id)]
|
|
90
|
+
except KeyError:
|
|
91
|
+
raise DoesNotExistError(item_id)
|
|
92
|
+
|
|
93
|
+
def search(self, **kwargs: Any) -> Iterable[ItemT]:
|
|
94
|
+
items = []
|
|
95
|
+
|
|
96
|
+
for item in self.items.values():
|
|
97
|
+
if kwargs.items() <= item.items():
|
|
98
|
+
items.append(self.formatter.load(item))
|
|
99
|
+
|
|
100
|
+
return items
|
|
101
|
+
|
|
102
|
+
def __iter__(self) -> Iterator[ItemT]:
|
|
103
|
+
yield from [self.formatter.load(deepcopy(raw)) for raw in self.items.values()]
|
|
104
|
+
|
|
105
|
+
def __len__(self) -> int:
|
|
106
|
+
return len(self.items)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from contextlib import nullcontext
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Any, ContextManager, Self
|
|
4
|
+
|
|
5
|
+
from apexdevkit.repository import DatabaseCommand
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class FakeConnector:
|
|
10
|
+
commands: list[tuple[str, Any]] = field(default_factory=list)
|
|
11
|
+
results: list[Any] = field(init=False, default_factory=list)
|
|
12
|
+
|
|
13
|
+
def with_result(self, values: Any) -> Self:
|
|
14
|
+
self.results = [values, *self.results]
|
|
15
|
+
|
|
16
|
+
return self
|
|
17
|
+
|
|
18
|
+
def execute(self, command: str, data: Any) -> None:
|
|
19
|
+
self.commands.append((command, data))
|
|
20
|
+
|
|
21
|
+
def executemany(self, command: str, data_list: list[Any]) -> None:
|
|
22
|
+
self.commands.extend([(command, data) for data in data_list])
|
|
23
|
+
|
|
24
|
+
def fetchone(self) -> dict[str, Any]:
|
|
25
|
+
return self.results.pop() # type: ignore
|
|
26
|
+
|
|
27
|
+
def fetchall(self) -> list[dict[str, Any]]:
|
|
28
|
+
return self.results.pop() # type: ignore
|
|
29
|
+
|
|
30
|
+
def connect(self) -> ContextManager[Self]:
|
|
31
|
+
return nullcontext(self)
|
|
32
|
+
|
|
33
|
+
def cursor(self) -> Self:
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
def close(self) -> None:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def assert_contains(self, command: DatabaseCommand, at_index: int = 0) -> None:
|
|
40
|
+
query, data = self.commands[at_index]
|
|
41
|
+
|
|
42
|
+
assert command == DatabaseCommand(query).with_data(data)
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from functools import cached_property
|
|
6
|
+
from typing import Any, Iterable, Self
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
from warnings import warn
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from fastapi.testclient import TestClient
|
|
12
|
+
|
|
13
|
+
from apexdevkit.http import HttpUrl, JsonDict
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class RestResource:
|
|
18
|
+
http: TestClient
|
|
19
|
+
name: RestfulName
|
|
20
|
+
|
|
21
|
+
def create_one(self) -> CreateOne:
|
|
22
|
+
return CreateOne(self.name, self.http)
|
|
23
|
+
|
|
24
|
+
def read_one(self) -> ReadOne:
|
|
25
|
+
return ReadOne(self.name, self.http)
|
|
26
|
+
|
|
27
|
+
def read_all(self) -> ReadAll:
|
|
28
|
+
return ReadAll(self.name, self.http)
|
|
29
|
+
|
|
30
|
+
def update_one(self) -> UpdateOne:
|
|
31
|
+
return UpdateOne(self.name, self.http)
|
|
32
|
+
|
|
33
|
+
def update_many(self) -> UpdateMany:
|
|
34
|
+
return UpdateMany(self.name, self.http)
|
|
35
|
+
|
|
36
|
+
def create_many(self) -> CreateMany:
|
|
37
|
+
return CreateMany(self.name, self.http)
|
|
38
|
+
|
|
39
|
+
def delete_one(self) -> DeleteOne:
|
|
40
|
+
return DeleteOne(self.name, self.http)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class RestCollection(RestResource):
|
|
45
|
+
def sub_resource(self, name: str) -> RestItem:
|
|
46
|
+
return RestItem(
|
|
47
|
+
TestClient(
|
|
48
|
+
self.http.app,
|
|
49
|
+
base_url=HttpUrl(str(self.http.base_url)) + self.name.plural,
|
|
50
|
+
),
|
|
51
|
+
RestfulName(name),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class RestItem(RestResource):
|
|
57
|
+
def sub_resource(self, name: str) -> RestItem:
|
|
58
|
+
return RestItem(
|
|
59
|
+
TestClient(
|
|
60
|
+
self.http.app,
|
|
61
|
+
base_url=HttpUrl(str(self.http.base_url)) + self.name.singular,
|
|
62
|
+
),
|
|
63
|
+
RestfulName(name),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class RestfulName:
|
|
69
|
+
singular: str
|
|
70
|
+
|
|
71
|
+
plural: str = ""
|
|
72
|
+
|
|
73
|
+
def __post_init__(self) -> None:
|
|
74
|
+
self.plural = self.plural or as_plural(self.singular)
|
|
75
|
+
|
|
76
|
+
def __add__(self, other: str) -> str:
|
|
77
|
+
return HttpUrl(self.plural) + other
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def as_plural(singular: str) -> str:
|
|
81
|
+
if singular.endswith("y"):
|
|
82
|
+
return singular[:-1] + "ies"
|
|
83
|
+
|
|
84
|
+
return singular + "s"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class RestRequest:
|
|
89
|
+
resource: RestfulName
|
|
90
|
+
http: TestClient
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
@cached_property
|
|
94
|
+
def response(self) -> httpx.Response: # pragma: no cover
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
def unpack(self) -> JsonDict:
|
|
98
|
+
return JsonDict(self.response.json()["data"][self.resource.singular])
|
|
99
|
+
|
|
100
|
+
def unpack_many(self) -> Iterable[JsonDict]:
|
|
101
|
+
items = self.response.json()["data"][self.resource.plural]
|
|
102
|
+
|
|
103
|
+
return [JsonDict(item) for item in items]
|
|
104
|
+
|
|
105
|
+
def ensure(self) -> RestResponse:
|
|
106
|
+
return RestResponse(
|
|
107
|
+
resource=self.resource,
|
|
108
|
+
json=JsonDict(self.response.json()),
|
|
109
|
+
http_code=self.response.status_code,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass
|
|
114
|
+
class CreateOne(RestRequest):
|
|
115
|
+
data: JsonDict = field(init=False)
|
|
116
|
+
|
|
117
|
+
def from_data(self, value: JsonDict) -> Self:
|
|
118
|
+
self.data = value
|
|
119
|
+
|
|
120
|
+
return self
|
|
121
|
+
|
|
122
|
+
@cached_property
|
|
123
|
+
def response(self) -> httpx.Response:
|
|
124
|
+
return self.http.post(self.resource + "", json=dict(self.data))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class ReadOne(RestRequest):
|
|
129
|
+
item_id: str | UUID = field(init=False)
|
|
130
|
+
|
|
131
|
+
@cached_property
|
|
132
|
+
def response(self) -> httpx.Response:
|
|
133
|
+
return self.http.get(self.resource + str(self.item_id))
|
|
134
|
+
|
|
135
|
+
def with_id(self, value: str | UUID) -> Self:
|
|
136
|
+
self.item_id = str(value)
|
|
137
|
+
|
|
138
|
+
return self
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class ReadAll(RestRequest):
|
|
143
|
+
params: dict[str, Any] = field(init=False, default_factory=dict)
|
|
144
|
+
|
|
145
|
+
@cached_property
|
|
146
|
+
def response(self) -> httpx.Response:
|
|
147
|
+
return self.http.get(self.resource + "", params=self.params)
|
|
148
|
+
|
|
149
|
+
def with_params(self, **kwargs: Any) -> Self:
|
|
150
|
+
self.params = {**kwargs}
|
|
151
|
+
|
|
152
|
+
return self
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass
|
|
156
|
+
class UpdateOne(RestRequest):
|
|
157
|
+
item_id: str | UUID = field(init=False)
|
|
158
|
+
data: JsonDict = field(init=False)
|
|
159
|
+
|
|
160
|
+
@cached_property
|
|
161
|
+
def response(self) -> httpx.Response:
|
|
162
|
+
return self.http.patch(self.resource + str(self.item_id), json=dict(self.data))
|
|
163
|
+
|
|
164
|
+
def with_id(self, value: str | UUID) -> Self:
|
|
165
|
+
self.item_id = str(value)
|
|
166
|
+
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
def and_data(self, value: JsonDict) -> Self:
|
|
170
|
+
self.data = value
|
|
171
|
+
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@dataclass
|
|
176
|
+
class CreateMany(RestRequest):
|
|
177
|
+
data: list[JsonDict] = field(default_factory=list)
|
|
178
|
+
|
|
179
|
+
@cached_property
|
|
180
|
+
def response(self) -> httpx.Response:
|
|
181
|
+
return self.http.post(
|
|
182
|
+
self.resource + "batch",
|
|
183
|
+
json={self.resource.plural: [dict(data) for data in self.data]},
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def from_data(self, value: JsonDict) -> Self:
|
|
187
|
+
self.data.append(value)
|
|
188
|
+
|
|
189
|
+
return self
|
|
190
|
+
|
|
191
|
+
def and_data(self, value: JsonDict) -> Self:
|
|
192
|
+
return self.from_data(value)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass
|
|
196
|
+
class UpdateMany(RestRequest):
|
|
197
|
+
data: list[JsonDict] = field(default_factory=list)
|
|
198
|
+
|
|
199
|
+
@cached_property
|
|
200
|
+
def response(self) -> httpx.Response:
|
|
201
|
+
return self.http.patch(
|
|
202
|
+
self.resource + "",
|
|
203
|
+
json={self.resource.plural: [dict(data) for data in self.data]},
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def from_data(self, value: JsonDict) -> Self:
|
|
207
|
+
self.data.append(value)
|
|
208
|
+
|
|
209
|
+
return self
|
|
210
|
+
|
|
211
|
+
def and_data(self, value: JsonDict) -> Self:
|
|
212
|
+
return self.from_data(value)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@dataclass
|
|
216
|
+
class DeleteOne(RestRequest):
|
|
217
|
+
item_id: str | UUID = field(init=False)
|
|
218
|
+
|
|
219
|
+
@cached_property
|
|
220
|
+
def response(self) -> httpx.Response:
|
|
221
|
+
return self.http.delete(self.resource + str(self.item_id))
|
|
222
|
+
|
|
223
|
+
def with_id(self, value: str | UUID) -> Self:
|
|
224
|
+
self.item_id = str(value)
|
|
225
|
+
|
|
226
|
+
return self
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass
|
|
230
|
+
class RestResponse:
|
|
231
|
+
resource: RestfulName
|
|
232
|
+
json: JsonDict
|
|
233
|
+
http_code: int
|
|
234
|
+
|
|
235
|
+
def fail(self) -> Self:
|
|
236
|
+
return self.with_status("fail")
|
|
237
|
+
|
|
238
|
+
def success(self) -> Self:
|
|
239
|
+
return self.with_status("success")
|
|
240
|
+
|
|
241
|
+
def with_status(self, value: str) -> Self:
|
|
242
|
+
assert self.json.value_of("status").to(str) == value
|
|
243
|
+
|
|
244
|
+
return self
|
|
245
|
+
|
|
246
|
+
def with_code(self, value: int) -> Self:
|
|
247
|
+
assert self.http_code == value
|
|
248
|
+
assert self.json.value_of("code").to(int) == value
|
|
249
|
+
|
|
250
|
+
return self
|
|
251
|
+
|
|
252
|
+
def message(self, value: str) -> Self:
|
|
253
|
+
return self.with_message(value)
|
|
254
|
+
|
|
255
|
+
def and_message(self, value: str) -> Self:
|
|
256
|
+
return self.with_message(value)
|
|
257
|
+
|
|
258
|
+
def with_message(self, value: str) -> Self:
|
|
259
|
+
assert self.json.value_of("error").to(dict) == {"message": value}
|
|
260
|
+
|
|
261
|
+
return self
|
|
262
|
+
|
|
263
|
+
def and_data(self, *values: JsonDict) -> Self: # pragma: no cover
|
|
264
|
+
warn(
|
|
265
|
+
(
|
|
266
|
+
"The 'and_data' method is deprecated. "
|
|
267
|
+
"Please use 'and_item' or 'and_collection' instead."
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
if len(values) == 1:
|
|
271
|
+
return self.with_item(values[0])
|
|
272
|
+
|
|
273
|
+
return self.with_collection(list(values))
|
|
274
|
+
|
|
275
|
+
def and_item(self, value: Any) -> Self:
|
|
276
|
+
return self.with_item(value)
|
|
277
|
+
|
|
278
|
+
def with_item(self, value: Any) -> Self:
|
|
279
|
+
return self.with_data(
|
|
280
|
+
**{
|
|
281
|
+
self.resource.singular: dict(value)
|
|
282
|
+
if isinstance(value, JsonDict)
|
|
283
|
+
else value
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def and_collection(self, value: list[Any]) -> Self:
|
|
288
|
+
return self.with_collection(value)
|
|
289
|
+
|
|
290
|
+
def with_collection(self, values: list[Any]) -> Self:
|
|
291
|
+
return self.with_data(
|
|
292
|
+
**{
|
|
293
|
+
self.resource.plural: [
|
|
294
|
+
dict(value) if isinstance(value, JsonDict) else value
|
|
295
|
+
for value in values
|
|
296
|
+
],
|
|
297
|
+
},
|
|
298
|
+
count=len(values),
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def and_no_data(self) -> Self:
|
|
302
|
+
return self.no_data()
|
|
303
|
+
|
|
304
|
+
def no_data(self) -> Self:
|
|
305
|
+
return self.with_data()
|
|
306
|
+
|
|
307
|
+
def with_data(self, **kwargs: Any) -> Self:
|
|
308
|
+
assert self.json.value_of("data").to(dict) == {**kwargs}
|
|
309
|
+
|
|
310
|
+
return self
|