jknife 0.0.1__py2.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.
- jknife/__init__.py +0 -0
- jknife/commands/__init__.py +0 -0
- jknife/commands/jknife.py +376 -0
- jknife/db/__init__.py +142 -0
- jknife/db/models/__init__.py +0 -0
- jknife/db/models/mongo/__init__.py +57 -0
- jknife/db/models/mongo/network.py +27 -0
- jknife/db/models/mongo/personnel_info.py +108 -0
- jknife/db/models/mongo/settings.py +58 -0
- jknife/db/models/mongo/token.py +62 -0
- jknife/db/models/mongo/users.py +91 -0
- jknife/db/models/rdbms/__init__.py +53 -0
- jknife/db/models/rdbms/network.py +24 -0
- jknife/db/models/rdbms/personnel_info.py +88 -0
- jknife/db/models/rdbms/settings.py +39 -0
- jknife/db/models/rdbms/token.py +53 -0
- jknife/db/models/rdbms/users.py +94 -0
- jknife/dependencies/__init__.py +0 -0
- jknife/dependencies/token.py +202 -0
- jknife/dependencies/users.py +76 -0
- jknife/logging.py +69 -0
- jknife/views/__init__.py +23 -0
- jknife/views/error_message.py +38 -0
- jknife/views/personnel_info.py +42 -0
- jknife/views/tokens.py +22 -0
- jknife/views/users.py +70 -0
- jknife-0.0.1.dist-info/METADATA +17 -0
- jknife-0.0.1.dist-info/RECORD +31 -0
- jknife-0.0.1.dist-info/WHEEL +5 -0
- jknife-0.0.1.dist-info/entry_points.txt +2 -0
- jknife-0.0.1.dist-info/licenses/LICENSE +7 -0
jknife/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
FOLDER_LIST: tuple = ("models", "routers", "views", )
|
|
6
|
+
|
|
7
|
+
# Help MSG for commands 'jknife'
|
|
8
|
+
HELP_MSG = """
|
|
9
|
+
[ Command 'jknife' ]
|
|
10
|
+
Usage: jknife [sub_command] [3rd argument]
|
|
11
|
+
|
|
12
|
+
* sub_command:
|
|
13
|
+
- startproject [PROJECT_NAME] : create project filesystem
|
|
14
|
+
- createapp [APP_NAME] : create API application in project
|
|
15
|
+
- run [OPTIONS] : run server with uvicorn
|
|
16
|
+
|
|
17
|
+
[run OPTIONS]
|
|
18
|
+
--host [HOSTNAME] : run server with hostname [HOSTNAME]
|
|
19
|
+
--port [PORT_INT] : run server with portnumber [PORT]
|
|
20
|
+
--reload : reload uvicorn server automatically after editing source.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Template for models/APP_NAME.py
|
|
24
|
+
MODULE_IMPORT_STRING_MODELS: str = """
|
|
25
|
+
# import packages from default or pip library
|
|
26
|
+
from typing_extensions import Annotated, Doc
|
|
27
|
+
|
|
28
|
+
# import packages from this framework below
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# set CONSTANT variables below.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# define your own customising class below
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Template for routers/APP_NAME.py
|
|
38
|
+
MODULE_IMPORT_STRING_ROUTERS: str = """
|
|
39
|
+
# import packages from default or pip library
|
|
40
|
+
from fastapi import APIRouter, Depends, status, HTTPException
|
|
41
|
+
from typing_extensions import Annotated, Doc
|
|
42
|
+
|
|
43
|
+
# import packages from this framework below
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# set CONSTANT variables below.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# define your own customising class below
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
# Template for views/APP_NAME.py
|
|
53
|
+
MODULE_IMPORT_STRING_VIEWS: str = """
|
|
54
|
+
# import packages from default or pip library
|
|
55
|
+
from pydantic import BaseModel
|
|
56
|
+
|
|
57
|
+
# import packages from this framework below
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# set CONSTANT variables below.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# define your own customising class below
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
# WARN MSGs
|
|
67
|
+
WARN_MSG_ALREADY_EXIST_PROJECT: str = """
|
|
68
|
+
[WARNING] You already have project '{}'.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
# ERROR MSGs
|
|
72
|
+
ERROR_MSG_START_PROJECT: str = """
|
|
73
|
+
[ERROR]
|
|
74
|
+
- You have to execute 'createapp' command in your project folder.
|
|
75
|
+
|
|
76
|
+
* Command for Starting Project: jknife startproject [PROJECT_NAME]
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
# File Contents
|
|
80
|
+
CONTENTS_IN_SETTINGS: str = """# this file is charge of config for custom fastapi.
|
|
81
|
+
from os import environ
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# [ DEBUG ]
|
|
85
|
+
# this variable is a mode switcher.
|
|
86
|
+
# if you are about to publish your application to public, please turn this switch to False.
|
|
87
|
+
# on the other hands, during development, please switch this value to True.
|
|
88
|
+
# DEBUG_MODE provides
|
|
89
|
+
# - query logs on database.
|
|
90
|
+
DEBUG_MODE: bool = True
|
|
91
|
+
|
|
92
|
+
# [ API_VERSION ]
|
|
93
|
+
# API_VERSION is a management variables for your api.
|
|
94
|
+
# This value will be used to call api as an URL prefix. e.g) /api/{YOUR_API_VERSION}/
|
|
95
|
+
# you can modify the value of API_VERSION to whatever you want.
|
|
96
|
+
API_VERSION: str = "v1"
|
|
97
|
+
|
|
98
|
+
# [ DATABASE ]
|
|
99
|
+
# DATABASE is charge of connection to database server.
|
|
100
|
+
# recommend to store DB information in env var and get them by using environ.get()
|
|
101
|
+
# this framework supports some databases.
|
|
102
|
+
# - supported type: ["none", "postgresql", "mysql", "sqlite", "mongo"]
|
|
103
|
+
#
|
|
104
|
+
# type 'none': if you do not want to use database. it is a default if the type is None
|
|
105
|
+
# structure of database
|
|
106
|
+
# - type in ['postgresql', 'mysql', 'mongo']
|
|
107
|
+
# - key: "dbms"
|
|
108
|
+
# - values: {"username": DB_USERNAME, "password": DB_PASSWORD, "host": DB_HOST, "port": DB_PORT, "db": DB_NAME}
|
|
109
|
+
# - if you use mongo, it is possible to add key 'authSource' for name of database that is responsible for auth.
|
|
110
|
+
# - type 'sqlite'
|
|
111
|
+
# - sqlite_filepath: input path of sqlite3 database file in an absolute way or a relative one.
|
|
112
|
+
# "redis" exist for the Redis - cached db.
|
|
113
|
+
DATABASE: dict = {
|
|
114
|
+
"type": environ.get("db_type"),
|
|
115
|
+
"dbms": {
|
|
116
|
+
"username": environ.get("db_username"),
|
|
117
|
+
"password": environ.get("db_password"),
|
|
118
|
+
"host": environ.get("db_host"),
|
|
119
|
+
"port": int(environ.get("db_port")),
|
|
120
|
+
"db": environ.get("db_name"),
|
|
121
|
+
"authSource": environ.get("mongo_auth_source")
|
|
122
|
+
},
|
|
123
|
+
"sqlite": {
|
|
124
|
+
"sqlite_filepath": "./fastapi.db"
|
|
125
|
+
},
|
|
126
|
+
"redis": {
|
|
127
|
+
"host": environ.get("redis_host"),
|
|
128
|
+
"port": environ.get("redis_port"),
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# [ AUTHENTICATION ]
|
|
133
|
+
# AUTHENTICATION is charge of JWT token for authenticated user.
|
|
134
|
+
# token: information for JWT token
|
|
135
|
+
# - token_issuer: token issuer
|
|
136
|
+
# - token_audience: IP or URL address that allows this token
|
|
137
|
+
# - token_valid_time: interval time between when the access token issued and expired in minutes.
|
|
138
|
+
# - token_refresh_time: interval time in minute for reissuing access token.
|
|
139
|
+
# - secret_key: secret key to encrypt token
|
|
140
|
+
# - algorithm: select encryption algorithm
|
|
141
|
+
# * HS: for HMAC symmetric algorithm
|
|
142
|
+
# * RS: for RSA symmetric algorithm
|
|
143
|
+
# * ES: for ECDSA asymmetric algorithm
|
|
144
|
+
AUTHENTICATION: dict = {
|
|
145
|
+
"token": {
|
|
146
|
+
"token_issuer": "http://127.0.0.1",
|
|
147
|
+
"token_audience": "http://127.0.0.1",
|
|
148
|
+
"token_valid_time": 20,
|
|
149
|
+
"token_refresh_time": 60 * 24 * 30,
|
|
150
|
+
"secret_key": "THIS IS A TEST",
|
|
151
|
+
"algorithm": "HS256"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# [ PASSWORD_POLICIES ]
|
|
156
|
+
# PASSWORD_POLICIES is charge of password compliance, when users are created or change their password
|
|
157
|
+
# compliance: the list of password policies
|
|
158
|
+
# - PASSWORD_MINLEN: limit the minimum length of password. default is 8 but it can be customized with 'min_length'.
|
|
159
|
+
# - PASSWORD_UPCHAR: password must contain at least one upper alphabet character.
|
|
160
|
+
# - PASSWORD_LOWCHAR: password must contain at least one lower alphabet character.
|
|
161
|
+
# - PASSWORD_NUMBER: password must contain at least one numeric character.
|
|
162
|
+
# - PASSWORD_SPECIAL: password must contain at least one special character.
|
|
163
|
+
# encrypt_type: algorithm for encrypting password. please refer to 'hashlib.algorithms_available'
|
|
164
|
+
# min_length: minimum length of password. it will be used for PASSWORD_MINLEN
|
|
165
|
+
PASSWORD_POLICIES: dict = {
|
|
166
|
+
"compliance": [
|
|
167
|
+
"PASSWORD_MINLEN",
|
|
168
|
+
"PASSWORD_UPCHAR",
|
|
169
|
+
"PASSWORD_LOWCHAR",
|
|
170
|
+
"PASSWORD_NUMBER",
|
|
171
|
+
"PASSWORD_SPECIAL"
|
|
172
|
+
],
|
|
173
|
+
"encrypt_type": "sha256",
|
|
174
|
+
"min_length": 8
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# [ LOG_SETTINGS ]
|
|
179
|
+
# LOG_SETTINGS is based on python dictConfig
|
|
180
|
+
# (https://docs.python.org/3/library/logging.config.html#dictionary-schema-details).
|
|
181
|
+
# Refer to the official documentation, write down your own config for logging.
|
|
182
|
+
# {"version": 1, "disable_existing_logger": False} will be applied automatically.
|
|
183
|
+
# If DEBUG_MODE is True, uvicorn log will be printed out on your console.
|
|
184
|
+
# Default Logger:
|
|
185
|
+
# * DB: The logger 'db' is charge of database logging.
|
|
186
|
+
# if you want to change the name of logger, you should also change the DB_LOGGER_LIST below.
|
|
187
|
+
# or you can not see any proper log.
|
|
188
|
+
LOG_SETTINGS: dict = {
|
|
189
|
+
"formatters": {
|
|
190
|
+
"default": {
|
|
191
|
+
"format": "[%(asctime)s] %(name)s:%(levelname)s - %(message)s",
|
|
192
|
+
"datefmt": "%Y-%m-%d %H:%M:%S:%s"
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
"handlers": {
|
|
196
|
+
"main": {
|
|
197
|
+
"class": "logging.handlers.RotatingFileHandler",
|
|
198
|
+
"level": "INFO",
|
|
199
|
+
"formatter": "default",
|
|
200
|
+
"filename": "logs/all_logs.log",
|
|
201
|
+
"maxBytes": 1024 * 1024 * 100, # 10 MB
|
|
202
|
+
"backupCount": 3
|
|
203
|
+
},
|
|
204
|
+
"db": {
|
|
205
|
+
"class": "logging.handlers.RotatingFileHandler",
|
|
206
|
+
"level": "INFO",
|
|
207
|
+
"formatter": "default",
|
|
208
|
+
"filename": "logs/db.log",
|
|
209
|
+
"maxBytes": 1024 * 1024 * 100, # 10 MB
|
|
210
|
+
"backupCount": 3
|
|
211
|
+
},
|
|
212
|
+
"db_error": {
|
|
213
|
+
"class": "logging.handlers.RotatingFileHandler",
|
|
214
|
+
"level": "ERROR",
|
|
215
|
+
"formatter": "default",
|
|
216
|
+
"filename": "logs/db_error.log",
|
|
217
|
+
"maxBytes": 1024 * 1024 * 100, # 10 MB
|
|
218
|
+
"backupCount": 3
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
"loggers": {
|
|
222
|
+
"": {
|
|
223
|
+
"level": "INFO",
|
|
224
|
+
"handlers": ["main"],
|
|
225
|
+
},
|
|
226
|
+
"db": {
|
|
227
|
+
"level": "INFO",
|
|
228
|
+
"handlers": ["db", "db_error"],
|
|
229
|
+
"propagate": True,
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# [ LOGGER_LIST ]
|
|
235
|
+
# create your own logger instance with core.logging.LoggerMgmt and use it in specific realm.
|
|
236
|
+
DB_LOGGER_LIST = ("db",)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
### END CONTENTS ###
|
|
240
|
+
"""
|
|
241
|
+
CONTENTS_IN_MAIN: str = """# import modules from python library
|
|
242
|
+
import importlib, os
|
|
243
|
+
from fastapi import FastAPI, Request, status
|
|
244
|
+
from fastapi.responses import JSONResponse
|
|
245
|
+
from fastapi.exceptions import RequestValidationError
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# import required objects from projects
|
|
249
|
+
from jknife.db import DBConnector
|
|
250
|
+
from settings import API_VERSION
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# set main url for all router
|
|
254
|
+
MAIN_URL: str = f"/api/{API_VERSION}"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# DB Connector
|
|
258
|
+
db_connector = DBConnector()
|
|
259
|
+
|
|
260
|
+
# start application
|
|
261
|
+
app = FastAPI(lifespan=db_connector.startup_db)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# add exception handler for ValidationException
|
|
265
|
+
@app.exception_handler(exc_class_or_status_code=RequestValidationError)
|
|
266
|
+
async def validation_error(req: Request, exc: RequestValidationError):
|
|
267
|
+
errors: dict = exc.errors()[0] if isinstance(exc.errors(), list) else dict(exc.errors())
|
|
268
|
+
result: dict = {"field": errors.get("input"), "msg": errors.get("msg")}
|
|
269
|
+
return JSONResponse(
|
|
270
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
271
|
+
content={"detail": result}
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# add routers_bak
|
|
276
|
+
for router in os.listdir(os.path.abspath(path="routers_bak")):
|
|
277
|
+
if not router.startswith("__"):
|
|
278
|
+
tmp_module = importlib.import_module(name=f"routers_bak.{router.split('.')[0]}")
|
|
279
|
+
app.include_router(router=tmp_module.router,
|
|
280
|
+
prefix=MAIN_URL)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def startproject(pjt_name: str) -> None:
|
|
287
|
+
# check whether the folder 'pjt_name' exist or not.
|
|
288
|
+
if pjt_name in os.listdir():
|
|
289
|
+
# print WARNING
|
|
290
|
+
print(WARN_MSG_ALREADY_EXIST_PROJECT.format(pjt_name))
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
# create project folder
|
|
294
|
+
os.mkdir(pjt_name)
|
|
295
|
+
|
|
296
|
+
# create working folders for jknife
|
|
297
|
+
for folder_name in FOLDER_LIST:
|
|
298
|
+
if f"{folder_name}" not in os.listdir(pjt_name):
|
|
299
|
+
os.mkdir(f"{pjt_name}/{folder_name}")
|
|
300
|
+
|
|
301
|
+
if "__init__.py" not in os.listdir(f"{pjt_name}/{folder_name}"):
|
|
302
|
+
with open(f"{pjt_name}/{folder_name}/__init__.py", mode="w") as f:
|
|
303
|
+
f.write("")
|
|
304
|
+
|
|
305
|
+
# create settings.py file
|
|
306
|
+
if "settings.py" not in os.listdir():
|
|
307
|
+
with open("settings.py", "w") as f:
|
|
308
|
+
f.write(CONTENTS_IN_SETTINGS)
|
|
309
|
+
|
|
310
|
+
# create main.py file
|
|
311
|
+
if "main.py" not in os.listdir():
|
|
312
|
+
with open("main.py", "w") as f:
|
|
313
|
+
f.write(CONTENTS_IN_MAIN)
|
|
314
|
+
|
|
315
|
+
return None
|
|
316
|
+
|
|
317
|
+
def createapp(app_name: str) -> None:
|
|
318
|
+
# check main folders for jknife
|
|
319
|
+
for folder_name in FOLDER_LIST:
|
|
320
|
+
try:
|
|
321
|
+
if os.listdir(folder_name):
|
|
322
|
+
with open(f"{folder_name}/__init__.py", "w") as f:
|
|
323
|
+
f.write("")
|
|
324
|
+
|
|
325
|
+
except FileNotFoundError:
|
|
326
|
+
print(ERROR_MSG_START_PROJECT)
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
# create app.py in 'models' folder
|
|
330
|
+
with open(f"models/{app_name}.py", "w") as f:
|
|
331
|
+
f.write(MODULE_IMPORT_STRING_MODELS)
|
|
332
|
+
|
|
333
|
+
with open(f"routers/{app_name}.py", "w") as f:
|
|
334
|
+
f.write(MODULE_IMPORT_STRING_ROUTERS)
|
|
335
|
+
|
|
336
|
+
with open(f"views/{app_name}.py", "w") as f:
|
|
337
|
+
f.write(MODULE_IMPORT_STRING_VIEWS)
|
|
338
|
+
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
def help_msg():
|
|
342
|
+
print(HELP_MSG)
|
|
343
|
+
return None
|
|
344
|
+
|
|
345
|
+
def run_server(options: list):
|
|
346
|
+
args = "".join([ option.replace("=", "") for option in options ])
|
|
347
|
+
print(f"uvicorn main:app {args}")
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
def main() -> None:
|
|
351
|
+
read_command: list = sys.argv
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
sub_command = read_command[1]
|
|
355
|
+
|
|
356
|
+
if sub_command == "startproject":
|
|
357
|
+
startproject(pjt_name=read_command[2])
|
|
358
|
+
return None
|
|
359
|
+
|
|
360
|
+
elif sub_command == "createapp":
|
|
361
|
+
createapp(app_name=read_command[2])
|
|
362
|
+
return None
|
|
363
|
+
|
|
364
|
+
elif sub_command == "run":
|
|
365
|
+
run_server(read_command[2:])
|
|
366
|
+
return None
|
|
367
|
+
|
|
368
|
+
except IndexError:
|
|
369
|
+
pass
|
|
370
|
+
|
|
371
|
+
help_msg()
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
if __name__ == "__main__":
|
|
376
|
+
main()
|
jknife/db/__init__.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# import packages from default or pip library
|
|
2
|
+
import importlib, os, redis
|
|
3
|
+
from contextlib import asynccontextmanager
|
|
4
|
+
from typing_extensions import Annotated, Doc
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
from sqlalchemy.engine.base import Engine
|
|
7
|
+
from sqlalchemy.exc import OperationalError
|
|
8
|
+
from sqlmodel import SQLModel, Session, create_engine
|
|
9
|
+
from mongoengine import connect
|
|
10
|
+
|
|
11
|
+
# import packages from this framework
|
|
12
|
+
from src.jknife.logging import LoggerMgmt
|
|
13
|
+
from settings import *
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Define supported database
|
|
17
|
+
SUPPORT_DATABASE: dict = {
|
|
18
|
+
"none": {"type": "none", "engine": "none"},
|
|
19
|
+
"postgresql": {"type": "dbms", "engine": "rdbms"},
|
|
20
|
+
"mysql": {"type": "dbms", "engine": "rdbms"},
|
|
21
|
+
"mongo": {"type": "dbms", "engine": "mongo"},
|
|
22
|
+
"sqlite": {"type": "sqlite", "engine": "rdbms"},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# set logger
|
|
26
|
+
logger = LoggerMgmt(logger_names=DB_LOGGER_LIST or ["db"])
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# define redis instance
|
|
30
|
+
redis_host: str = DATABASE.get("redis").get("host")
|
|
31
|
+
redis_port: int = DATABASE.get("redis").get("port")
|
|
32
|
+
redis_connector = redis.Redis(host=redis_host, port=redis_port)
|
|
33
|
+
if redis_host is not None:
|
|
34
|
+
try:
|
|
35
|
+
redis_connector.ping()
|
|
36
|
+
if not DEBUG_MODE:
|
|
37
|
+
redis_connector.flushdb()
|
|
38
|
+
|
|
39
|
+
except redis.exceptions.ConnectionError:
|
|
40
|
+
logger.warning(msg="Can not connect to Redis server. Please check your server's information.")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# define DBConnector
|
|
44
|
+
class DBConnector:
|
|
45
|
+
"""
|
|
46
|
+
This class will make the application connect to database server which is designed in settings.py
|
|
47
|
+
The list of supported database is written in SUPPORT_DATABASE above.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self):
|
|
51
|
+
self.__type: Annotated[str,
|
|
52
|
+
Doc("Assign the database type. It must be one of value in SUPPORT_DB_TYPES.")] = DATABASE.get("type") or "none"
|
|
53
|
+
self.__db_info: Annotated[dict | None,
|
|
54
|
+
Doc(f"type of database defined in SUPPORTED_DATABASE")] = SUPPORT_DATABASE.get(self.__type)
|
|
55
|
+
|
|
56
|
+
# import table classes in models_bak for sqlmodel.
|
|
57
|
+
if self.__db_info.get("engine") == "rdbms":
|
|
58
|
+
for file in os.listdir(os.path.abspath("models")):
|
|
59
|
+
if not file.startswith("__") and file.endswith(".py"):
|
|
60
|
+
importlib.import_module(f"models_bak.{file.split('.')[0]}")
|
|
61
|
+
|
|
62
|
+
# get connection information from settings.py
|
|
63
|
+
if self.__db_info is not None:
|
|
64
|
+
self.__conn_info: Annotated[dict | None,
|
|
65
|
+
Doc("the type of database from DATABASE in settings.py")] = DATABASE.get(self.__db_info.get("type"))
|
|
66
|
+
|
|
67
|
+
if self.__type in SUPPORT_DATABASE.keys() and self.__conn_info is not None:
|
|
68
|
+
|
|
69
|
+
# for normal DBMS server
|
|
70
|
+
if SUPPORT_DATABASE.get(self.__type).get("type") == "dbms":
|
|
71
|
+
self.__db: Annotated[str,
|
|
72
|
+
Doc("The name of database that will be used for this projects")] = self.__conn_info.get("db")
|
|
73
|
+
self.__host: Annotated[str,
|
|
74
|
+
Doc("The name of database that will be used for this projects")] = self.__conn_info.get("host")
|
|
75
|
+
self.__port: Annotated[str,
|
|
76
|
+
Doc("The name of database that will be used for this projects")] = self.__conn_info.get("port")
|
|
77
|
+
self.__username: Annotated[str,
|
|
78
|
+
Doc("The name of database that will be used for this projects")] = self.__conn_info.get("username")
|
|
79
|
+
self.__password: Annotated[str,
|
|
80
|
+
Doc("The name of database that will be used for this projects")] = self.__conn_info.get("password")
|
|
81
|
+
self.__auth_source: Annotated[str,
|
|
82
|
+
Doc("This class member is for mongo. Input the name of database that the username came from.")] = self.__conn_info.get("auth_source")
|
|
83
|
+
|
|
84
|
+
# for sqlite3
|
|
85
|
+
elif SUPPORT_DATABASE.get(self.__type).get("type") == "filedb":
|
|
86
|
+
self.__sqlite3_filepath: Annotated[str,
|
|
87
|
+
Doc("This class member is for sqlite3 only. Input must be absolute or relative path.")] = self.__conn_info.get("sqlite_filepath")
|
|
88
|
+
|
|
89
|
+
self.__engine: Annotated[Engine,
|
|
90
|
+
Doc("engine will be used to get db session.")] = self.__create_engine()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def __create_engine(self) -> Engine | None:
|
|
94
|
+
args: Annotated[dict,
|
|
95
|
+
Doc("Arguments for create_engine()")] = {"check_same_thread": False} \
|
|
96
|
+
if SUPPORT_DATABASE.get("type") == "filedb" else {}
|
|
97
|
+
logger.debug(f"DB connection arguments: {args}")
|
|
98
|
+
|
|
99
|
+
url = f"{self.__type}://{self.__username}:{self.__password}@{self.__host}:{self.__port}/{self.__db}" \
|
|
100
|
+
if SUPPORT_DATABASE.get(self.__type).get("type") == "dbms" else f"{self.__type}:///{self.__sqlite3_filepath}"
|
|
101
|
+
logger.debug(f"DB connection URL: {url}")
|
|
102
|
+
|
|
103
|
+
if SUPPORT_DATABASE.get(self.__type).get("engine") == "rdbms":
|
|
104
|
+
logger.info(f"creating RDBMS engine for '{self.__type}' database...")
|
|
105
|
+
return create_engine(url=url,
|
|
106
|
+
echo=DEBUG_MODE,
|
|
107
|
+
connect_args=args)
|
|
108
|
+
|
|
109
|
+
# create mongo client module.
|
|
110
|
+
connect(**self.__conn_info)
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
@asynccontextmanager
|
|
114
|
+
async def startup_db(self, app: FastAPI):
|
|
115
|
+
if self.__type == "none":
|
|
116
|
+
logger.debug("There is no database config.")
|
|
117
|
+
yield
|
|
118
|
+
|
|
119
|
+
else:
|
|
120
|
+
if self.__type in SUPPORT_DATABASE:
|
|
121
|
+
try:
|
|
122
|
+
if SUPPORT_DATABASE.get(self.__type).get("engine") == "rdbms":
|
|
123
|
+
SQLModel.metadata.create_all(bind=self.__engine)
|
|
124
|
+
logger.info(f"successfully connected to '{self.__type}' database!")
|
|
125
|
+
|
|
126
|
+
else:
|
|
127
|
+
logger.info(f"successfully created `{self.__type}` connecting module!")
|
|
128
|
+
|
|
129
|
+
yield
|
|
130
|
+
|
|
131
|
+
except (OperationalError,) as e:
|
|
132
|
+
logger.error(f"Can not connect to DB server: '{self.__host}:{self.__port}/{self.__db}'")
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
else:
|
|
136
|
+
logger.warning(f"'{self.__type} is not supported database type.")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
# return session for RDBMS
|
|
140
|
+
def get_session(self):
|
|
141
|
+
with Session(self.__engine) as session:
|
|
142
|
+
yield session
|
|
File without changes
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# import packages from default or pip library
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from typing_extensions import (Annotated,
|
|
4
|
+
Doc,
|
|
5
|
+
Optional,
|
|
6
|
+
deprecated)
|
|
7
|
+
from uuid import UUID, uuid4
|
|
8
|
+
from mongoengine import Document, UUIDField, DateTimeField
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# import packages from this framework
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# define Class for Common MongoDB
|
|
15
|
+
@deprecated("MongoDB does not requires to set uuid manually.")
|
|
16
|
+
class UUIDMixin(Document):
|
|
17
|
+
meta = {'abstract': True}
|
|
18
|
+
|
|
19
|
+
id: Annotated[UUID,
|
|
20
|
+
Doc("UUID format id for each table row.")] = UUIDField(primary_key=True, default=None, unique=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RegisterDateTimeMixin(Document):
|
|
24
|
+
meta = {'abstract': True}
|
|
25
|
+
|
|
26
|
+
register_dt: Annotated[datetime,
|
|
27
|
+
Doc("Datetime that the row was added at.")] = DateTimeField(null=False, default=None)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class UpdateDateTimeMixin(Document):
|
|
31
|
+
meta = {'abstract': True}
|
|
32
|
+
|
|
33
|
+
update_dt: Annotated[Optional[datetime],
|
|
34
|
+
Doc("Datetime that the row was updated at.")] = DateTimeField(null=False, default=None)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# define function to control MongoDB document event
|
|
38
|
+
# please follow the procedure below
|
|
39
|
+
# create event handler function with name starting with _.
|
|
40
|
+
# event handler function must have 3 args: sender, document, **kwargs)
|
|
41
|
+
# each db column can get from 'document'
|
|
42
|
+
# import pre_init or pre_save from 'mongoengine.signals' in your model file.
|
|
43
|
+
# pre_init.connect(EVNET_HANDLER_FUNC_NAME, sender=TABLE_CLASS_NAME)
|
|
44
|
+
@deprecated("MongoDB does not requires to set uuid manually.")
|
|
45
|
+
def _assign_uuid(sender, document, **kwargs) -> None:
|
|
46
|
+
if document.id is None:
|
|
47
|
+
document.id = uuid4()
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def _assign_register_datetime(sender, document, **kwargs) -> None:
|
|
51
|
+
if document.register_dt is None:
|
|
52
|
+
document.register_dt = datetime.now(tz=timezone.utc)
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
def _assign_update_datetime(sender, document, **kwargs) -> None:
|
|
56
|
+
document.update_dt = datetime.now(tz=timezone.utc)
|
|
57
|
+
return None
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# import packages from default or pip library
|
|
2
|
+
from typing_extensions import Annotated, Doc
|
|
3
|
+
from mongoengine import Document, ListField, pre_save
|
|
4
|
+
|
|
5
|
+
# import packages from this framework
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# define Class for Common SQLModel
|
|
9
|
+
class AllowIPsMixin(Document):
|
|
10
|
+
meta = {'abstract': True}
|
|
11
|
+
|
|
12
|
+
allow_ips: Annotated[list[str],
|
|
13
|
+
Doc("Set IPv4 or IPv6 addresses or networks to allow user to access")] = ListField(null=False,
|
|
14
|
+
default=["127.0.0.1"])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# define function to control MongoDB document event
|
|
18
|
+
# please follow the procedure below
|
|
19
|
+
# create event handler function with name starting with _.
|
|
20
|
+
# event handler function must have 3 args: sender, document, **kwargs)
|
|
21
|
+
# each db column can get from 'document'
|
|
22
|
+
# import pre_init or pre_save from 'mongoengine.signals' in your model file.
|
|
23
|
+
# pre_init.connect(EVNET_HANDLER_FUNC_NAME, sender=TABLE_CLASS_NAME)
|
|
24
|
+
def _convert_ips_to_str(sender, document, **kwargs) -> None:
|
|
25
|
+
if document.allow_ip:
|
|
26
|
+
document.allow_ip = [ str(ip) for ip in document.allow_ip ]
|
|
27
|
+
return None
|