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
apexdevkit/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from typing import Any, Callable, TypeVar, cast
|
|
3
|
+
from warnings import warn
|
|
4
|
+
|
|
5
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def deprecated(warning: str) -> Callable[[F], F]:
|
|
9
|
+
"""Marks a function, method, or class as deprecated.
|
|
10
|
+
|
|
11
|
+
Do not use this annotation inside dataclass annotation.
|
|
12
|
+
Use it in this order instead:
|
|
13
|
+
|
|
14
|
+
@deprecated("Replace this class with NewClass")
|
|
15
|
+
@dataclass\n
|
|
16
|
+
class OldClass:
|
|
17
|
+
|
|
18
|
+
Do not use this annotation around generic classes. For
|
|
19
|
+
example:
|
|
20
|
+
|
|
21
|
+
class GenericClass(Generic[T])"""
|
|
22
|
+
|
|
23
|
+
def decorator(obj: F) -> F:
|
|
24
|
+
if inspect.isfunction(obj) or inspect.ismethod(obj):
|
|
25
|
+
return cast(F, _wrap_function(obj, warning))
|
|
26
|
+
elif inspect.isclass(obj):
|
|
27
|
+
return cast(F, _deprecate_class(obj, warning))
|
|
28
|
+
else:
|
|
29
|
+
raise TypeError(f"Unsupported type for deprecation: {type(obj)}")
|
|
30
|
+
|
|
31
|
+
return decorator
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _wrap_function(func: F, warning: str) -> F:
|
|
35
|
+
def wrapper(*args: Any, **kwargs: dict[str, Any]) -> Any:
|
|
36
|
+
warn(warning, category=DeprecationWarning, stacklevel=2)
|
|
37
|
+
return func(*args, **kwargs)
|
|
38
|
+
|
|
39
|
+
return cast(F, wrapper)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _deprecate_class(cls: type, warning: str) -> type:
|
|
43
|
+
class DeprecatedClass(cls): # type: ignore
|
|
44
|
+
def __init__(self, *args: Any, **kwargs: dict[str, Any]) -> None:
|
|
45
|
+
warn(warning, category=DeprecationWarning, stacklevel=2)
|
|
46
|
+
super().__init__(*args, **kwargs)
|
|
47
|
+
|
|
48
|
+
return cast(type, DeprecatedClass)
|
apexdevkit/error.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Callable, Self
|
|
5
|
+
|
|
6
|
+
Criteria = Callable[[Any], str]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UnknownItem:
|
|
10
|
+
@property
|
|
11
|
+
def id(self) -> str:
|
|
12
|
+
return "unknown"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ExistsError(Exception):
|
|
17
|
+
item: Any = field(default_factory=UnknownItem)
|
|
18
|
+
|
|
19
|
+
_duplicates: list[Criteria] = field(init=False, default_factory=list)
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def id(self) -> Any:
|
|
23
|
+
return self.item.id
|
|
24
|
+
|
|
25
|
+
def with_duplicate(self, criteria: Criteria) -> Self:
|
|
26
|
+
self._duplicates.append(criteria)
|
|
27
|
+
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
def __str__(self) -> str:
|
|
31
|
+
return ",".join([f"{criteria(self.item)}" for criteria in self._duplicates])
|
|
32
|
+
|
|
33
|
+
def fire(self) -> None:
|
|
34
|
+
if self._duplicates:
|
|
35
|
+
raise self
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class DoesNotExistError(Exception):
|
|
40
|
+
id: Any = "unknown"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from apexdevkit.fastapi.builder import FastApiBuilder
|
|
2
|
+
from apexdevkit.fastapi.dependable import inject
|
|
3
|
+
from apexdevkit.fastapi.docs import NoData, Response
|
|
4
|
+
from apexdevkit.fastapi.response import (
|
|
5
|
+
BadRequest,
|
|
6
|
+
ErrorResponse,
|
|
7
|
+
ResourceCreated,
|
|
8
|
+
ResourceExists,
|
|
9
|
+
ResourceFound,
|
|
10
|
+
ResourceNotFound,
|
|
11
|
+
SuccessResponse,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"FastApiBuilder",
|
|
16
|
+
"inject",
|
|
17
|
+
"NoData",
|
|
18
|
+
"Response",
|
|
19
|
+
"SuccessResponse",
|
|
20
|
+
"ResourceCreated",
|
|
21
|
+
"ResourceFound",
|
|
22
|
+
"ResourceExists",
|
|
23
|
+
"ResourceNotFound",
|
|
24
|
+
"ErrorResponse",
|
|
25
|
+
"BadRequest",
|
|
26
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, Self
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, FastAPI
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class FastApiBuilder:
|
|
9
|
+
app: FastAPI = field(default_factory=FastAPI)
|
|
10
|
+
|
|
11
|
+
def build(self) -> FastAPI:
|
|
12
|
+
return self.app
|
|
13
|
+
|
|
14
|
+
def with_title(self, value: str) -> Self:
|
|
15
|
+
self.app.title = value
|
|
16
|
+
|
|
17
|
+
return self
|
|
18
|
+
|
|
19
|
+
def with_description(self, value: str) -> Self:
|
|
20
|
+
self.app.description = value
|
|
21
|
+
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def with_version(self, value: str) -> Self:
|
|
25
|
+
self.app.version = value
|
|
26
|
+
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
def with_dependency(self, **values: Any) -> Self:
|
|
30
|
+
for key, value in values.items():
|
|
31
|
+
setattr(self.app.state, key, value)
|
|
32
|
+
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
def with_route(self, **values: APIRouter) -> Self:
|
|
36
|
+
for key, value in values.items():
|
|
37
|
+
self.app.include_router(value, prefix=f"/{key}", tags=[key.title()])
|
|
38
|
+
|
|
39
|
+
return self
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from fastapi import status
|
|
6
|
+
from fastapi.responses import JSONResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SuccessResponse(dict[str, Any]):
|
|
10
|
+
def __init__(self, status_code: int, **kwargs: Any):
|
|
11
|
+
super().__init__(
|
|
12
|
+
{
|
|
13
|
+
"status": "success",
|
|
14
|
+
"code": status_code,
|
|
15
|
+
"data": {**kwargs},
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ResourceFound(SuccessResponse):
|
|
21
|
+
def __init__(self, **kwargs: Any):
|
|
22
|
+
super().__init__(status_code=status.HTTP_200_OK, **kwargs)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ResourceCreated(SuccessResponse):
|
|
26
|
+
def __init__(self, **kwargs: Any):
|
|
27
|
+
super().__init__(status_code=status.HTTP_201_CREATED, **kwargs)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ErrorResponse(JSONResponse):
|
|
31
|
+
def __init__(self, status_code: int, message: str, **kwargs: Any):
|
|
32
|
+
content = {
|
|
33
|
+
"status": "fail",
|
|
34
|
+
"code": status_code,
|
|
35
|
+
"error": {"message": message},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if kwargs:
|
|
39
|
+
content["data"] = {**kwargs}
|
|
40
|
+
|
|
41
|
+
super().__init__(status_code=status_code, content=content)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BadRequest(ErrorResponse):
|
|
45
|
+
def __init__(self, message: str, **kwargs: Any) -> None:
|
|
46
|
+
super().__init__(
|
|
47
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
48
|
+
message=message,
|
|
49
|
+
**kwargs,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ResourceNotFound(ErrorResponse):
|
|
54
|
+
def __init__(self, message: str):
|
|
55
|
+
super().__init__(status_code=status.HTTP_404_NOT_FOUND, message=message)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ResourceExists(ErrorResponse):
|
|
59
|
+
def __init__(self, message: str, **kwargs: Any):
|
|
60
|
+
super().__init__(
|
|
61
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
62
|
+
message=message,
|
|
63
|
+
**kwargs,
|
|
64
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .fake import FakeHttp
|
|
2
|
+
from .fluent import FluentHttp, FluentHttpRequest, FluentHttpResponse, Http, HttpMethod
|
|
3
|
+
from .httpx import Httpx, HttpxConfig
|
|
4
|
+
from .json import JsonDict
|
|
5
|
+
from .url import HttpUrl
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"FakeHttp",
|
|
9
|
+
"FluentHttp",
|
|
10
|
+
"FluentHttpRequest",
|
|
11
|
+
"FluentHttpResponse",
|
|
12
|
+
"Http",
|
|
13
|
+
"HttpMethod",
|
|
14
|
+
"Httpx",
|
|
15
|
+
"HttpxConfig",
|
|
16
|
+
"JsonDict",
|
|
17
|
+
"HttpUrl",
|
|
18
|
+
]
|
apexdevkit/http/fake.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Self
|
|
5
|
+
|
|
6
|
+
from apexdevkit.http.fluent import HttpMethod, HttpResponse
|
|
7
|
+
from apexdevkit.http.json import JsonDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class FakeResponse:
|
|
12
|
+
content: Any = field(default_factory=dict)
|
|
13
|
+
status_code: int = 200
|
|
14
|
+
|
|
15
|
+
def raw(self) -> Any:
|
|
16
|
+
return self.content
|
|
17
|
+
|
|
18
|
+
def code(self) -> int:
|
|
19
|
+
return self.status_code
|
|
20
|
+
|
|
21
|
+
def json(self) -> Any:
|
|
22
|
+
return JsonDict(self.content)
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def bad_request(cls) -> FakeResponse:
|
|
26
|
+
return FakeResponse(status_code=400)
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def conflict(cls) -> FakeResponse:
|
|
30
|
+
return FakeResponse(status_code=409)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def fail(cls) -> FakeResponse:
|
|
34
|
+
return FakeResponse(status_code=500)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def not_found(cls) -> FakeResponse:
|
|
38
|
+
return FakeResponse(status_code=404)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class FakeHttp:
|
|
43
|
+
response: FakeResponse = field(default_factory=FakeResponse)
|
|
44
|
+
|
|
45
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
46
|
+
params: dict[str, str] = field(default_factory=dict)
|
|
47
|
+
json: JsonDict = field(default_factory=JsonDict)
|
|
48
|
+
|
|
49
|
+
_request: InterceptedRequest = field(init=False)
|
|
50
|
+
|
|
51
|
+
def with_header(self, key: str, value: str) -> Self:
|
|
52
|
+
self.headers[key] = value
|
|
53
|
+
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
def with_param(self, key: str, value: str) -> Self:
|
|
57
|
+
self.params[key] = value
|
|
58
|
+
|
|
59
|
+
return self
|
|
60
|
+
|
|
61
|
+
def with_json(self, value: JsonDict) -> Self:
|
|
62
|
+
self.json = value
|
|
63
|
+
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
def request(self, method: HttpMethod, endpoint: str) -> HttpResponse:
|
|
67
|
+
self._request = InterceptedRequest(method, endpoint)
|
|
68
|
+
|
|
69
|
+
return self.response
|
|
70
|
+
|
|
71
|
+
def intercepted(self, method: HttpMethod) -> InterceptedRequest:
|
|
72
|
+
assert self._request.method == method
|
|
73
|
+
|
|
74
|
+
return self._request
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class InterceptedRequest:
|
|
79
|
+
method: HttpMethod
|
|
80
|
+
endpoint: str
|
|
81
|
+
|
|
82
|
+
def on_endpoint(self, value: str) -> None:
|
|
83
|
+
assert self.endpoint == value
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum, auto
|
|
5
|
+
from typing import Any, Protocol, Type
|
|
6
|
+
|
|
7
|
+
from apexdevkit.http.json import JsonDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Http(Protocol): # pragma: no cover
|
|
11
|
+
def with_header(self, key: str, value: str) -> Http:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
def with_param(self, key: str, value: str) -> Http:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
def with_json(self, value: JsonDict) -> Http:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
def request(self, method: HttpMethod, endpoint: str) -> HttpResponse:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HttpMethod(Enum):
|
|
25
|
+
post = auto()
|
|
26
|
+
get = auto()
|
|
27
|
+
patch = auto()
|
|
28
|
+
delete = auto()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class HttpResponse(Protocol): # pragma: no cover
|
|
32
|
+
def code(self) -> int:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def raw(self) -> Any:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def json(self) -> JsonDict:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class FluentHttp:
|
|
44
|
+
http: Http
|
|
45
|
+
|
|
46
|
+
def and_header(self, key: str, value: str) -> FluentHttp:
|
|
47
|
+
return self.with_header(key, value)
|
|
48
|
+
|
|
49
|
+
def with_header(self, key: str, value: str) -> FluentHttp:
|
|
50
|
+
return FluentHttp(self.http.with_header(key, value))
|
|
51
|
+
|
|
52
|
+
def and_param(self, key: str, value: str) -> FluentHttp:
|
|
53
|
+
return self.with_param(key, value)
|
|
54
|
+
|
|
55
|
+
def with_param(self, key: str, value: str) -> FluentHttp:
|
|
56
|
+
return FluentHttp(self.http.with_param(key, value))
|
|
57
|
+
|
|
58
|
+
def and_json(self, value: JsonDict) -> FluentHttp:
|
|
59
|
+
return self.with_json(value)
|
|
60
|
+
|
|
61
|
+
def with_json(self, value: JsonDict) -> FluentHttp:
|
|
62
|
+
return FluentHttp(self.http.with_json(value))
|
|
63
|
+
|
|
64
|
+
def post(self) -> FluentHttpRequest:
|
|
65
|
+
return FluentHttpRequest(HttpMethod.post, self.http)
|
|
66
|
+
|
|
67
|
+
def get(self) -> FluentHttpRequest:
|
|
68
|
+
return FluentHttpRequest(HttpMethod.get, self.http)
|
|
69
|
+
|
|
70
|
+
def patch(self) -> FluentHttpRequest:
|
|
71
|
+
return FluentHttpRequest(HttpMethod.patch, self.http)
|
|
72
|
+
|
|
73
|
+
def delete(self) -> FluentHttpRequest:
|
|
74
|
+
return FluentHttpRequest(HttpMethod.delete, self.http)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(frozen=True)
|
|
78
|
+
class FluentHttpRequest:
|
|
79
|
+
method: HttpMethod
|
|
80
|
+
http: Http
|
|
81
|
+
|
|
82
|
+
def on_endpoint(self, value: str) -> FluentHttpResponse:
|
|
83
|
+
return FluentHttpResponse(self.http.request(self.method, value))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class FluentHttpResponse:
|
|
88
|
+
response: HttpResponse
|
|
89
|
+
|
|
90
|
+
def on_bad_request(self, raises: Exception | Type[Exception]) -> FluentHttpResponse:
|
|
91
|
+
if self.response.code() == 400:
|
|
92
|
+
raise raises
|
|
93
|
+
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
def on_conflict(self, raises: Exception | Type[Exception]) -> FluentHttpResponse:
|
|
97
|
+
if self.response.code() == 409:
|
|
98
|
+
raise raises
|
|
99
|
+
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
def on_not_found(self, raises: Exception | Type[Exception]) -> FluentHttpResponse:
|
|
103
|
+
if self.response.code() == 404:
|
|
104
|
+
raise raises
|
|
105
|
+
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
def on_failure(self, raises: Type[Exception]) -> FluentHttpResponse:
|
|
109
|
+
if self.response.code() < 200 or self.response.code() > 299:
|
|
110
|
+
raise raises(self.response.raw(), self.response.code())
|
|
111
|
+
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def json(self) -> JsonDict:
|
|
115
|
+
return self.response.json()
|
apexdevkit/http/httpx.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Iterator, Mapping, Self
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from apexdevkit.http.fluent import HttpMethod, HttpResponse
|
|
9
|
+
from apexdevkit.http.json import JsonDict
|
|
10
|
+
from apexdevkit.http.url import HttpUrl
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class Httpx:
|
|
15
|
+
url: HttpUrl
|
|
16
|
+
config: HttpxConfig
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def create_for(cls, url: str) -> Self:
|
|
20
|
+
return cls(HttpUrl(url), HttpxConfig())
|
|
21
|
+
|
|
22
|
+
def with_header(self, key: str, value: str) -> Httpx:
|
|
23
|
+
return Httpx(self.url, self.config.with_header(key, value))
|
|
24
|
+
|
|
25
|
+
def with_param(self, key: str, value: str) -> Httpx:
|
|
26
|
+
return Httpx(self.url, self.config.with_param(key, value))
|
|
27
|
+
|
|
28
|
+
def with_json(self, value: JsonDict) -> Httpx:
|
|
29
|
+
return Httpx(self.url, self.config.with_json(value))
|
|
30
|
+
|
|
31
|
+
def request(self, method: HttpMethod, endpoint: str) -> HttpResponse:
|
|
32
|
+
return _HttpxResponse(
|
|
33
|
+
httpx.request(method.name, self.url + endpoint, **self.config)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class _HttpxResponse:
|
|
39
|
+
inner: httpx.Response
|
|
40
|
+
|
|
41
|
+
def code(self) -> int:
|
|
42
|
+
return self.inner.status_code
|
|
43
|
+
|
|
44
|
+
def raw(self) -> bytes:
|
|
45
|
+
return self.inner.content
|
|
46
|
+
|
|
47
|
+
def json(self) -> JsonDict:
|
|
48
|
+
return JsonDict(self.inner.json())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class HttpxConfig(Mapping[str, Any]):
|
|
53
|
+
timeout_s: int = 30
|
|
54
|
+
headers: JsonDict = field(default_factory=JsonDict)
|
|
55
|
+
params: JsonDict = field(default_factory=JsonDict)
|
|
56
|
+
json: JsonDict = field(default_factory=JsonDict)
|
|
57
|
+
|
|
58
|
+
def with_header(self, key: str, value: str) -> HttpxConfig:
|
|
59
|
+
return HttpxConfig(
|
|
60
|
+
timeout_s=self.timeout_s,
|
|
61
|
+
headers=self.headers.merge(JsonDict({key: value})),
|
|
62
|
+
params=self.params,
|
|
63
|
+
json=self.json,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def with_param(self, key: str, value: str) -> HttpxConfig:
|
|
67
|
+
return HttpxConfig(
|
|
68
|
+
timeout_s=self.timeout_s,
|
|
69
|
+
headers=self.headers,
|
|
70
|
+
params=self.params.merge(JsonDict({key: value})),
|
|
71
|
+
json=self.json,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def with_json(self, value: JsonDict) -> HttpxConfig:
|
|
75
|
+
return HttpxConfig(
|
|
76
|
+
timeout_s=self.timeout_s,
|
|
77
|
+
headers=self.headers,
|
|
78
|
+
params=self.params,
|
|
79
|
+
json=value,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def as_dict(self) -> dict[str, Any]:
|
|
83
|
+
return {
|
|
84
|
+
"timeout": self.timeout_s,
|
|
85
|
+
"headers": dict(self.headers),
|
|
86
|
+
"params": dict(self.params),
|
|
87
|
+
"json": dict(self.json),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
def __len__(self) -> int:
|
|
91
|
+
return len(self.as_dict())
|
|
92
|
+
|
|
93
|
+
def __iter__(self) -> Iterator[str]:
|
|
94
|
+
return iter(self.as_dict())
|
|
95
|
+
|
|
96
|
+
def __getitem__(self, key: str) -> Any:
|
|
97
|
+
return self.as_dict().__getitem__(key)
|
apexdevkit/http/json.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Callable, TypeVar
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class JsonDict(dict[str, Any]):
|
|
8
|
+
def value_of(self, key: str) -> FluentElement:
|
|
9
|
+
return FluentElement(self[key])
|
|
10
|
+
|
|
11
|
+
def and_a(self, **fields: Any) -> JsonDict:
|
|
12
|
+
return self.with_a(**fields)
|
|
13
|
+
|
|
14
|
+
def with_a(self, **fields: Any) -> JsonDict:
|
|
15
|
+
return self.merge(JsonDict(fields))
|
|
16
|
+
|
|
17
|
+
def merge(self, other: JsonDict) -> JsonDict:
|
|
18
|
+
return JsonDict({**self, **other})
|
|
19
|
+
|
|
20
|
+
def drop(self, *keys: str) -> JsonDict:
|
|
21
|
+
return self.select(*set(self.keys()).difference(keys))
|
|
22
|
+
|
|
23
|
+
def select(self, *keys: str) -> JsonDict:
|
|
24
|
+
return JsonDict({k: v for k, v in self.items() if k in keys})
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class FluentElement:
|
|
29
|
+
value: Any
|
|
30
|
+
|
|
31
|
+
def to(self, a_type: Callable[[Any], ConvertedT]) -> ConvertedT:
|
|
32
|
+
return a_type(self.value)
|
|
33
|
+
|
|
34
|
+
def __str__(self) -> str:
|
|
35
|
+
return str(self.value)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
ConvertedT = TypeVar("ConvertedT")
|
apexdevkit/http/url.py
ADDED
apexdevkit/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from apexdevkit.repository.database import (
|
|
2
|
+
Connection,
|
|
3
|
+
Connector,
|
|
4
|
+
Cursor,
|
|
5
|
+
Database,
|
|
6
|
+
DatabaseCommand,
|
|
7
|
+
)
|
|
8
|
+
from apexdevkit.repository.in_memory import InMemoryRepository
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Connection",
|
|
12
|
+
"Connector",
|
|
13
|
+
"Cursor",
|
|
14
|
+
"Database",
|
|
15
|
+
"DatabaseCommand",
|
|
16
|
+
"InMemoryRepository",
|
|
17
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from typing import ContextManager
|
|
5
|
+
|
|
6
|
+
from apexdevkit.repository import Connection
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class SqliteFileConnector:
|
|
11
|
+
dsn: str
|
|
12
|
+
|
|
13
|
+
def connect(self) -> ContextManager[Connection]:
|
|
14
|
+
connection = sqlite3.connect(self.dsn)
|
|
15
|
+
connection.row_factory = sqlite3.Row
|
|
16
|
+
|
|
17
|
+
return connection
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class SqliteInMemoryConnector:
|
|
22
|
+
dsn: str = ":memory:"
|
|
23
|
+
|
|
24
|
+
def connect(self) -> ContextManager[Connection]:
|
|
25
|
+
return self._connection
|
|
26
|
+
|
|
27
|
+
@cached_property
|
|
28
|
+
def _connection(self) -> ContextManager[Connection]:
|
|
29
|
+
connection = sqlite3.connect(self.dsn, check_same_thread=False)
|
|
30
|
+
connection.row_factory = sqlite3.Row
|
|
31
|
+
|
|
32
|
+
return connection
|