module-typica 0.2.2__tar.gz → 0.2.4__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.
- {module_typica-0.2.2 → module_typica-0.2.4}/PKG-INFO +6 -6
- {module_typica-0.2.2 → module_typica-0.2.4}/Readme.md +4 -3
- {module_typica-0.2.2 → module_typica-0.2.4}/pyproject.toml +20 -3
- {module_typica-0.2.2 → module_typica-0.2.4}/typica/__init__.py +2 -1
- {module_typica-0.2.2 → module_typica-0.2.4}/typica/connection.py +59 -25
- {module_typica-0.2.2 → module_typica-0.2.4}/typica/metadata.py +30 -8
- module_typica-0.2.4/typica/modules/cclickhouse.py +67 -0
- module_typica-0.2.4/typica/modules/pmongo.py +70 -0
- module_typica-0.2.4/typica/modules/psycopg.py +76 -0
- module_typica-0.2.4/typica/modules/psycopg_alchemy.py +83 -0
- module_typica-0.2.4/typica/modules/redis.py +165 -0
- {module_typica-0.2.2 → module_typica-0.2.4}/typica/response.py +85 -90
- module_typica-0.2.4/typica/schema.py +27 -0
- module_typica-0.2.4/typica/utils/__init__.py +2 -0
- module_typica-0.2.4/typica/utils/config.py +35 -0
- {module_typica-0.2.2 → module_typica-0.2.4}/typica/utils/enums.py +9 -8
- module_typica-0.2.2/typica/utils/__init__.py +0 -1
- {module_typica-0.2.2 → module_typica-0.2.4}/typica/base.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: module-typica
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Standard Pydantic usages & utilities
|
|
5
5
|
Author: Oktapian
|
|
6
|
-
Author-email:
|
|
6
|
+
Author-email: oktapian1998@gmail.com
|
|
7
7
|
Requires-Python: >=3.10,<4.0
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -12,15 +12,15 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
12
12
|
Requires-Dist: deprecated (>=1.2.14,<2.0.0)
|
|
13
13
|
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
|
|
14
14
|
Requires-Dist: pydantic-settings (>=2.6.1,<3.0.0)
|
|
15
|
-
Requires-Dist: tomli (>=2.1.0,<3.0.0)
|
|
16
15
|
Description-Content-Type: text/markdown
|
|
17
16
|
|
|
18
17
|
# Description
|
|
19
|
-
[](https://www.python.org/downloads/release/python-3100/)
|
|
20
|
-
[](https://pydantic.dev)
|
|
21
|
-
|
|
22
18
|
This is a standardized model library that uses the pydantic library, which is often used in work, especially in database connection, parameter, or response in service, and other utilities. This repository provides a standardized model that can be used in various scenarios. It aims to simplify the process of defining and validating models in Python applications.
|
|
23
19
|
|
|
20
|
+
## Usages
|
|
21
|
+
there are several use cases that can be implemented using this library, such as some of the examples below
|
|
22
|
+
|
|
23
|
+
|
|
24
24
|
|
|
25
25
|
## Contributors
|
|
26
26
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# Description
|
|
2
|
-
[](https://www.python.org/downloads/release/python-3100/)
|
|
3
|
-
[](https://pydantic.dev)
|
|
4
|
-
|
|
5
2
|
This is a standardized model library that uses the pydantic library, which is often used in work, especially in database connection, parameter, or response in service, and other utilities. This repository provides a standardized model that can be used in various scenarios. It aims to simplify the process of defining and validating models in Python applications.
|
|
6
3
|
|
|
4
|
+
## Usages
|
|
5
|
+
there are several use cases that can be implemented using this library, such as some of the examples below
|
|
6
|
+
|
|
7
|
+
|
|
7
8
|
|
|
8
9
|
## Contributors
|
|
9
10
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "module-typica"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.4"
|
|
4
4
|
description = "Standard Pydantic usages & utilities"
|
|
5
|
-
authors = ["Oktapian <
|
|
5
|
+
authors = ["Oktapian <oktapian1998@gmail.com>"]
|
|
6
6
|
readme = "Readme.md"
|
|
7
7
|
packages = [{include = "typica"}]
|
|
8
8
|
|
|
@@ -14,11 +14,28 @@ venv = ".venv"
|
|
|
14
14
|
python = "^3.10"
|
|
15
15
|
pydantic = "^2.9.2"
|
|
16
16
|
pydantic-settings = "^2.6.1"
|
|
17
|
-
tomli = "^2.1.0"
|
|
18
17
|
deprecated = "^1.2.14"
|
|
19
18
|
|
|
20
19
|
[tool.poetry.group.dev.dependencies]
|
|
21
20
|
faker = "^25.3.0"
|
|
21
|
+
pytest = "^8.3.3"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
[tool.poetry.group.postgresql.dependencies]
|
|
25
|
+
psycopg2-binary = "^2.9.10"
|
|
26
|
+
sqlalchemy = "^2.0.36"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
[tool.poetry.group.mongo.dependencies]
|
|
30
|
+
pymongo = "^4.10.1"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
[tool.poetry.group.clickhouse.dependencies]
|
|
34
|
+
clickhouse-connect = "^0.8.7"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
[tool.poetry.group.redis.dependencies]
|
|
38
|
+
redis = "^5.2.0"
|
|
22
39
|
|
|
23
40
|
[build-system]
|
|
24
41
|
requires = ["poetry-core"]
|
|
@@ -4,11 +4,15 @@ from typing import Optional
|
|
|
4
4
|
from pydantic import BaseModel, Field, model_validator
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
class EndpointMeta(BaseModel):
|
|
9
8
|
host: Optional[str] = Field("localhost", description="Connection host")
|
|
10
9
|
port: Optional[str | int] = Field(8000, description="Connection port")
|
|
11
10
|
|
|
11
|
+
@property
|
|
12
|
+
def port_int(self) -> int | None:
|
|
13
|
+
if isinstance(self.port, str):
|
|
14
|
+
return int(self.port)
|
|
15
|
+
return self.port
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
class AuthMeta(BaseModel):
|
|
@@ -23,7 +27,6 @@ class URIConnectionMeta(BaseModel):
|
|
|
23
27
|
class DBConnectionMeta(EndpointMeta, AuthMeta, URIConnectionMeta):
|
|
24
28
|
database: Optional[str] = Field(None, description="Database name")
|
|
25
29
|
|
|
26
|
-
|
|
27
30
|
def uri_string(self, base: str = "http", with_db: bool = True) -> str:
|
|
28
31
|
"""
|
|
29
32
|
Return a URI string for the database connection.
|
|
@@ -39,9 +42,26 @@ class DBConnectionMeta(EndpointMeta, AuthMeta, URIConnectionMeta):
|
|
|
39
42
|
return f"{base}://{meta}/{self.database if with_db else ''}"
|
|
40
43
|
return ""
|
|
41
44
|
|
|
42
|
-
|
|
43
45
|
@model_validator(mode="after")
|
|
44
46
|
def extract_uri(self):
|
|
47
|
+
"""
|
|
48
|
+
Extracts and parses the URI to populate the connection metadata fields.
|
|
49
|
+
|
|
50
|
+
This method processes the `uri` attribute to extract authentication and
|
|
51
|
+
connection details such as username, password, host, port, and database.
|
|
52
|
+
It modifies the respective attributes of the instance based on the parsed
|
|
53
|
+
URI components.
|
|
54
|
+
|
|
55
|
+
Steps involved:
|
|
56
|
+
- Strips the scheme from the URI.
|
|
57
|
+
- Splits the URI into metadata and additional query parameters.
|
|
58
|
+
- Extracts database name from query parameters if present.
|
|
59
|
+
- Parses authentication info and host details from the metadata.
|
|
60
|
+
- Converts the port to an integer.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The instance with populated connection metadata fields.
|
|
64
|
+
"""
|
|
45
65
|
if self.uri:
|
|
46
66
|
uri = re.sub(r"\w+:(//|/)", "", self.uri)
|
|
47
67
|
metadata, others = (
|
|
@@ -63,8 +83,11 @@ class DBConnectionMeta(EndpointMeta, AuthMeta, URIConnectionMeta):
|
|
|
63
83
|
self.port = int(self.port)
|
|
64
84
|
return self
|
|
65
85
|
|
|
86
|
+
|
|
66
87
|
class ClusterConnectionMeta(AuthMeta, URIConnectionMeta):
|
|
67
|
-
cluster_uri: Optional[list[EndpointMeta]]
|
|
88
|
+
cluster_uri: Optional[list[EndpointMeta]] = Field(
|
|
89
|
+
[], description="List of clusters endpoint"
|
|
90
|
+
)
|
|
68
91
|
database: Optional[str] = Field(None, description="Database name")
|
|
69
92
|
|
|
70
93
|
def uri_string(self, base: str = "http", with_db: bool = True) -> str:
|
|
@@ -81,38 +104,42 @@ class ClusterConnectionMeta(AuthMeta, URIConnectionMeta):
|
|
|
81
104
|
return f"{base}://{self.username}:{self.password}@{meta}/{self.database if with_db else ''}"
|
|
82
105
|
return f"{base}://{meta}/{self.database if with_db else ''}"
|
|
83
106
|
return ""
|
|
84
|
-
|
|
85
107
|
|
|
86
108
|
@model_validator(mode="after")
|
|
87
109
|
def extract_uri(self):
|
|
110
|
+
"""
|
|
111
|
+
Extract URI from connection string and fill in the respective fields.
|
|
112
|
+
|
|
113
|
+
If the connection string is in the format of mongodb://user:password@host:port/database,
|
|
114
|
+
the respective fields will be filled in. If the connection string is in the format of
|
|
115
|
+
mongodb://host:port,host:port/database, the hosts will be split into a list of
|
|
116
|
+
EndpointMeta objects.
|
|
117
|
+
|
|
118
|
+
:return: The modified ClusterConnectionMeta object.
|
|
119
|
+
:rtype: ClusterConnectionMeta
|
|
120
|
+
"""
|
|
88
121
|
if self.uri:
|
|
89
122
|
uri = re.sub(r"\w+:(//|/)", "", self.uri)
|
|
90
|
-
|
|
123
|
+
clean_meta, others = (
|
|
91
124
|
re.split(r"\/\?|\/", uri) if re.search(r"\/\?|\/", uri) else [uri, None]
|
|
92
125
|
)
|
|
126
|
+
cluster_uri = []
|
|
93
127
|
if others and "&" in others:
|
|
94
128
|
for other in others.split("&"):
|
|
95
129
|
if "=" in other and re.search(r"authSource", other):
|
|
96
130
|
self.database = other.split("=")[-1]
|
|
97
131
|
elif "=" not in other:
|
|
98
132
|
self.database = other
|
|
99
|
-
if "@" in
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
self.username, self.password, self.host, self.port = re.split(
|
|
110
|
-
r"\@|\:", metadata
|
|
111
|
-
)
|
|
112
|
-
else:
|
|
113
|
-
self.host, self.port = re.split(r"\:", metadata)
|
|
114
|
-
if self.port:
|
|
115
|
-
self.port = int(self.port)
|
|
133
|
+
if "@" in clean_meta:
|
|
134
|
+
auth_meta, clean_meta = re.split(r"\@", clean_meta)
|
|
135
|
+
self.username, self.password = re.split(r"\:", auth_meta)
|
|
136
|
+
|
|
137
|
+
for cluster in clean_meta.split(","):
|
|
138
|
+
hostData = re.split(r"\:", cluster)
|
|
139
|
+
cluster_uri.append(
|
|
140
|
+
EndpointMeta(host=hostData[0], port=int(hostData[1]))
|
|
141
|
+
)
|
|
142
|
+
self.cluster_uri = cluster_uri
|
|
116
143
|
return self
|
|
117
144
|
|
|
118
145
|
|
|
@@ -122,7 +149,6 @@ class S3ConnectionMeta(EndpointMeta):
|
|
|
122
149
|
bucket: str = Field(..., description="S3 bucket name")
|
|
123
150
|
base_path: Optional[str] = Field("/", description="S3 base path")
|
|
124
151
|
|
|
125
|
-
|
|
126
152
|
@property
|
|
127
153
|
def json_meta(self) -> dict:
|
|
128
154
|
"""
|
|
@@ -134,4 +160,12 @@ class S3ConnectionMeta(EndpointMeta):
|
|
|
134
160
|
"endpoint_url": f"http://{self.host}:{self.port}",
|
|
135
161
|
"key": self.access_key,
|
|
136
162
|
"secret": self.secret_key,
|
|
137
|
-
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class RedisConnectionMeta(EndpointMeta, AuthMeta):
|
|
167
|
+
database: int = Field(..., description="Database name")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class RMQConnectionMeta(EndpointMeta, AuthMeta):
|
|
171
|
+
vhost: Optional[str] = Field(None, description="Virtual host")
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
1
|
from typing import Optional
|
|
3
2
|
|
|
4
|
-
from pydantic import BaseModel, Field
|
|
3
|
+
from pydantic import BaseModel, RootModel, Field
|
|
5
4
|
|
|
6
5
|
from .utils import MedallionTypes, LocationLevel
|
|
7
6
|
from .connection import DBConnectionMeta, S3ConnectionMeta, ClusterConnectionMeta
|
|
@@ -16,6 +15,30 @@ class SchemaMeta(BaseModel):
|
|
|
16
15
|
field_hide: Optional[bool] = Field(False, description="Hide field")
|
|
17
16
|
|
|
18
17
|
|
|
18
|
+
class SchemaRawMeta(SchemaMeta):
|
|
19
|
+
regional_field: Optional[str] = Field(None, description="Regional field")
|
|
20
|
+
none_percentage: Optional[float] = Field(
|
|
21
|
+
0.0, description="Percentage of null / none values"
|
|
22
|
+
)
|
|
23
|
+
unique_value: Optional[list[str]] = Field(
|
|
24
|
+
None, description="Unique values in field"
|
|
25
|
+
)
|
|
26
|
+
describe_field: Optional[str] = Field(None, description="Describe field")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Schemas(RootModel):
|
|
30
|
+
root: list[SchemaMeta]
|
|
31
|
+
|
|
32
|
+
def __iter__(self): # type: ignore
|
|
33
|
+
return self.root.__iter__()
|
|
34
|
+
|
|
35
|
+
def __getitem__(self, item):
|
|
36
|
+
return self.root[item]
|
|
37
|
+
|
|
38
|
+
def __len__(self):
|
|
39
|
+
return self.root.__len__()
|
|
40
|
+
|
|
41
|
+
|
|
19
42
|
class SimplifieMetadata(BaseModel):
|
|
20
43
|
id: str = Field(..., description="Identifier for the metadata")
|
|
21
44
|
|
|
@@ -23,7 +46,7 @@ class SimplifieMetadata(BaseModel):
|
|
|
23
46
|
title: str = Field(..., description="Title, name, or label for the metadata")
|
|
24
47
|
source: str = Field(..., description="Source of the metadata, e.g. www.example.com")
|
|
25
48
|
country: str = Field(..., description="Country of origin")
|
|
26
|
-
year: str = Field(..., description="Year of the metadata")
|
|
49
|
+
year: str | int = Field(..., description="Year of the metadata")
|
|
27
50
|
range_data: str = Field(..., description="Range of data, eg. 2021-2022")
|
|
28
51
|
description: Optional[str] = Field(None)
|
|
29
52
|
|
|
@@ -32,7 +55,7 @@ class SimplifieMetadata(BaseModel):
|
|
|
32
55
|
sub_category: Optional[str] = Field(None)
|
|
33
56
|
|
|
34
57
|
# ? Schemas
|
|
35
|
-
schemas:
|
|
58
|
+
schemas: Schemas = Field(..., description="Description of all fields in data")
|
|
36
59
|
|
|
37
60
|
# ? Database
|
|
38
61
|
database_access: DBConnectionMeta | ClusterConnectionMeta
|
|
@@ -65,10 +88,9 @@ class FullMetadata(SimplifieMetadata):
|
|
|
65
88
|
|
|
66
89
|
# ? Data lake
|
|
67
90
|
# * Use case: bronze
|
|
68
|
-
lake_access: Optional[
|
|
69
|
-
|
|
70
|
-
)
|
|
91
|
+
lake_access: Optional[
|
|
92
|
+
S3ConnectionMeta | DBConnectionMeta | ClusterConnectionMeta
|
|
93
|
+
] = Field(None, description="Data lake access")
|
|
71
94
|
lake_meta_path: Optional[str] = Field(None, description="Data lake metadata path")
|
|
72
95
|
lake_data_path: Optional[str] = Field(None, description="Data lake data path")
|
|
73
96
|
lake_data_format: Optional[str] = Field(None, description="Data lake data format")
|
|
74
|
-
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from clickhouse_connect import get_client
|
|
2
|
+
from clickhouse_connect.driver.client import Client
|
|
3
|
+
|
|
4
|
+
from typica.connection import DBConnectionMeta
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CHConnector:
|
|
8
|
+
|
|
9
|
+
_meta: DBConnectionMeta
|
|
10
|
+
_client: Client
|
|
11
|
+
|
|
12
|
+
def __init__(self, meta: DBConnectionMeta) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Initialize the ClickHouse connector with the given connection metadata.
|
|
15
|
+
|
|
16
|
+
:param meta: The metadata of the database connection.
|
|
17
|
+
:type meta: DBConnectionMeta
|
|
18
|
+
"""
|
|
19
|
+
self._meta = meta
|
|
20
|
+
|
|
21
|
+
def __enter__(self) -> "CHConnector":
|
|
22
|
+
"""
|
|
23
|
+
Connect to the ClickHouse server and return the connection object.
|
|
24
|
+
|
|
25
|
+
:return: The connection object.
|
|
26
|
+
:rtype: CHConnector
|
|
27
|
+
:raises ValueError: If the connection to the ClickHouse server fails.
|
|
28
|
+
"""
|
|
29
|
+
self.connect()
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Close the connection to the ClickHouse server.
|
|
35
|
+
|
|
36
|
+
This method is called when the context manager exits its scope.
|
|
37
|
+
"""
|
|
38
|
+
self.close()
|
|
39
|
+
|
|
40
|
+
def connect(self) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Establish a connection to the ClickHouse server.
|
|
43
|
+
|
|
44
|
+
:raises ValueError: If the connection to the ClickHouse server fails.
|
|
45
|
+
:raises Exception: If any other error occurs during the connection.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
self._client = get_client(
|
|
49
|
+
host=str(self._meta.host),
|
|
50
|
+
port=int(self._meta.port), # type: ignore
|
|
51
|
+
user=self._meta.username,
|
|
52
|
+
password=self._meta.password
|
|
53
|
+
or "", # cause get_client doesn't support empty password
|
|
54
|
+
database=str(self._meta.database),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
except Exception as e:
|
|
58
|
+
raise e
|
|
59
|
+
|
|
60
|
+
def close(self) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Close the connection to the ClickHouse server.
|
|
63
|
+
|
|
64
|
+
This method is a no-op if the connection is already closed.
|
|
65
|
+
"""
|
|
66
|
+
if self._client:
|
|
67
|
+
self._client.close()
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from pymongo import MongoClient
|
|
2
|
+
from pymongo.database import Database
|
|
3
|
+
from pymongo.errors import NetworkTimeout, ExecutionTimeout
|
|
4
|
+
|
|
5
|
+
from typica.connection import DBConnectionMeta
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MongoConnector:
|
|
9
|
+
|
|
10
|
+
_meta: DBConnectionMeta
|
|
11
|
+
_client: MongoClient
|
|
12
|
+
_db: Database
|
|
13
|
+
|
|
14
|
+
def __init__(self, meta: DBConnectionMeta) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Initialize the Mongo connector with the given connection metadata.
|
|
17
|
+
|
|
18
|
+
:param meta: The metadata of the database connection.
|
|
19
|
+
:type meta: DBConnectionMeta
|
|
20
|
+
"""
|
|
21
|
+
self._meta = meta
|
|
22
|
+
if not self._meta.uri:
|
|
23
|
+
self._meta.uri = self._meta.uri_string(base="mongodb", with_db=False)
|
|
24
|
+
|
|
25
|
+
def __enter__(self):
|
|
26
|
+
"""
|
|
27
|
+
Connect to the MongoDB server and return the connection object.
|
|
28
|
+
|
|
29
|
+
:return: The connection object.
|
|
30
|
+
:rtype: MongoConnector
|
|
31
|
+
:raises ValueError: If the connection to the MongoDB server fails.
|
|
32
|
+
"""
|
|
33
|
+
self.connect()
|
|
34
|
+
if self._client is None:
|
|
35
|
+
raise ValueError("Mongo not connected.")
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Close the connection to the MongoDB server.
|
|
41
|
+
|
|
42
|
+
This method is called when the context manager exits its scope.
|
|
43
|
+
"""
|
|
44
|
+
self.close()
|
|
45
|
+
|
|
46
|
+
def connect(self, **kwargs) -> None:
|
|
47
|
+
"""
|
|
48
|
+
Establish a connection to the MongoDB server.
|
|
49
|
+
|
|
50
|
+
:param kwargs: Additional keyword arguments for MongoClient.
|
|
51
|
+
:raises ValueError: If the connection to the MongoDB server fails.
|
|
52
|
+
:raises Exception: If any other error occurs during the connection.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
self._client = MongoClient(self._meta.uri, timeoutMS=20000, **kwargs)
|
|
57
|
+
self._db = self._client[str(self._meta.database)]
|
|
58
|
+
except (NetworkTimeout, ExecutionTimeout):
|
|
59
|
+
raise ValueError("Mongo connection timed out.")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
raise e
|
|
62
|
+
|
|
63
|
+
def close(self) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Close the connection to the MongoDB server.
|
|
66
|
+
|
|
67
|
+
This method is a no-op if the connection is already closed.
|
|
68
|
+
"""
|
|
69
|
+
if self._client:
|
|
70
|
+
self._client.close()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import psycopg2
|
|
2
|
+
|
|
3
|
+
from psycopg2.extensions import connection, cursor
|
|
4
|
+
from psycopg2.errors import (
|
|
5
|
+
IdleSessionTimeout,
|
|
6
|
+
IdleInTransactionSessionTimeout,
|
|
7
|
+
ConnectionFailure,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from typica.connection import DBConnectionMeta
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PostgreConnector:
|
|
14
|
+
|
|
15
|
+
_meta: DBConnectionMeta
|
|
16
|
+
_conn: connection
|
|
17
|
+
_cursor: cursor
|
|
18
|
+
|
|
19
|
+
def __init__(self, meta: DBConnectionMeta) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Initialize the Postgre connector with the given connection metadata.
|
|
22
|
+
|
|
23
|
+
:param meta: The metadata of the database connection.
|
|
24
|
+
:type meta: DBConnectionMeta
|
|
25
|
+
"""
|
|
26
|
+
self._meta = meta
|
|
27
|
+
|
|
28
|
+
def __enter__(self) -> "PostgreConnector":
|
|
29
|
+
"""
|
|
30
|
+
Connect to the PostgreSQL server and return the connection object.
|
|
31
|
+
|
|
32
|
+
:return: The connection object.
|
|
33
|
+
:rtype: PostgreConnector
|
|
34
|
+
:raises ValueError: If the connection to the PostgreSQL server fails.
|
|
35
|
+
"""
|
|
36
|
+
self.connect()
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
|
|
40
|
+
self.close()
|
|
41
|
+
|
|
42
|
+
def connect(self, **kwargs) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Establish a connection to the PostgreSQL server.
|
|
45
|
+
|
|
46
|
+
:param kwargs: Additional keyword arguments for psycopg2.connect.
|
|
47
|
+
:raises ValueError: If the connection to the PostgreSQL server fails.
|
|
48
|
+
:raises Exception: If any other error occurs during the connection.
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
self._conn = psycopg2.connect(
|
|
52
|
+
dbname=self._meta.database,
|
|
53
|
+
user=self._meta.username,
|
|
54
|
+
password=self._meta.password,
|
|
55
|
+
host=self._meta.host,
|
|
56
|
+
port=self._meta.port,
|
|
57
|
+
**kwargs
|
|
58
|
+
)
|
|
59
|
+
self._cursor = self._conn.cursor()
|
|
60
|
+
except ConnectionFailure:
|
|
61
|
+
raise ValueError("PostgreSQL connection failed.")
|
|
62
|
+
except (IdleInTransactionSessionTimeout, IdleSessionTimeout):
|
|
63
|
+
raise ValueError("Session timed out.")
|
|
64
|
+
except Exception as e:
|
|
65
|
+
raise e
|
|
66
|
+
|
|
67
|
+
def close(self) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Close the connection to the PostgreSQL server.
|
|
70
|
+
|
|
71
|
+
This method is a no-op if the connection is already closed.
|
|
72
|
+
"""
|
|
73
|
+
if self._cursor:
|
|
74
|
+
self._cursor.close()
|
|
75
|
+
if self._conn:
|
|
76
|
+
self._conn.close()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from sqlalchemy import Engine, URL, Connection, create_engine, text, CursorResult
|
|
2
|
+
from sqlalchemy.exc import TimeoutError
|
|
3
|
+
|
|
4
|
+
from typica.connection import DBConnectionMeta
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PostgreConnector:
|
|
8
|
+
|
|
9
|
+
_meta: DBConnectionMeta
|
|
10
|
+
_engine: Engine
|
|
11
|
+
_conn: Connection
|
|
12
|
+
|
|
13
|
+
def __init__(self, meta: DBConnectionMeta) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Initialize the Postgre connector with the given connection metadata.
|
|
16
|
+
|
|
17
|
+
:param meta: The metadata of the database connection.
|
|
18
|
+
:type meta: DBConnectionMeta
|
|
19
|
+
"""
|
|
20
|
+
self._meta = meta
|
|
21
|
+
|
|
22
|
+
def __enter__(self) -> Connection:
|
|
23
|
+
"""
|
|
24
|
+
Connect to the PostgreSQL server and return the connection object.
|
|
25
|
+
|
|
26
|
+
:return: The connection object.
|
|
27
|
+
:rtype: Connection
|
|
28
|
+
:raises ValueError: If the connection to the PostgreSQL server fails.
|
|
29
|
+
"""
|
|
30
|
+
self.connect()
|
|
31
|
+
return self._conn
|
|
32
|
+
|
|
33
|
+
def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Close the connection to the PostgreSQL server.
|
|
36
|
+
|
|
37
|
+
This method is called when the context manager exits its scope.
|
|
38
|
+
"""
|
|
39
|
+
self.close()
|
|
40
|
+
|
|
41
|
+
def execute_text(self, sql: str) -> CursorResult:
|
|
42
|
+
try:
|
|
43
|
+
res = self._conn.execute(text(sql))
|
|
44
|
+
return res
|
|
45
|
+
except Exception as e:
|
|
46
|
+
raise e
|
|
47
|
+
|
|
48
|
+
def connect(self) -> Connection:
|
|
49
|
+
"""
|
|
50
|
+
Establish a connection to the PostgreSQL server.
|
|
51
|
+
|
|
52
|
+
:return: The connection object.
|
|
53
|
+
:rtype: Connection
|
|
54
|
+
:raises ValueError: If the connection to the PostgreSQL server fails.
|
|
55
|
+
:raises Exception: If any other error occurs during the connection.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
self._engine = create_engine(
|
|
59
|
+
URL.create(
|
|
60
|
+
drivername="postgresql",
|
|
61
|
+
username=self._meta.username,
|
|
62
|
+
password=self._meta.password,
|
|
63
|
+
host=self._meta.host,
|
|
64
|
+
port=int(self._meta.port), # type: ignore
|
|
65
|
+
database=self._meta.database,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
self._conn = self._engine.connect()
|
|
69
|
+
|
|
70
|
+
return self._conn
|
|
71
|
+
except TimeoutError:
|
|
72
|
+
raise ValueError("PostgreSQL connection timed out.")
|
|
73
|
+
except Exception as e:
|
|
74
|
+
raise e
|
|
75
|
+
|
|
76
|
+
def close(self) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Close the connection to the PostgreSQL server.
|
|
79
|
+
|
|
80
|
+
This method is a no-op if the connection is already closed.
|
|
81
|
+
"""
|
|
82
|
+
if self._conn:
|
|
83
|
+
self._conn.close()
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from redis import Redis
|
|
2
|
+
from redis.asyncio import Redis as AsyncRedis
|
|
3
|
+
from redis.exceptions import TimeoutError
|
|
4
|
+
|
|
5
|
+
from typica.connection import RedisConnectionMeta
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RedisConnector:
|
|
9
|
+
|
|
10
|
+
_meta: RedisConnectionMeta
|
|
11
|
+
_client: Redis
|
|
12
|
+
|
|
13
|
+
def __init__(self, meta: RedisConnectionMeta) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Initialize the Redis connector with the given connection metadata.
|
|
16
|
+
|
|
17
|
+
:param meta: The metadata of the database connection.
|
|
18
|
+
:type meta: RedisConnectionMeta
|
|
19
|
+
"""
|
|
20
|
+
self._meta = meta
|
|
21
|
+
|
|
22
|
+
def __enter__(self):
|
|
23
|
+
"""
|
|
24
|
+
Connect to the Redis server and return the connection object.
|
|
25
|
+
|
|
26
|
+
:return: The connection object.
|
|
27
|
+
:rtype: RedisConnector
|
|
28
|
+
:raises ValueError: If the connection to the Redis server fails.
|
|
29
|
+
"""
|
|
30
|
+
self.connect()
|
|
31
|
+
if self._client is None:
|
|
32
|
+
raise ValueError("Redis not connected.")
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
def __call__(self, *args, **kwds) -> bool:
|
|
36
|
+
"""
|
|
37
|
+
Check if the connection is already established.
|
|
38
|
+
|
|
39
|
+
:return: True if the connection is available, False otherwise.
|
|
40
|
+
:rtype: bool
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
if self._client is None:
|
|
44
|
+
raise ValueError("Redis not connected.")
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Close the connection to the Redis server.
|
|
50
|
+
|
|
51
|
+
This method is called when the context manager exits its scope.
|
|
52
|
+
"""
|
|
53
|
+
self.close()
|
|
54
|
+
|
|
55
|
+
def connect(self, other_database: int | None = None) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Establish a connection to the Redis server.
|
|
58
|
+
|
|
59
|
+
:param other_database: The ID of an alternative database to connect to.
|
|
60
|
+
If not provided, the default database ID will be used.
|
|
61
|
+
:type other_database: int | None
|
|
62
|
+
:raises ValueError: If the connection to the Redis server fails.
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
|
|
66
|
+
self._client = Redis(
|
|
67
|
+
host=str(self._meta.host),
|
|
68
|
+
port=int(self._meta.port), # type: ignore
|
|
69
|
+
username=self._meta.username,
|
|
70
|
+
password=self._meta.password,
|
|
71
|
+
db=other_database if other_database else self._meta.database,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
except TimeoutError:
|
|
75
|
+
raise ValueError("Redis connection timed out.")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
raise e
|
|
78
|
+
|
|
79
|
+
def close(self) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Close the connection to the Redis server.
|
|
82
|
+
|
|
83
|
+
This method is a no-op if the connection is already closed.
|
|
84
|
+
"""
|
|
85
|
+
if self._client:
|
|
86
|
+
self._client.close()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class AsyncRedisConnector:
|
|
90
|
+
|
|
91
|
+
_meta: RedisConnectionMeta
|
|
92
|
+
_client: AsyncRedis
|
|
93
|
+
|
|
94
|
+
def __init__(self, meta: RedisConnectionMeta) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Initialize the Redis connector with the given connection metadata.
|
|
97
|
+
|
|
98
|
+
:param meta: The metadata of the database connection.
|
|
99
|
+
:type meta: RedisConnectionMeta
|
|
100
|
+
"""
|
|
101
|
+
self._meta = meta
|
|
102
|
+
|
|
103
|
+
async def __enter__(self):
|
|
104
|
+
"""
|
|
105
|
+
Connect to the Redis server and return the connection object.
|
|
106
|
+
|
|
107
|
+
:return: The connection object.
|
|
108
|
+
:rtype: RedisConnector
|
|
109
|
+
:raises ValueError: If the connection to the Redis server fails.
|
|
110
|
+
"""
|
|
111
|
+
await self.connect()
|
|
112
|
+
if self._client is None:
|
|
113
|
+
raise ValueError("Redis not connected.")
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
async def __call__(self, *args, **kwds) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Check if the connection is already established.
|
|
119
|
+
|
|
120
|
+
:return: True if the connection is available, False otherwise.
|
|
121
|
+
:rtype: bool
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
if self._client is None:
|
|
125
|
+
raise ValueError("Redis not connected.")
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
async def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Close the connection to the Redis server.
|
|
131
|
+
|
|
132
|
+
This method is called when the context manager exits its scope.
|
|
133
|
+
"""
|
|
134
|
+
await self.close()
|
|
135
|
+
|
|
136
|
+
async def connect(self, other_database: int | None = None) -> None:
|
|
137
|
+
"""
|
|
138
|
+
Establish a connection to the Redis server.
|
|
139
|
+
|
|
140
|
+
:param other_database: The alternative database name to connect to.
|
|
141
|
+
:raises ValueError: If the connection to the Redis server fails.
|
|
142
|
+
:raises Exception: If any other error occurs during the connection.
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
|
|
146
|
+
self._client = AsyncRedis(
|
|
147
|
+
host=str(self._meta.host),
|
|
148
|
+
port=int(self._meta.port), # type: ignore
|
|
149
|
+
username=self._meta.username,
|
|
150
|
+
password=self._meta.password,
|
|
151
|
+
db=other_database if other_database else self._meta.database,
|
|
152
|
+
)
|
|
153
|
+
except TimeoutError:
|
|
154
|
+
raise ValueError("Redis connection timed out.")
|
|
155
|
+
except Exception as e:
|
|
156
|
+
raise e
|
|
157
|
+
|
|
158
|
+
async def close(self) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Close the connection to the Redis server.
|
|
161
|
+
|
|
162
|
+
This method is a no-op if the connection is already closed.
|
|
163
|
+
"""
|
|
164
|
+
if self._client:
|
|
165
|
+
await self._client.close()
|
|
@@ -7,6 +7,7 @@ class BaseResponseMeta(BaseModel):
|
|
|
7
7
|
code: int
|
|
8
8
|
message: str
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
class PaginationResponseMeta(BaseResponseMeta):
|
|
11
12
|
page: Optional[int] = Field(1, gt=0)
|
|
12
13
|
size: Optional[int] = Field(10, ge=0)
|
|
@@ -19,7 +20,7 @@ class ServiceResponse:
|
|
|
19
20
|
self.model = model
|
|
20
21
|
self.auth = auth
|
|
21
22
|
|
|
22
|
-
def basic(self,
|
|
23
|
+
def basic(self, route_name: str) -> dict:
|
|
23
24
|
"""
|
|
24
25
|
Generate a basic response, which is a dictionary of common response codes and models.
|
|
25
26
|
|
|
@@ -27,27 +28,32 @@ class ServiceResponse:
|
|
|
27
28
|
- 400 Bad Request
|
|
28
29
|
- 500 Internal Server Error
|
|
29
30
|
|
|
30
|
-
:param
|
|
31
|
+
:param route_name: The name of model for response
|
|
31
32
|
:return: A dictionary of common response codes and models
|
|
32
33
|
"""
|
|
33
34
|
return {
|
|
34
35
|
400: {
|
|
35
|
-
"model": create_model(
|
|
36
|
-
route, code=(int, 400), message=(str, "Bad Request")
|
|
37
|
-
),
|
|
36
|
+
"model": create_model(route_name, message=(str, "Bad Request")),
|
|
38
37
|
"description": "Occurs when the request you make does not match or is invalid",
|
|
39
38
|
},
|
|
40
39
|
500: {
|
|
41
40
|
"model": create_model(
|
|
42
|
-
|
|
43
|
-
code=(int, 500),
|
|
41
|
+
route_name,
|
|
44
42
|
message=(str, "Internal Server Error"),
|
|
45
43
|
),
|
|
46
44
|
"description": "Occurs when there is an engine or lib error in the engine",
|
|
47
45
|
},
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
def get(
|
|
48
|
+
def get(
|
|
49
|
+
self,
|
|
50
|
+
route_name: str,
|
|
51
|
+
model: Any = None,
|
|
52
|
+
obj: str = "Data",
|
|
53
|
+
auth: bool = False,
|
|
54
|
+
exclude_codes: list = [],
|
|
55
|
+
**kwargs,
|
|
56
|
+
) -> dict:
|
|
51
57
|
"""
|
|
52
58
|
Generate a response for a get request, which is a dictionary of common response codes and models.
|
|
53
59
|
|
|
@@ -55,7 +61,7 @@ class ServiceResponse:
|
|
|
55
61
|
- 200 Success
|
|
56
62
|
- 404 Not Found
|
|
57
63
|
|
|
58
|
-
:param
|
|
64
|
+
:param route_name: The name of model for response
|
|
59
65
|
:param model: The model to use for the response
|
|
60
66
|
:param obj: The object name to use for the response
|
|
61
67
|
:param auth: Whether or not the route requires authentication
|
|
@@ -65,39 +71,39 @@ class ServiceResponse:
|
|
|
65
71
|
response: dict = {
|
|
66
72
|
200: {
|
|
67
73
|
"model": create_model(
|
|
68
|
-
|
|
69
|
-
code=(int, 200),
|
|
74
|
+
route_name,
|
|
70
75
|
message=(str, "Success"),
|
|
71
76
|
data=(model if model else self.model, ...),
|
|
72
77
|
),
|
|
73
78
|
"description": "Success get data",
|
|
74
79
|
},
|
|
75
80
|
404: {
|
|
76
|
-
"model": create_model(
|
|
77
|
-
route, code=(int, 404), message=(str, f"{obj} not found")
|
|
78
|
-
),
|
|
81
|
+
"model": create_model(route_name, message=(str, f"{obj} not found")),
|
|
79
82
|
},
|
|
80
|
-
**self.basic(
|
|
81
|
-
**kwargs
|
|
83
|
+
**self.basic(route_name),
|
|
84
|
+
**kwargs,
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
if exclude_codes:
|
|
85
88
|
for code in exclude_codes:
|
|
86
89
|
del response[code]
|
|
87
|
-
|
|
88
90
|
|
|
89
91
|
if auth or self.auth:
|
|
90
92
|
response[401] = {
|
|
91
|
-
"model": create_model(
|
|
92
|
-
route, code=(int, 401), message=(str, "Unauthorized")
|
|
93
|
-
)
|
|
93
|
+
"model": create_model(route_name, message=(str, "Unauthorized"))
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
return response
|
|
97
|
-
|
|
98
95
|
|
|
96
|
+
return response
|
|
99
97
|
|
|
100
|
-
def pagination(
|
|
98
|
+
def pagination(
|
|
99
|
+
self,
|
|
100
|
+
route_name: str,
|
|
101
|
+
model: Any = None,
|
|
102
|
+
obj: str = "Data",
|
|
103
|
+
auth: bool = False,
|
|
104
|
+
exclude_codes: list = [],
|
|
105
|
+
**kwargs,
|
|
106
|
+
) -> dict:
|
|
101
107
|
"""
|
|
102
108
|
Generate a pagination response, which is a dictionary of common response codes and models.
|
|
103
109
|
|
|
@@ -106,7 +112,7 @@ class ServiceResponse:
|
|
|
106
112
|
- 404 Not Found
|
|
107
113
|
- 401 Unauthorized (optional)
|
|
108
114
|
|
|
109
|
-
:param
|
|
115
|
+
:param route_name: The name of model for response
|
|
110
116
|
:param model: The model to use for the response
|
|
111
117
|
:param obj: The object to be gotten
|
|
112
118
|
:param auth: Whether or not the route requires authentication
|
|
@@ -116,8 +122,7 @@ class ServiceResponse:
|
|
|
116
122
|
response: dict = {
|
|
117
123
|
200: {
|
|
118
124
|
"model": create_model(
|
|
119
|
-
|
|
120
|
-
code=(int, 200),
|
|
125
|
+
route_name,
|
|
121
126
|
message=(str, f"Success get all {obj}"),
|
|
122
127
|
data=(model if model else self.model, ...),
|
|
123
128
|
page=(int, 1),
|
|
@@ -126,29 +131,32 @@ class ServiceResponse:
|
|
|
126
131
|
),
|
|
127
132
|
},
|
|
128
133
|
404: {
|
|
129
|
-
"model": create_model(
|
|
130
|
-
route, code=(int, 404), message=(str, f"{obj} not found")
|
|
131
|
-
),
|
|
134
|
+
"model": create_model(route_name, message=(str, f"{obj} not found")),
|
|
132
135
|
},
|
|
133
|
-
**self.basic(
|
|
134
|
-
**kwargs
|
|
136
|
+
**self.basic(route_name),
|
|
137
|
+
**kwargs,
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
if exclude_codes:
|
|
138
141
|
for code in exclude_codes:
|
|
139
142
|
del response[code]
|
|
140
|
-
|
|
141
143
|
|
|
142
144
|
if auth or self.auth:
|
|
143
145
|
response[401] = {
|
|
144
|
-
"model": create_model(
|
|
145
|
-
route, code=(int, 401), message=(str, "Unauthorized")
|
|
146
|
-
)
|
|
146
|
+
"model": create_model(route_name, message=(str, "Unauthorized"))
|
|
147
147
|
}
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
return response
|
|
150
150
|
|
|
151
|
-
def creation(
|
|
151
|
+
def creation(
|
|
152
|
+
self,
|
|
153
|
+
route_name: str,
|
|
154
|
+
model: Any = None,
|
|
155
|
+
obj: str = "Data",
|
|
156
|
+
auth: bool = False,
|
|
157
|
+
exclude_codes: list = [],
|
|
158
|
+
**kwargs,
|
|
159
|
+
) -> dict:
|
|
152
160
|
"""
|
|
153
161
|
Generate a response for a create request, which is a dictionary of common response codes and models.
|
|
154
162
|
|
|
@@ -156,139 +164,126 @@ class ServiceResponse:
|
|
|
156
164
|
- 201 Created
|
|
157
165
|
- 401 Unauthorized
|
|
158
166
|
|
|
159
|
-
:param
|
|
167
|
+
:param route_name: The name of model for response
|
|
160
168
|
:param model: The model to use for the response
|
|
161
169
|
:param obj: The object name to use for the response
|
|
162
170
|
:param auth: Whether or not the route requires authentication
|
|
163
171
|
:param exclude_codes: A list of codes to exclude from the response
|
|
164
172
|
:return: A dictionary of common response codes and models
|
|
165
173
|
"""
|
|
166
|
-
response: dict =
|
|
174
|
+
response: dict = {
|
|
167
175
|
201: {
|
|
168
176
|
"model": create_model(
|
|
169
|
-
|
|
170
|
-
code=(int, 201),
|
|
177
|
+
route_name,
|
|
171
178
|
message=(str, f"{obj} created successfully"),
|
|
172
179
|
data=(model if model else self.model, ...),
|
|
173
180
|
),
|
|
174
181
|
},
|
|
175
|
-
**self.basic(
|
|
176
|
-
**kwargs
|
|
182
|
+
**self.basic(route_name),
|
|
183
|
+
**kwargs,
|
|
177
184
|
}
|
|
178
185
|
|
|
179
186
|
if exclude_codes:
|
|
180
187
|
for code in exclude_codes:
|
|
181
188
|
del response[code]
|
|
182
|
-
|
|
183
189
|
|
|
184
190
|
if auth or self.auth:
|
|
185
191
|
response[401] = {
|
|
186
|
-
"model": create_model(
|
|
187
|
-
route, code=(int, 401), message=(str, "Unauthorized")
|
|
188
|
-
)
|
|
192
|
+
"model": create_model(route_name, message=(str, "Unauthorized"))
|
|
189
193
|
}
|
|
190
|
-
|
|
194
|
+
|
|
191
195
|
return response
|
|
192
196
|
|
|
193
|
-
def update(
|
|
197
|
+
def update(
|
|
198
|
+
self,
|
|
199
|
+
route_name: str,
|
|
200
|
+
model: Any = None,
|
|
201
|
+
obj: str = "Data",
|
|
202
|
+
auth: bool = False,
|
|
203
|
+
exclude_codes: list = [],
|
|
204
|
+
**kwargs,
|
|
205
|
+
) -> dict:
|
|
194
206
|
"""
|
|
195
207
|
Generate a response for a update request, which is a dictionary of common response codes and models.
|
|
196
208
|
|
|
197
209
|
Contains:
|
|
198
210
|
- 200 OK
|
|
199
|
-
- 204 No Content
|
|
200
211
|
- 401 Unauthorized (optional)
|
|
201
212
|
|
|
202
|
-
:param
|
|
213
|
+
:param route_name: The name of model for response
|
|
203
214
|
:param model: The model to use for the response
|
|
204
215
|
:param obj: The object name to use for the response
|
|
205
216
|
:param auth: Whether or not the route requires authentication
|
|
206
217
|
:param exclude_codes: A list of codes to exclude from the response
|
|
207
218
|
:return: A dictionary of common response codes and models
|
|
208
219
|
"""
|
|
209
|
-
response: dict =
|
|
220
|
+
response: dict = {
|
|
210
221
|
200: {
|
|
211
222
|
"model": create_model(
|
|
212
|
-
|
|
213
|
-
code=(int, 200),
|
|
223
|
+
route_name,
|
|
214
224
|
message=(str, f"{obj} updated successfully"),
|
|
215
225
|
data=(model if model else self.model, ...),
|
|
216
226
|
),
|
|
217
227
|
},
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
route,
|
|
221
|
-
code=(int, 204),
|
|
222
|
-
message=(str, f"{obj} updated successfully"),
|
|
223
|
-
),
|
|
224
|
-
},
|
|
225
|
-
**self.basic(route),
|
|
226
|
-
**kwargs
|
|
228
|
+
**self.basic(route_name),
|
|
229
|
+
**kwargs,
|
|
227
230
|
}
|
|
228
231
|
|
|
229
232
|
if exclude_codes:
|
|
230
233
|
for code in exclude_codes:
|
|
231
234
|
del response[code]
|
|
232
|
-
|
|
233
235
|
|
|
234
236
|
if auth or self.auth:
|
|
235
237
|
response[401] = {
|
|
236
|
-
"model": create_model(
|
|
237
|
-
route, code=(int, 401), message=(str, "Unauthorized")
|
|
238
|
-
)
|
|
238
|
+
"model": create_model(route_name, message=(str, "Unauthorized"))
|
|
239
239
|
}
|
|
240
|
-
|
|
240
|
+
|
|
241
241
|
return response
|
|
242
242
|
|
|
243
|
-
def delete(
|
|
243
|
+
def delete(
|
|
244
|
+
self,
|
|
245
|
+
route_name: str,
|
|
246
|
+
model: Any = None,
|
|
247
|
+
obj: str = "Data",
|
|
248
|
+
auth: bool = False,
|
|
249
|
+
exclude_codes: list = [],
|
|
250
|
+
**kwargs,
|
|
251
|
+
) -> dict:
|
|
244
252
|
"""
|
|
245
253
|
Generate a response for a delete request, which is a dictionary of common response codes and models.
|
|
246
254
|
|
|
247
255
|
Contains:
|
|
248
256
|
- 200 Success
|
|
249
|
-
- 204 Success
|
|
250
257
|
- 400 Bad Request
|
|
251
258
|
- 401 Unauthorized (optional)
|
|
252
259
|
- 500 Internal Server Error
|
|
253
260
|
|
|
254
|
-
:param
|
|
261
|
+
:param route_name: The name of model for response
|
|
255
262
|
:param model: The model to use for the response
|
|
256
263
|
:param obj: The object name to use for the response
|
|
257
264
|
:param auth: Whether or not the route requires authentication
|
|
258
265
|
:param exclude_codes: A list of codes to exclude from the response
|
|
259
266
|
:return: A dictionary of common response codes and models
|
|
260
267
|
"""
|
|
261
|
-
response: dict =
|
|
268
|
+
response: dict = {
|
|
262
269
|
200: {
|
|
263
270
|
"model": create_model(
|
|
264
|
-
|
|
265
|
-
code=(int, 200),
|
|
271
|
+
route_name,
|
|
266
272
|
message=(str, f"{obj} delete successfully"),
|
|
267
273
|
data=(model if model else self.model, ...),
|
|
268
274
|
),
|
|
269
275
|
},
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
route,
|
|
273
|
-
code=(int, 204),
|
|
274
|
-
message=(str, f"{obj} delete successfully"),
|
|
275
|
-
),
|
|
276
|
-
},
|
|
277
|
-
**self.basic(route),
|
|
278
|
-
**kwargs
|
|
276
|
+
**self.basic(route_name),
|
|
277
|
+
**kwargs,
|
|
279
278
|
}
|
|
280
279
|
|
|
281
280
|
if exclude_codes:
|
|
282
281
|
for code in exclude_codes:
|
|
283
282
|
del response[code]
|
|
284
|
-
|
|
285
283
|
|
|
286
284
|
if auth or self.auth:
|
|
287
285
|
response[401] = {
|
|
288
|
-
"model": create_model(
|
|
289
|
-
route, code=(int, 401), message=(str, "Unauthorized")
|
|
290
|
-
)
|
|
286
|
+
"model": create_model(route_name, message=(str, "Unauthorized"))
|
|
291
287
|
}
|
|
292
|
-
|
|
288
|
+
|
|
293
289
|
return response
|
|
294
|
-
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Optional, Any
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PaginationSchema(BaseModel):
|
|
7
|
+
page: Optional[int] = Field(1, gt=0, description="Page number")
|
|
8
|
+
size: Optional[int] = Field(10, ge=0, description="Page size")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OrderSchema(BaseModel):
|
|
12
|
+
order_by: Optional[str] = Field(None, description="Order by field")
|
|
13
|
+
order_type: Optional[str | int] = Field(
|
|
14
|
+
None, description="Order type", examples=[1, "asc", "desc"]
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FilterValueSchema(BaseModel):
|
|
19
|
+
filter_value: Optional[Any] = Field(None, description="Filter value")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FilterSchema(FilterValueSchema):
|
|
23
|
+
filter_by: Optional[str] = Field(None, description="Filter by field")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FilterOpsSchema(FilterSchema):
|
|
27
|
+
filter_op: Optional[str] = Field(None, description="Filter operation")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Tuple, Type
|
|
2
|
+
|
|
3
|
+
from pydantic_settings import (
|
|
4
|
+
BaseSettings,
|
|
5
|
+
SettingsConfigDict,
|
|
6
|
+
PyprojectTomlConfigSettingsSource,
|
|
7
|
+
PydanticBaseSettingsSource,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProjectConfig(BaseSettings):
|
|
12
|
+
|
|
13
|
+
name: str
|
|
14
|
+
version: str = "0.1.0"
|
|
15
|
+
description: str = ""
|
|
16
|
+
authors: list[str] = []
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def settings_customise_sources(
|
|
20
|
+
cls,
|
|
21
|
+
settings_cls: Type[BaseSettings],
|
|
22
|
+
init_settings: PydanticBaseSettingsSource,
|
|
23
|
+
env_settings: PydanticBaseSettingsSource,
|
|
24
|
+
dotenv_settings: PydanticBaseSettingsSource,
|
|
25
|
+
file_secret_settings: PydanticBaseSettingsSource,
|
|
26
|
+
) -> Tuple[PydanticBaseSettingsSource, ...]:
|
|
27
|
+
return (PyprojectTomlConfigSettingsSource(settings_cls),)
|
|
28
|
+
|
|
29
|
+
model_config = SettingsConfigDict(
|
|
30
|
+
pyproject_toml_table_header=("tool", "poetry"), extra="ignore"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def title(self) -> str:
|
|
35
|
+
return self.name.replace("-", " ").title()
|
|
@@ -42,6 +42,7 @@ class EnumV2(Enum):
|
|
|
42
42
|
"""
|
|
43
43
|
return self._description_
|
|
44
44
|
|
|
45
|
+
|
|
45
46
|
class Operator(EnumV2):
|
|
46
47
|
equal = ("eq", "value is equals to")
|
|
47
48
|
unequal = ("ne", "value isn't equals to")
|
|
@@ -61,9 +62,9 @@ class FilterOption(EnumV2):
|
|
|
61
62
|
mustnt = ("mustnt", "List of filter mustn't exact")
|
|
62
63
|
should = ("should", "List of filter should exact")
|
|
63
64
|
shouldnt = ("shouldnt", "List of filter shouldn't exact")
|
|
64
|
-
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
|
|
67
|
+
class LocationLevel(EnumV2):
|
|
67
68
|
CONTINENT = ("continent", "Continent level data")
|
|
68
69
|
COUNTRY = ("country", "Country level data")
|
|
69
70
|
PROVINCE = ("province", "Province level data")
|
|
@@ -72,9 +73,9 @@ class LocationLevel(str, EnumV2):
|
|
|
72
73
|
SUBDISTRICT = ("subdistrict", "Subdistrict level data")
|
|
73
74
|
|
|
74
75
|
|
|
75
|
-
class MedallionTypes(
|
|
76
|
-
LAKE = ("lake",
|
|
77
|
-
BRONZE = ("bronze",
|
|
78
|
-
SILVER = ("silver",
|
|
79
|
-
GOLD = ("gold",
|
|
80
|
-
OTHER = ("other",
|
|
76
|
+
class MedallionTypes(EnumV2):
|
|
77
|
+
LAKE = ("lake", "Lake data")
|
|
78
|
+
BRONZE = ("bronze", "bronze level Medallion")
|
|
79
|
+
SILVER = ("silver", "silver level Medallion")
|
|
80
|
+
GOLD = ("gold", "gold level Medallion")
|
|
81
|
+
OTHER = ("other", "other than any level Medallion")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .enums import *
|
|
File without changes
|