jknife 0.0.1__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.
Files changed (36) hide show
  1. jknife-0.0.1/.gitignore +7 -0
  2. jknife-0.0.1/LICENSE +7 -0
  3. jknife-0.0.1/PKG-INFO +17 -0
  4. jknife-0.0.1/README.md +0 -0
  5. jknife-0.0.1/main.py +40 -0
  6. jknife-0.0.1/pyproject.toml +39 -0
  7. jknife-0.0.1/settings.py +161 -0
  8. jknife-0.0.1/src/__init__.py +10 -0
  9. jknife-0.0.1/src/jknife/__init__.py +0 -0
  10. jknife-0.0.1/src/jknife/commands/__init__.py +0 -0
  11. jknife-0.0.1/src/jknife/commands/jknife.py +376 -0
  12. jknife-0.0.1/src/jknife/db/__init__.py +142 -0
  13. jknife-0.0.1/src/jknife/db/models/__init__.py +0 -0
  14. jknife-0.0.1/src/jknife/db/models/mongo/__init__.py +57 -0
  15. jknife-0.0.1/src/jknife/db/models/mongo/network.py +27 -0
  16. jknife-0.0.1/src/jknife/db/models/mongo/personnel_info.py +108 -0
  17. jknife-0.0.1/src/jknife/db/models/mongo/settings.py +58 -0
  18. jknife-0.0.1/src/jknife/db/models/mongo/token.py +62 -0
  19. jknife-0.0.1/src/jknife/db/models/mongo/users.py +91 -0
  20. jknife-0.0.1/src/jknife/db/models/rdbms/__init__.py +53 -0
  21. jknife-0.0.1/src/jknife/db/models/rdbms/network.py +24 -0
  22. jknife-0.0.1/src/jknife/db/models/rdbms/personnel_info.py +88 -0
  23. jknife-0.0.1/src/jknife/db/models/rdbms/settings.py +39 -0
  24. jknife-0.0.1/src/jknife/db/models/rdbms/token.py +53 -0
  25. jknife-0.0.1/src/jknife/db/models/rdbms/users.py +94 -0
  26. jknife-0.0.1/src/jknife/dependencies/__init__.py +0 -0
  27. jknife-0.0.1/src/jknife/dependencies/token.py +202 -0
  28. jknife-0.0.1/src/jknife/dependencies/users.py +76 -0
  29. jknife-0.0.1/src/jknife/logging.py +69 -0
  30. jknife-0.0.1/src/jknife/views/__init__.py +23 -0
  31. jknife-0.0.1/src/jknife/views/error_message.py +38 -0
  32. jknife-0.0.1/src/jknife/views/personnel_info.py +42 -0
  33. jknife-0.0.1/src/jknife/views/tokens.py +22 -0
  34. jknife-0.0.1/src/jknife/views/users.py +70 -0
  35. jknife-0.0.1/src/settings_loader.py +37 -0
  36. jknife-0.0.1/tests/__init__.py +0 -0
