fastapi-clean-archi 0.6.5__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.
- fastapi_clean_archi-0.6.5/LICENSE +21 -0
- fastapi_clean_archi-0.6.5/MANIFEST.in +1 -0
- fastapi_clean_archi-0.6.5/PKG-INFO +88 -0
- fastapi_clean_archi-0.6.5/README.md +44 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/__init__.py +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/cli.py +59 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/__init__.py +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/auth.py +14 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/commons/__init__.py +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/commons/repository.py +16 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/commons/service.py +3 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/config.py +23 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/db/__init__.py +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/db/base.py +40 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/__init__.py +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/__init__.py +1 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/add_module.py +28 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/base.py +41 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/makemigrations.py +11 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/migrate.py +9 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/runserver.py +20 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/test.py +30 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/__init__.py +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/__init__.tmpl +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/__init__.tmpl +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/application/__init__.tmpl +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/application/service.tmpl +5 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/domain/__init__.tmpl +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/domain/entity.tmpl +6 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/infrastructure/__init__.tmpl +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/infrastructure/models.tmpl +5 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/infrastructure/repository.tmpl +7 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/interfaces/__init__.tmpl +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/interfaces/controller.tmpl +3 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/interfaces/schemas.tmpl +1 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/tests/__init__.tmpl +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/tests/test.tmpl +9 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/core/__init__.tmpl +0 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/core/config.tmpl +30 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/core/jwt_util.tmpl +45 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/core/session.tmpl +17 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/main.tmpl +20 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/manage.tmpl +35 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/pytest.tmpl +8 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/PKG-INFO +88 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/SOURCES.txt +50 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/dependency_links.txt +1 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/entry_points.txt +2 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/requires.txt +11 -0
- fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/top_level.txt +1 -0
- fastapi_clean_archi-0.6.5/pyproject.toml +35 -0
- fastapi_clean_archi-0.6.5/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 YuJeonghoon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recursive-include fastapi_clean_archi/managements/templates *.tmpl
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-clean-archi
|
|
3
|
+
Version: 0.6.5
|
|
4
|
+
Summary: FastAPI with clean architecture
|
|
5
|
+
Author-email: YuJeonghoon <moning02004@naver.com>
|
|
6
|
+
License: The MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 YuJeonghoon
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in
|
|
18
|
+
all copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
26
|
+
THE SOFTWARE.
|
|
27
|
+
Project-URL: Homepage, https://github.com/moning02004/fastapi-clean-archi
|
|
28
|
+
Project-URL: Bug Tracker, https://github.com/moning02004/fastapi-clean-archi/issues
|
|
29
|
+
Requires-Python: >=3.8
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Requires-Dist: fastapi>=0.121.3
|
|
33
|
+
Requires-Dist: passlib
|
|
34
|
+
Requires-Dist: uvicorn
|
|
35
|
+
Requires-Dist: SQLAlchemy
|
|
36
|
+
Requires-Dist: PyJWT
|
|
37
|
+
Requires-Dist: pytest
|
|
38
|
+
Requires-Dist: alembic
|
|
39
|
+
Requires-Dist: python-dotenv
|
|
40
|
+
Requires-Dist: bcrypt==4.0.1
|
|
41
|
+
Requires-Dist: typer
|
|
42
|
+
Requires-Dist: httpx
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
## FastAPI with Clean-Architecture
|
|
46
|
+
> 클린 아키텍처 구조를 적용한 FastAPI.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
### Clean Architecture 구조 및 역할
|
|
50
|
+
#### 흐름
|
|
51
|
+
> interfaces -> application -> domain -> infrastructure
|
|
52
|
+
|
|
53
|
+
#### 설명
|
|
54
|
+
- 클린 아키텍처의 핵심은 관심사의 분리로 유지보수와 확장성을 높일 수 있다.
|
|
55
|
+
|
|
56
|
+
| Layer | Description |
|
|
57
|
+
|-------------|-----------------------------------------|
|
|
58
|
+
| Interface | 사용자/외부 입력을 Application의 유즈케이스에 연결하는 레이어 |
|
|
59
|
+
| Application | 사용자 시나리오(usecase) 및 비즈니스 로직을 실행하는 레이어 |
|
|
60
|
+
| Domain | 핵심 비즈니스 규칙 및 엔티티를 정의하는 레이어 |
|
|
61
|
+
| Infrastructure | 외부 시스템\(DB, API 등\)과의 연동 및 구현을 담당하는 레이어 |
|
|
62
|
+
|
|
63
|
+
**예시** _by github copilot_
|
|
64
|
+
- **Interface**: 사용자가 회원가입 폼을 제출하면, API 엔드포인트에서 입력 데이터를 받아 Application 레이어로 전달합니다.
|
|
65
|
+
- **Application**: 회원가입 유즈케이스를 실행하며, 입력값 검증, 비밀번호 암호화, 도메인 객체 생성, 트랜잭션 관리 등을 처리합니다.
|
|
66
|
+
- **Domain**: `User` 엔티티와 회원가입 관련 비즈니스 규칙(예: 이메일 중복 체크, 비밀번호 정책 등)을 정의합니다.
|
|
67
|
+
- **Infrastructure**: DB에 사용자 정보를 저장하고, 이메일 인증을 위해 외부 메일 서비스와 연동합니다.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### 기본 사용법
|
|
73
|
+
- fastapi-setup 으로 프로젝트 초기화할 수 있습니다.
|
|
74
|
+
```bash
|
|
75
|
+
fastapi-setup
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- --help 를 통해 명령어를 확인할 수 있습니다.
|
|
79
|
+
```bash
|
|
80
|
+
python3 manage.py --help
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 커스텀 명령어 추가 방법
|
|
84
|
+
- `{PROJECT_DIR}/managements/commands/` 아래 Command 를 상속받아 명령어를 추가할 수 있습니다.
|
|
85
|
+
```bash
|
|
86
|
+
python3 manage.py {command_name}
|
|
87
|
+
```
|
|
88
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
## FastAPI with Clean-Architecture
|
|
2
|
+
> 클린 아키텍처 구조를 적용한 FastAPI.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
### Clean Architecture 구조 및 역할
|
|
6
|
+
#### 흐름
|
|
7
|
+
> interfaces -> application -> domain -> infrastructure
|
|
8
|
+
|
|
9
|
+
#### 설명
|
|
10
|
+
- 클린 아키텍처의 핵심은 관심사의 분리로 유지보수와 확장성을 높일 수 있다.
|
|
11
|
+
|
|
12
|
+
| Layer | Description |
|
|
13
|
+
|-------------|-----------------------------------------|
|
|
14
|
+
| Interface | 사용자/외부 입력을 Application의 유즈케이스에 연결하는 레이어 |
|
|
15
|
+
| Application | 사용자 시나리오(usecase) 및 비즈니스 로직을 실행하는 레이어 |
|
|
16
|
+
| Domain | 핵심 비즈니스 규칙 및 엔티티를 정의하는 레이어 |
|
|
17
|
+
| Infrastructure | 외부 시스템\(DB, API 등\)과의 연동 및 구현을 담당하는 레이어 |
|
|
18
|
+
|
|
19
|
+
**예시** _by github copilot_
|
|
20
|
+
- **Interface**: 사용자가 회원가입 폼을 제출하면, API 엔드포인트에서 입력 데이터를 받아 Application 레이어로 전달합니다.
|
|
21
|
+
- **Application**: 회원가입 유즈케이스를 실행하며, 입력값 검증, 비밀번호 암호화, 도메인 객체 생성, 트랜잭션 관리 등을 처리합니다.
|
|
22
|
+
- **Domain**: `User` 엔티티와 회원가입 관련 비즈니스 규칙(예: 이메일 중복 체크, 비밀번호 정책 등)을 정의합니다.
|
|
23
|
+
- **Infrastructure**: DB에 사용자 정보를 저장하고, 이메일 인증을 위해 외부 메일 서비스와 연동합니다.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
### 기본 사용법
|
|
29
|
+
- fastapi-setup 으로 프로젝트 초기화할 수 있습니다.
|
|
30
|
+
```bash
|
|
31
|
+
fastapi-setup
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- --help 를 통해 명령어를 확인할 수 있습니다.
|
|
35
|
+
```bash
|
|
36
|
+
python3 manage.py --help
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 커스텀 명령어 추가 방법
|
|
40
|
+
- `{PROJECT_DIR}/managements/commands/` 아래 Command 를 상속받아 명령어를 추가할 수 있습니다.
|
|
41
|
+
```bash
|
|
42
|
+
python3 manage.py {command_name}
|
|
43
|
+
```
|
|
44
|
+
|
|
File without changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import re
|
|
3
|
+
import secrets
|
|
4
|
+
from importlib import resources
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from fastapi_clean_archi.managements.commands.base import copy_files, copy_file, run_alembic
|
|
8
|
+
|
|
9
|
+
template_dir = resources.files("fastapi_clean_archi.managements.templates")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def edit_file(filename, remove_line_words, add_lines, line_number):
|
|
13
|
+
path = Path(filename)
|
|
14
|
+
lines = path.read_text().splitlines()
|
|
15
|
+
|
|
16
|
+
for index in range(len(lines)):
|
|
17
|
+
for x in remove_line_words:
|
|
18
|
+
if lines[index].startswith(x):
|
|
19
|
+
lines[index] = ""
|
|
20
|
+
path.write_text("\n".join(lines[:line_number] + add_lines + lines[line_number:]))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_core():
|
|
24
|
+
secret_key = secrets.token_urlsafe(32)
|
|
25
|
+
copy_files(source_dir=Path(f"{template_dir}/core"),
|
|
26
|
+
target_dir=Path("./app/core"),
|
|
27
|
+
secret_key=secret_key)
|
|
28
|
+
|
|
29
|
+
copy_file(file_path=Path(f"{template_dir}/__init__.tmpl"),
|
|
30
|
+
new_file_path=Path("./app/__init__.py"))
|
|
31
|
+
|
|
32
|
+
copy_file(file_path=Path(f"{template_dir}/main.tmpl"),
|
|
33
|
+
new_file_path=Path("./main.py"))
|
|
34
|
+
|
|
35
|
+
copy_file(file_path=Path(f"{template_dir}/pytest.tmpl"),
|
|
36
|
+
new_file_path=Path("./pytest.ini"))
|
|
37
|
+
|
|
38
|
+
copy_file(file_path=Path(f"{template_dir}/manage.tmpl"),
|
|
39
|
+
new_file_path=Path("./manage.py"))
|
|
40
|
+
run_alembic("alembic init migrations")
|
|
41
|
+
|
|
42
|
+
edit_file("./alembic.ini", remove_line_words=["sqlalchemy.url"], add_lines=[], line_number=0)
|
|
43
|
+
edit_file("./migrations/env.py", remove_line_words=["target_metadata = "], add_lines=[
|
|
44
|
+
"",
|
|
45
|
+
"from app.core.config import settings",
|
|
46
|
+
"config.set_main_option(\"sqlalchemy.url\", settings.DATABASE_URL)",
|
|
47
|
+
"",
|
|
48
|
+
"import os, importlib",
|
|
49
|
+
"modules = os.listdir(\"app/modules\")",
|
|
50
|
+
"for module in modules:",
|
|
51
|
+
" try:",
|
|
52
|
+
" importlib.import_module(f\"app.modules.{module}.infrastructure.models\")",
|
|
53
|
+
" except ModuleNotFoundError:",
|
|
54
|
+
" continue",
|
|
55
|
+
"",
|
|
56
|
+
"from fastapi_clean_archi.core.db.base import BaseModel",
|
|
57
|
+
"target_metadata = BaseModel.metadata"
|
|
58
|
+
"",
|
|
59
|
+
], line_number=19)
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from passlib.context import CryptContext
|
|
2
|
+
|
|
3
|
+
pwd_context = CryptContext(
|
|
4
|
+
schemes=["argon2", "bcrypt"],
|
|
5
|
+
deprecated="auto",
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def hash_password(password: str) -> str:
|
|
10
|
+
return pwd_context.hash(password)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
14
|
+
return pwd_context.verify(plain_password, hashed_password)
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from sqlalchemy.orm import Session
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Repository:
|
|
5
|
+
DB_MODEL = None
|
|
6
|
+
|
|
7
|
+
def __init__(self, db: Session):
|
|
8
|
+
self.db = db
|
|
9
|
+
|
|
10
|
+
def get_by_pk(self, pk: int | str):
|
|
11
|
+
instance = self.db.query(self.DB_MODEL).filter(self.DB_MODEL.pk == pk).first()
|
|
12
|
+
return instance
|
|
13
|
+
|
|
14
|
+
def get_by_hash_id(self, hash_id: str):
|
|
15
|
+
instance = self.db.query(self.DB_MODEL).filter(self.DB_MODEL.hash_id == hash_id).first()
|
|
16
|
+
return instance
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from pydantic.v1 import BaseSettings
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AbstractSettings(BaseSettings):
|
|
7
|
+
DATABASE = {
|
|
8
|
+
"driver": "sqlite",
|
|
9
|
+
"name": "sqlite.db",
|
|
10
|
+
"user": "",
|
|
11
|
+
"password": "",
|
|
12
|
+
"host": "",
|
|
13
|
+
"port": "",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
DATABASE_URL: str = (
|
|
17
|
+
f"{DATABASE['driver']}:///{DATABASE['name']}"
|
|
18
|
+
if DATABASE["driver"] == "sqlite"
|
|
19
|
+
else f"{DATABASE['driver']}://{DATABASE['user']}:{DATABASE['password']}@{DATABASE['host']}:{DATABASE['port']}/{DATABASE['name']}"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
class Config:
|
|
23
|
+
env_file = os.environ.get("SETTINGS_ENV", ".env")
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import Column, Integer, DateTime, func, String, event
|
|
4
|
+
from sqlalchemy.orm import as_declarative, declarative_base, declared_attr, Session
|
|
5
|
+
|
|
6
|
+
Base = declarative_base()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@as_declarative()
|
|
10
|
+
class BaseModel(Base):
|
|
11
|
+
__abstract__ = True
|
|
12
|
+
|
|
13
|
+
hash_id = Column(String(100), nullable=False, unique=True)
|
|
14
|
+
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
15
|
+
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
16
|
+
|
|
17
|
+
@declared_attr
|
|
18
|
+
def __tablename__(cls):
|
|
19
|
+
meta = getattr(cls, "Meta", None)
|
|
20
|
+
if meta and hasattr(meta, "db_table"):
|
|
21
|
+
return meta.db_table
|
|
22
|
+
return cls.__name__.lower()
|
|
23
|
+
|
|
24
|
+
@declared_attr
|
|
25
|
+
def pk(cls):
|
|
26
|
+
for attr in cls.__dict__.values():
|
|
27
|
+
if isinstance(attr, Column) and attr.primary_key:
|
|
28
|
+
return Column("id", Integer, index=True, unique=True, autoincrement=True)
|
|
29
|
+
return Column("id", Integer, primary_key=True, index=True, unique=True, autoincrement=True)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@event.listens_for(Session, "before_flush")
|
|
33
|
+
def generate_hash_id(session, flush_context, instances):
|
|
34
|
+
for instance in session.new:
|
|
35
|
+
if instance.hash_id is None:
|
|
36
|
+
if getattr(instance, "user_id"):
|
|
37
|
+
hash_id = str(uuid.uuid3(uuid.NAMESPACE_OID, f"{instance.user_id}_{instance.pk}").hex)
|
|
38
|
+
else:
|
|
39
|
+
hash_id = str(uuid.uuid4().hex)
|
|
40
|
+
instance.hash_id = hash_id
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import *
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from importlib import resources
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from fastapi_clean_archi.managements.commands.base import Command, copy_files, copy_file
|
|
8
|
+
|
|
9
|
+
template_dir = resources.files("fastapi_clean_archi.managements.templates")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AddModule(Command):
|
|
13
|
+
name = "add_module"
|
|
14
|
+
help = "구조화된 모듈을 추가합니다."
|
|
15
|
+
|
|
16
|
+
def execute(self, module_name=typer.Argument(...)):
|
|
17
|
+
class_name = module_name.capitalize()
|
|
18
|
+
module_name = module_name.lower()
|
|
19
|
+
|
|
20
|
+
copy_files(source_dir=Path(f"{template_dir}/app_module"),
|
|
21
|
+
target_dir=Path(f"./app/modules/{module_name}"),
|
|
22
|
+
module_name=module_name,
|
|
23
|
+
module_class=class_name)
|
|
24
|
+
|
|
25
|
+
if not os.path.exists("./app/modules/__init__.py"):
|
|
26
|
+
copy_file(file_path=Path(f"{template_dir}/__init__.tmpl"),
|
|
27
|
+
new_file_path=Path("./app/modules/__init__.py"))
|
|
28
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
from abc import ABC
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Command(ABC):
|
|
8
|
+
name: str = None
|
|
9
|
+
help: str = None
|
|
10
|
+
|
|
11
|
+
@abc.abstractmethod
|
|
12
|
+
def execute(self):
|
|
13
|
+
raise NotImplementedError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_alembic(alembic_command: str):
|
|
17
|
+
subprocess.run(alembic_command.split(" "))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def copy_file(file_path, new_file_path, **kwargs):
|
|
21
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
22
|
+
data = f.read().format(**kwargs)
|
|
23
|
+
|
|
24
|
+
with open(new_file_path, 'w', encoding='utf-8') as f:
|
|
25
|
+
f.write(data)
|
|
26
|
+
print(f"✅ Created {file_path}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def copy_files(source_dir, target_dir, **kwargs):
|
|
30
|
+
for file_path in source_dir.rglob('*'):
|
|
31
|
+
if file_path.is_file():
|
|
32
|
+
relative_path = file_path.relative_to(source_dir)
|
|
33
|
+
relative_path = relative_path.with_suffix(".py")
|
|
34
|
+
new_file_path = target_dir / relative_path
|
|
35
|
+
|
|
36
|
+
if os.path.isfile(new_file_path):
|
|
37
|
+
print(f"⚠️ {file_path} already exists, skipping")
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
new_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
copy_file(file_path, new_file_path, **kwargs)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from fastapi_clean_archi.managements.commands.base import Command, run_alembic
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MakeMigrations(Command):
|
|
7
|
+
name = "makemigrations"
|
|
8
|
+
help = "db migration 파일을 생성합니다."
|
|
9
|
+
|
|
10
|
+
def execute(self, message=typer.Argument("auto_migration")):
|
|
11
|
+
run_alembic(f"alembic revision --autogenerate -m {message}")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
import uvicorn
|
|
5
|
+
|
|
6
|
+
from fastapi_clean_archi.managements.commands.base import Command
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Runserver(Command):
|
|
10
|
+
name = "runserver"
|
|
11
|
+
help = "FastAPI server 가 실행됩니다."
|
|
12
|
+
|
|
13
|
+
def execute(self, host_port=typer.Argument(None), host=typer.Argument("localhost"), port=typer.Argument("8000"), settings_env=typer.Argument(".env")):
|
|
14
|
+
if host_port:
|
|
15
|
+
host, port = host_port.split(":")
|
|
16
|
+
else:
|
|
17
|
+
host, port = host, port
|
|
18
|
+
|
|
19
|
+
os.environ.setdefault("SETTINGS_ENV", settings_env)
|
|
20
|
+
uvicorn.run("main:app", host=host, port=int(port), reload=True)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from fastapi_clean_archi.managements.commands.base import Command
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Test(Command):
|
|
9
|
+
name = "test"
|
|
10
|
+
help = "테스트를 실행합니다."
|
|
11
|
+
|
|
12
|
+
def execute(self,
|
|
13
|
+
module_name=typer.Argument("", help="테스트 대상 모듈 이름"),
|
|
14
|
+
test_name=typer.Argument("", help="테스트 파일 및 함수 이름. ex) filename, filename::function_name 형식으로 입력")):
|
|
15
|
+
if not module_name and test_name:
|
|
16
|
+
raise typer.BadParameter("모듈 이름이 필요합니다.")
|
|
17
|
+
|
|
18
|
+
specific_params = ""
|
|
19
|
+
if test_name:
|
|
20
|
+
test_filename, *test_function_name = test_name.split("::")
|
|
21
|
+
if not test_filename.endswith(".py"):
|
|
22
|
+
test_filename = f"{test_filename}.py"
|
|
23
|
+
test_function_name = test_function_name and test_function_name[0]
|
|
24
|
+
specific_params = f"{test_filename}::{test_function_name}" if test_function_name else test_filename
|
|
25
|
+
|
|
26
|
+
params = f"app/modules/{module_name}/tests/{specific_params}" if module_name else ""
|
|
27
|
+
|
|
28
|
+
command = f"pytest {params}".strip()
|
|
29
|
+
subprocess.run(command, shell=True)
|
|
30
|
+
print(f"Pytest Command is \"{command}\"")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/domain/__init__.tmpl
ADDED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import pydantic
|
fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/tests/__init__.tmpl
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from fastapi_clean_archi.core.config import AbstractSettings
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Settings(AbstractSettings):
|
|
5
|
+
APP_NAME: str = "FastAPI clean architecture"
|
|
6
|
+
|
|
7
|
+
# JWT settings
|
|
8
|
+
ALGORITHM: str = "HS256"
|
|
9
|
+
ACCESS_TOKEN_EXPIRE_MINUTES: str = "1D"
|
|
10
|
+
REFRESH_TOKEN_EXPIRE_MINUTES: str = "30D"
|
|
11
|
+
|
|
12
|
+
# Application settings
|
|
13
|
+
DEBUG: bool = True
|
|
14
|
+
SECRET_KEY: str = "{secret_key}"
|
|
15
|
+
|
|
16
|
+
# Database settings
|
|
17
|
+
DATABASE = {{
|
|
18
|
+
"driver": "sqlite",
|
|
19
|
+
"name": "sqlite.db",
|
|
20
|
+
"user": "",
|
|
21
|
+
"password": "",
|
|
22
|
+
"host": "",
|
|
23
|
+
"port": "",
|
|
24
|
+
}}
|
|
25
|
+
|
|
26
|
+
# CORS settings
|
|
27
|
+
CORS_ORIGINS = []
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
settings = Settings()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
|
+
|
|
4
|
+
from app.core.config import settings
|
|
5
|
+
import jwt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JWTManager:
|
|
9
|
+
current_time = None
|
|
10
|
+
|
|
11
|
+
def get_payload(self, data, value):
|
|
12
|
+
payload = deepcopy(data)
|
|
13
|
+
number, _type = value[:-1], value[-1]
|
|
14
|
+
_type = _type.upper()
|
|
15
|
+
|
|
16
|
+
if _type == "D":
|
|
17
|
+
timedelta_params = {{"days": int(number)}}
|
|
18
|
+
elif _type == "M":
|
|
19
|
+
timedelta_params = {{"minutes": int(number)}}
|
|
20
|
+
elif _type == "S":
|
|
21
|
+
timedelta_params = {{"seconds": int(number)}}
|
|
22
|
+
else:
|
|
23
|
+
timedelta_params = {{"seconds": 3600}}
|
|
24
|
+
expired_at = self.current_time + timedelta(**timedelta_params)
|
|
25
|
+
|
|
26
|
+
payload.update({{"exp": int(expired_at.timestamp())}})
|
|
27
|
+
payload.update({{"iat": int(self.current_time.timestamp())}})
|
|
28
|
+
return payload
|
|
29
|
+
|
|
30
|
+
def create(self, data):
|
|
31
|
+
self.current_time = datetime.now()
|
|
32
|
+
|
|
33
|
+
access_payload = self.get_payload(data, settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
34
|
+
refresh_payload = self.get_payload(data, settings.REFRESH_TOKEN_EXPIRE_MINUTES)
|
|
35
|
+
|
|
36
|
+
return {{
|
|
37
|
+
"access_token": jwt.encode(access_payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM),
|
|
38
|
+
"refresh_token": jwt.encode(refresh_payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
|
39
|
+
}}
|
|
40
|
+
|
|
41
|
+
def decode_payload(self, token: str) -> dict:
|
|
42
|
+
return jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
jwt_manager = JWTManager()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from sqlalchemy import create_engine
|
|
2
|
+
from sqlalchemy.orm import sessionmaker
|
|
3
|
+
|
|
4
|
+
from app.core.config import settings
|
|
5
|
+
|
|
6
|
+
engine = create_engine(settings.DATABASE_URL)
|
|
7
|
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_db():
|
|
11
|
+
db = SessionLocal()
|
|
12
|
+
try:
|
|
13
|
+
yield db
|
|
14
|
+
finally:
|
|
15
|
+
db.close()
|
|
16
|
+
|
|
17
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from fastapi import FastAPI, Depends
|
|
2
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
3
|
+
from fastapi.security import APIKeyHeader
|
|
4
|
+
|
|
5
|
+
from app.core.config import settings
|
|
6
|
+
|
|
7
|
+
auth_header = APIKeyHeader(name="Authorization", auto_error=False)
|
|
8
|
+
app = FastAPI(
|
|
9
|
+
dependencies=[Depends(auth_header)]
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
origins = []
|
|
13
|
+
|
|
14
|
+
app.add_middleware(
|
|
15
|
+
CORSMiddleware,
|
|
16
|
+
allow_origins=settings.CORS_ORIGINS,
|
|
17
|
+
allow_credentials=True,
|
|
18
|
+
allow_methods=["*"],
|
|
19
|
+
allow_headers=["*"],
|
|
20
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import pkgutil
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from fastapi_clean_archi.managements.commands.base import Command
|
|
6
|
+
|
|
7
|
+
command_app = typer.Typer()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def auto_register_commands():
|
|
11
|
+
import fastapi_clean_archi.managements.commands as commands
|
|
12
|
+
for _, mod, _ in pkgutil.iter_modules(commands.__path__):
|
|
13
|
+
importlib.import_module(f"fastapi_clean_archi.managements.commands.{{mod}}")
|
|
14
|
+
for cls in Command.__subclasses__():
|
|
15
|
+
command_class = cls()
|
|
16
|
+
command_app.command(command_class.name)(command_class.execute)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def register_custom_commands():
|
|
20
|
+
try:
|
|
21
|
+
import managements.commands as commands
|
|
22
|
+
except ImportError:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
for _, mod, _ in pkgutil.iter_modules(commands.__path__):
|
|
26
|
+
importlib.import_module(f"managements.commands.{{mod}}")
|
|
27
|
+
for cls in Command.__subclasses__():
|
|
28
|
+
command_class = cls()
|
|
29
|
+
command_app.command(command_class.name)(command_class.execute)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
auto_register_commands()
|
|
34
|
+
register_custom_commands()
|
|
35
|
+
command_app()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-clean-archi
|
|
3
|
+
Version: 0.6.5
|
|
4
|
+
Summary: FastAPI with clean architecture
|
|
5
|
+
Author-email: YuJeonghoon <moning02004@naver.com>
|
|
6
|
+
License: The MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 YuJeonghoon
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in
|
|
18
|
+
all copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
26
|
+
THE SOFTWARE.
|
|
27
|
+
Project-URL: Homepage, https://github.com/moning02004/fastapi-clean-archi
|
|
28
|
+
Project-URL: Bug Tracker, https://github.com/moning02004/fastapi-clean-archi/issues
|
|
29
|
+
Requires-Python: >=3.8
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Requires-Dist: fastapi>=0.121.3
|
|
33
|
+
Requires-Dist: passlib
|
|
34
|
+
Requires-Dist: uvicorn
|
|
35
|
+
Requires-Dist: SQLAlchemy
|
|
36
|
+
Requires-Dist: PyJWT
|
|
37
|
+
Requires-Dist: pytest
|
|
38
|
+
Requires-Dist: alembic
|
|
39
|
+
Requires-Dist: python-dotenv
|
|
40
|
+
Requires-Dist: bcrypt==4.0.1
|
|
41
|
+
Requires-Dist: typer
|
|
42
|
+
Requires-Dist: httpx
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
## FastAPI with Clean-Architecture
|
|
46
|
+
> 클린 아키텍처 구조를 적용한 FastAPI.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
### Clean Architecture 구조 및 역할
|
|
50
|
+
#### 흐름
|
|
51
|
+
> interfaces -> application -> domain -> infrastructure
|
|
52
|
+
|
|
53
|
+
#### 설명
|
|
54
|
+
- 클린 아키텍처의 핵심은 관심사의 분리로 유지보수와 확장성을 높일 수 있다.
|
|
55
|
+
|
|
56
|
+
| Layer | Description |
|
|
57
|
+
|-------------|-----------------------------------------|
|
|
58
|
+
| Interface | 사용자/외부 입력을 Application의 유즈케이스에 연결하는 레이어 |
|
|
59
|
+
| Application | 사용자 시나리오(usecase) 및 비즈니스 로직을 실행하는 레이어 |
|
|
60
|
+
| Domain | 핵심 비즈니스 규칙 및 엔티티를 정의하는 레이어 |
|
|
61
|
+
| Infrastructure | 외부 시스템\(DB, API 등\)과의 연동 및 구현을 담당하는 레이어 |
|
|
62
|
+
|
|
63
|
+
**예시** _by github copilot_
|
|
64
|
+
- **Interface**: 사용자가 회원가입 폼을 제출하면, API 엔드포인트에서 입력 데이터를 받아 Application 레이어로 전달합니다.
|
|
65
|
+
- **Application**: 회원가입 유즈케이스를 실행하며, 입력값 검증, 비밀번호 암호화, 도메인 객체 생성, 트랜잭션 관리 등을 처리합니다.
|
|
66
|
+
- **Domain**: `User` 엔티티와 회원가입 관련 비즈니스 규칙(예: 이메일 중복 체크, 비밀번호 정책 등)을 정의합니다.
|
|
67
|
+
- **Infrastructure**: DB에 사용자 정보를 저장하고, 이메일 인증을 위해 외부 메일 서비스와 연동합니다.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### 기본 사용법
|
|
73
|
+
- fastapi-setup 으로 프로젝트 초기화할 수 있습니다.
|
|
74
|
+
```bash
|
|
75
|
+
fastapi-setup
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- --help 를 통해 명령어를 확인할 수 있습니다.
|
|
79
|
+
```bash
|
|
80
|
+
python3 manage.py --help
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 커스텀 명령어 추가 방법
|
|
84
|
+
- `{PROJECT_DIR}/managements/commands/` 아래 Command 를 상속받아 명령어를 추가할 수 있습니다.
|
|
85
|
+
```bash
|
|
86
|
+
python3 manage.py {command_name}
|
|
87
|
+
```
|
|
88
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
fastapi_clean_archi/__init__.py
|
|
6
|
+
fastapi_clean_archi/cli.py
|
|
7
|
+
fastapi_clean_archi.egg-info/PKG-INFO
|
|
8
|
+
fastapi_clean_archi.egg-info/SOURCES.txt
|
|
9
|
+
fastapi_clean_archi.egg-info/dependency_links.txt
|
|
10
|
+
fastapi_clean_archi.egg-info/entry_points.txt
|
|
11
|
+
fastapi_clean_archi.egg-info/requires.txt
|
|
12
|
+
fastapi_clean_archi.egg-info/top_level.txt
|
|
13
|
+
fastapi_clean_archi/core/__init__.py
|
|
14
|
+
fastapi_clean_archi/core/auth.py
|
|
15
|
+
fastapi_clean_archi/core/config.py
|
|
16
|
+
fastapi_clean_archi/core/commons/__init__.py
|
|
17
|
+
fastapi_clean_archi/core/commons/repository.py
|
|
18
|
+
fastapi_clean_archi/core/commons/service.py
|
|
19
|
+
fastapi_clean_archi/core/db/__init__.py
|
|
20
|
+
fastapi_clean_archi/core/db/base.py
|
|
21
|
+
fastapi_clean_archi/managements/__init__.py
|
|
22
|
+
fastapi_clean_archi/managements/commands/__init__.py
|
|
23
|
+
fastapi_clean_archi/managements/commands/add_module.py
|
|
24
|
+
fastapi_clean_archi/managements/commands/base.py
|
|
25
|
+
fastapi_clean_archi/managements/commands/makemigrations.py
|
|
26
|
+
fastapi_clean_archi/managements/commands/migrate.py
|
|
27
|
+
fastapi_clean_archi/managements/commands/runserver.py
|
|
28
|
+
fastapi_clean_archi/managements/commands/test.py
|
|
29
|
+
fastapi_clean_archi/managements/templates/__init__.py
|
|
30
|
+
fastapi_clean_archi/managements/templates/__init__.tmpl
|
|
31
|
+
fastapi_clean_archi/managements/templates/main.tmpl
|
|
32
|
+
fastapi_clean_archi/managements/templates/manage.tmpl
|
|
33
|
+
fastapi_clean_archi/managements/templates/pytest.tmpl
|
|
34
|
+
fastapi_clean_archi/managements/templates/app_module/__init__.tmpl
|
|
35
|
+
fastapi_clean_archi/managements/templates/app_module/application/__init__.tmpl
|
|
36
|
+
fastapi_clean_archi/managements/templates/app_module/application/service.tmpl
|
|
37
|
+
fastapi_clean_archi/managements/templates/app_module/domain/__init__.tmpl
|
|
38
|
+
fastapi_clean_archi/managements/templates/app_module/domain/entity.tmpl
|
|
39
|
+
fastapi_clean_archi/managements/templates/app_module/infrastructure/__init__.tmpl
|
|
40
|
+
fastapi_clean_archi/managements/templates/app_module/infrastructure/models.tmpl
|
|
41
|
+
fastapi_clean_archi/managements/templates/app_module/infrastructure/repository.tmpl
|
|
42
|
+
fastapi_clean_archi/managements/templates/app_module/interfaces/__init__.tmpl
|
|
43
|
+
fastapi_clean_archi/managements/templates/app_module/interfaces/controller.tmpl
|
|
44
|
+
fastapi_clean_archi/managements/templates/app_module/interfaces/schemas.tmpl
|
|
45
|
+
fastapi_clean_archi/managements/templates/app_module/tests/__init__.tmpl
|
|
46
|
+
fastapi_clean_archi/managements/templates/app_module/tests/test.tmpl
|
|
47
|
+
fastapi_clean_archi/managements/templates/core/__init__.tmpl
|
|
48
|
+
fastapi_clean_archi/managements/templates/core/config.tmpl
|
|
49
|
+
fastapi_clean_archi/managements/templates/core/jwt_util.tmpl
|
|
50
|
+
fastapi_clean_archi/managements/templates/core/session.tmpl
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fastapi_clean_archi
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fastapi-clean-archi"
|
|
7
|
+
version = "0.6.5"
|
|
8
|
+
description = "FastAPI with clean architecture"
|
|
9
|
+
license = {file = "LICENSE"}
|
|
10
|
+
authors = [{name="YuJeonghoon", email="moning02004@naver.com"}]
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"fastapi>=0.121.3",
|
|
15
|
+
"passlib",
|
|
16
|
+
"uvicorn",
|
|
17
|
+
"SQLAlchemy",
|
|
18
|
+
"PyJWT",
|
|
19
|
+
"pytest",
|
|
20
|
+
"alembic",
|
|
21
|
+
"python-dotenv",
|
|
22
|
+
"bcrypt==4.0.1",
|
|
23
|
+
"typer",
|
|
24
|
+
"httpx"
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
fastapi-setup = "fastapi_clean_archi.cli:create_core"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
include = ["fastapi_clean_archi*"]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
"Homepage" = "https://github.com/moning02004/fastapi-clean-archi"
|
|
35
|
+
"Bug Tracker" = "https://github.com/moning02004/fastapi-clean-archi/issues"
|