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 ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ from apexdevkit.annotation.deprecate import deprecated
2
+
3
+ __all__ = ["deprecated"]
@@ -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,11 @@
1
+ from typing import Any
2
+
3
+ from fastapi import Depends
4
+ from fastapi.requests import Request
5
+
6
+
7
+ def inject(dependency: str) -> Any:
8
+ def get(request: Request) -> Any:
9
+ return getattr(request.app.state, dependency)
10
+
11
+ return Depends(get)
@@ -0,0 +1,15 @@
1
+ from typing import Generic, TypeVar
2
+
3
+ from pydantic import BaseModel
4
+
5
+ PayloadT = TypeVar("PayloadT")
6
+
7
+
8
+ class Response(BaseModel, Generic[PayloadT]):
9
+ status: str
10
+ code: int
11
+ data: PayloadT
12
+
13
+
14
+ class NoData(BaseModel):
15
+ pass
@@ -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
+ ]
@@ -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()
@@ -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)
@@ -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
@@ -0,0 +1,9 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(frozen=True)
5
+ class HttpUrl:
6
+ value: str
7
+
8
+ def __add__(self, endpoint: str) -> str:
9
+ return self.value.strip("/") + "/" + endpoint.strip("/")
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