@@ -0,0 +1,7 @@
1
+ .idea
2
+ .venv
3
+ *.db
4
+ *__pycache__
5
+ */__pycache__
6
+ logs/*.log
7
+ *.txt
jknife-0.0.1/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright <YEAR> <COPYRIGHT HOLDER>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
jknife-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: jknife
3
+ Version: 0.0.1
4
+ Summary: Custom FastAPI for luna-negra
5
+ Author: luna-negra
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: Backend,Customised,FastAPI,Python,pypi
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Dist: blinker>=1.9.0
12
+ Requires-Dist: fastapi[standard]>=0.166.1
13
+ Requires-Dist: mongoengine
14
+ Requires-Dist: pycountry>=24.6.1
15
+ Requires-Dist: pyjwt>=2.10.1
16
+ Requires-Dist: redis>=6.4.0
17
+ Requires-Dist: sqlmodel>=0.0.24
jknife-0.0.1/README.md ADDED
File without changes
jknife-0.0.1/main.py ADDED
@@ -0,0 +1,40 @@
1
+ # import modules from python library
2
+ import importlib, os
3
+ from fastapi import FastAPI, Request, status
4
+ from fastapi.responses import JSONResponse
5
+ from fastapi.exceptions import RequestValidationError
6
+
7
+
8
+ # import required objects from projects
9
+ from src.jknife.db import DBConnector
10
+ from settings import API_VERSION
11
+
12
+
13
+ # set main url for all router
14
+ MAIN_URL: str = f"/api/{API_VERSION}"
15
+
16
+
17
+ # DB Connector
18
+ db_connector = DBConnector()
19
+
20
+ # start application
21
+ app = FastAPI(lifespan=db_connector.startup_db)
22
+
23
+
24
+ # add exception handler for ValidationException
25
+ @app.exception_handler(exc_class_or_status_code=RequestValidationError)
26
+ async def validation_error(req: Request, exc: RequestValidationError):
27
+ errors: dict = exc.errors()[0] if isinstance(exc.errors(), list) else dict(exc.errors())
28
+ result: dict = {"field": errors.get("input"), "msg": errors.get("msg")}
29
+ return JSONResponse(
30
+ status_code=status.HTTP_400_BAD_REQUEST,
31
+ content={"detail": result}
32
+ )
33
+
34
+
35
+ # add routers_bak
36
+ for router in os.listdir(os.path.abspath(path="routers_bak")):
37
+ if not router.startswith("__"):
38
+ tmp_module = importlib.import_module(name=f"routers_bak.{router.split('.')[0]}")
39
+ app.include_router(router=tmp_module.router,
40
+ prefix=MAIN_URL)
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "jknife"
7
+ version = "0.0.1"
8
+ authors = [
9
+ { name="luna-negra", email="" },
10
+ ]
11
+ description = "Custom FastAPI for luna-negra"
12
+ keywords = ["pypi", "Python", "FastAPI", "Backend", "Customised"]
13
+ readme = "README.md"
14
+ quires-python = ">=3.11"
15
+ dependencies = [
16
+ "blinker>=1.9.0",
17
+ "fastapi[standard]>=0.166.1",
18
+ "mongoengine",
19
+ "redis>=6.4.0",
20
+ "sqlmodel>=0.0.24",
21
+ "pycountry>=24.6.1",
22
+ "PyJWT>=2.10.1",
23
+ ]
24
+ classifiers = [
25
+ "Programming Language :: Python :: 3",
26
+ "Operating System :: OS Independent",
27
+ ]
28
+ license = "MIT"
29
+
30
+ [project.scripts]
31
+ jknife = "jknife.commands.jknife:main"
32
+
33
+ [prject.ulrs]
34
+ Homepage = "https://example.com"
35
+ DOWNLOAD = "https://example.com/abc.tar.gz"
36
+ Documentation = "https://readthedocs.org"
37
+ Repository = "https://github.com/me/spam.git"
38
+ Issues = "https://github.com/me/spam/issues"
39
+ Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"
@@ -0,0 +1,161 @@
1
+ # this file is charge of config for custom fastapi.
2
+
3
+ from os import environ
4
+
5
+
6
+ # [ DEBUG ]
7
+ # this variable is a mode switcher.
8
+ # if you are about to publish your application to public, please turn this switch to False.
9
+ # on the other hands, during development, please switch this value to True.
10
+ # DEBUG_MODE provides
11
+ # - query logs on database.
12
+ DEBUG_MODE: bool = True
13
+
14
+ # [ API_VERSION ]
15
+ # API_VERSION is a management variables for your api.
16
+ # This value will be used to call api as an URL prefix. e.g) /api/{YOUR_API_VERSION}/
17
+ # you can modify the value of API_VERSION to whatever you want.
18
+ API_VERSION: str = "v1"
19
+
20
+ # [ DATABASE ]
21
+ # DATABASE is charge of connection to database server.
22
+ # recommend to store DB information in env var and get them by using environ.get()
23
+ # this framework supports some databases.
24
+ # - supported type: ["none", "postgresql", "mysql", "sqlite", "mongo"]
25
+ #
26
+ # type 'none': if you do not want to use database. it is a default if the type is None
27
+ # structure of database
28
+ # - type in ['postgresql', 'mysql', 'mongo']
29
+ # - key: "dbms"
30
+ # - values: {"username": DB_USERNAME, "password": DB_PASSWORD, "host": DB_HOST, "port": DB_PORT, "db": DB_NAME}
31
+ # - if you use mongo, it is possible to add key 'authSource' for name of database that is responsible for auth.
32
+ # - type 'sqlite'
33
+ # - sqlite_filepath: input path of sqlite3 database file in an absolute way or a relative one.
34
+ # "redis" exist for the Redis - cached db.
35
+ DATABASE: dict = {
36
+ "type": environ.get("db_type"),
37
+ "dbms": {
38
+ "username": environ.get("db_username"),
39
+ "password": environ.get("db_password"),
40
+ "host": environ.get("db_host"),
41
+ "port": int(environ.get("db_port")),
42
+ "db": environ.get("db_name"),
43
+ "authSource": environ.get("mongo_auth_source")
44
+ },
45
+ "sqlite": {
46
+ "sqlite_filepath": "./fastapi.db"
47
+ },
48
+ "redis": {
49
+ "host": environ.get("redis_host"),
50
+ "port": environ.get("redis_port"),
51
+ }
52
+ }
53
+
54
+ # [ AUTHENTICATION ]
55
+ # AUTHENTICATION is charge of JWT token for authenticated user.
56
+ # token: information for JWT token
57
+ # - token_issuer: token issuer
58
+ # - token_audience: IP or URL address that allows this token
59
+ # - token_valid_time: interval time between when the access token issued and expired in minutes.
60
+ # - token_refresh_time: interval time in minute for reissuing access token.
61
+ # - secret_key: secret key to encrypt token
62
+ # - algorithm: select encryption algorithm
63
+ # * HS: for HMAC symmetric algorithm
64
+ # * RS: for RSA symmetric algorithm
65
+ # * ES: for ECDSA asymmetric algorithm
66
+ AUTHENTICATION: dict = {
67
+ "token": {
68
+ "token_issuer": "http://127.0.0.1",
69
+ "token_audience": "http://127.0.0.1",
70
+ "token_valid_time": 20,
71
+ "token_refresh_time": 60 * 24 * 30,
72
+ "secret_key": "THIS IS A TEST",
73
+ "algorithm": "HS256"
74
+ }
75
+ }
76
+
77
+ # [ PASSWORD_POLICIES ]
78
+ # PASSWORD_POLICIES is charge of password compliance, when users are created or change their password
79
+ # compliance: the list of password policies
80
+ # - PASSWORD_MINLEN: limit the minimum length of password. default is 8 but it can be customized with 'min_length'.
81
+ # - PASSWORD_UPCHAR: password must contain at least one upper alphabet character.
82
+ # - PASSWORD_LOWCHAR: password must contain at least one lower alphabet character.
83
+ # - PASSWORD_NUMBER: password must contain at least one numeric character.
84
+ # - PASSWORD_SPECIAL: password must contain at least one special character.
85
+ # encrypt_type: algorithm for encrypting password. please refer to 'hashlib.algorithms_available'
86
+ # min_length: minimum length of password. it will be used for PASSWORD_MINLEN
87
+ PASSWORD_POLICIES: dict = {
88
+ "compliance": [
89
+ "PASSWORD_MINLEN",
90
+ "PASSWORD_UPCHAR",
91
+ "PASSWORD_LOWCHAR",
92
+ "PASSWORD_NUMBER",
93
+ "PASSWORD_SPECIAL"
94
+ ],
95
+ "encrypt_type": "sha256",
96
+ "min_length": 8
97
+ }
98
+
99
+
100
+ # [ LOG_SETTINGS ]
101
+ # LOG_SETTINGS is based on python dictConfig
102
+ # (https://docs.python.org/3/library/logging.config.html#dictionary-schema-details).
103
+ # Refer to the official documentation, write down your own config for logging.
104
+ # {"version": 1, "disable_existing_logger": False} will be applied automatically.
105
+ # If DEBUG_MODE is True, uvicorn log will be printed out on your console.
106
+ # Default Logger:
107
+ # * DB: The logger 'db' is charge of database logging.
108
+ # if you want to change the name of logger, you should also change the DB_LOGGER_LIST below.
109
+ # or you can not see any proper log.
110
+ LOG_SETTINGS: dict = {
111
+ "formatters": {
112
+ "default": {
113
+ "format": "[%(asctime)s] %(name)s:%(levelname)s - %(message)s",
114
+ "datefmt": "%Y-%m-%d %H:%M:%S:%s"
115
+ }
116
+ },
117
+ "handlers": {
118
+ "main": {
119
+ "class": "logging.handlers.RotatingFileHandler",
120
+ "level": "INFO",
121
+ "formatter": "default",
122
+ "filename": "logs/all_logs.log",
123
+ "maxBytes": 1024 * 1024 * 100, # 10 MB
124
+ "backupCount": 3
125
+ },
126
+ "db": {
127
+ "class": "logging.handlers.RotatingFileHandler",
128
+ "level": "INFO",
129
+ "formatter": "default",
130
+ "filename": "logs/db.log",
131
+ "maxBytes": 1024 * 1024 * 100, # 10 MB
132
+ "backupCount": 3
133
+ },
134
+ "db_error": {
135
+ "class": "logging.handlers.RotatingFileHandler",
136
+ "level": "ERROR",
137
+ "formatter": "default",
138
+ "filename": "logs/db_error.log",
139
+ "maxBytes": 1024 * 1024 * 100, # 10 MB
140
+ "backupCount": 3
141
+ }
142
+ },
143
+ "loggers": {
144
+ "": {
145
+ "level": "INFO",
146
+ "handlers": ["main"],
147
+ },
148
+ "db": {
149
+ "level": "INFO",
150
+ "handlers": ["db", "db_error"],
151
+ "propagate": True,
152
+ },
153
+ }
154
+ }
155
+
156
+ # [ LOGGER_LIST ]
157
+ # create your own logger instance with core.logging.LoggerMgmt and use it in specific realm.
158
+ DB_LOGGER_LIST = ("db",)
159
+
160
+
161
+ ### END CONTENTS ###
@@ -0,0 +1,10 @@
1
+ from .settings_loader import load_settings
2
+
3
+
4
+ # Load config when `mizuhara` is imported
5
+ try:
6
+ settings = load_settings()
7
+
8
+ # add exception handler to avoid exception during creating project or app
9
+ except FileNotFoundError:
10
+ pass
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()