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.
Files changed (52) hide show
  1. fastapi_clean_archi-0.6.5/LICENSE +21 -0
  2. fastapi_clean_archi-0.6.5/MANIFEST.in +1 -0
  3. fastapi_clean_archi-0.6.5/PKG-INFO +88 -0
  4. fastapi_clean_archi-0.6.5/README.md +44 -0
  5. fastapi_clean_archi-0.6.5/fastapi_clean_archi/__init__.py +0 -0
  6. fastapi_clean_archi-0.6.5/fastapi_clean_archi/cli.py +59 -0
  7. fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/__init__.py +0 -0
  8. fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/auth.py +14 -0
  9. fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/commons/__init__.py +0 -0
  10. fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/commons/repository.py +16 -0
  11. fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/commons/service.py +3 -0
  12. fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/config.py +23 -0
  13. fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/db/__init__.py +0 -0
  14. fastapi_clean_archi-0.6.5/fastapi_clean_archi/core/db/base.py +40 -0
  15. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/__init__.py +0 -0
  16. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/__init__.py +1 -0
  17. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/add_module.py +28 -0
  18. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/base.py +41 -0
  19. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/makemigrations.py +11 -0
  20. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/migrate.py +9 -0
  21. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/runserver.py +20 -0
  22. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/commands/test.py +30 -0
  23. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/__init__.py +0 -0
  24. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/__init__.tmpl +0 -0
  25. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/__init__.tmpl +0 -0
  26. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/application/__init__.tmpl +0 -0
  27. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/application/service.tmpl +5 -0
  28. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/domain/__init__.tmpl +0 -0
  29. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/domain/entity.tmpl +6 -0
  30. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/infrastructure/__init__.tmpl +0 -0
  31. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/infrastructure/models.tmpl +5 -0
  32. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/infrastructure/repository.tmpl +7 -0
  33. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/interfaces/__init__.tmpl +0 -0
  34. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/interfaces/controller.tmpl +3 -0
  35. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/interfaces/schemas.tmpl +1 -0
  36. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/tests/__init__.tmpl +0 -0
  37. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/app_module/tests/test.tmpl +9 -0
  38. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/core/__init__.tmpl +0 -0
  39. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/core/config.tmpl +30 -0
  40. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/core/jwt_util.tmpl +45 -0
  41. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/core/session.tmpl +17 -0
  42. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/main.tmpl +20 -0
  43. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/manage.tmpl +35 -0
  44. fastapi_clean_archi-0.6.5/fastapi_clean_archi/managements/templates/pytest.tmpl +8 -0
  45. fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/PKG-INFO +88 -0
  46. fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/SOURCES.txt +50 -0
  47. fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/dependency_links.txt +1 -0
  48. fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/entry_points.txt +2 -0
  49. fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/requires.txt +11 -0
  50. fastapi_clean_archi-0.6.5/fastapi_clean_archi.egg-info/top_level.txt +1 -0
  51. fastapi_clean_archi-0.6.5/pyproject.toml +35 -0
  52. 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
+
@@ -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)
@@ -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)
@@ -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,3 @@
1
+ class Service:
2
+ def __init__(self, repository):
3
+ self.repository = repository
@@ -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")
@@ -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
@@ -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,9 @@
1
+ from fastapi_clean_archi.managements.commands.base import run_alembic, Command
2
+
3
+
4
+ class Migrate(Command):
5
+ name = "migrate"
6
+ help = "db migration 을 진행합니다."
7
+
8
+ def execute(self):
9
+ run_alembic("alembic upgrade head")
@@ -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}\"")
@@ -0,0 +1,5 @@
1
+ from fastapi_clean_archi.core.commons.service import Service
2
+
3
+
4
+ class {module_class}Service(Service):
5
+ pass
@@ -0,0 +1,6 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class {module_class}Entity:
6
+ pk: int
@@ -0,0 +1,5 @@
1
+ from fastapi_clean_archi.core.db.base import BaseModel
2
+
3
+
4
+ class {module_class}(BaseModel):
5
+ pass
@@ -0,0 +1,7 @@
1
+ from fastapi_clean_archi.core.commons.repository import Repository
2
+
3
+ from app.modules.{module_name}.infrastructure.models import {module_class}
4
+
5
+
6
+ class {module_class}Repository(Repository):
7
+ DB_MODEL = {module_class}
@@ -0,0 +1,3 @@
1
+ from fastapi import APIRouter
2
+
3
+ router = APIRouter(prefix="", tags=[])
@@ -0,0 +1,9 @@
1
+ from fastapi.testclient import TestClient
2
+
3
+ from main import app
4
+
5
+ client = TestClient(app)
6
+
7
+
8
+ def test_is_true():
9
+ assert 1 == 1
@@ -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,8 @@
1
+ [pytest]
2
+ addopts = -ra -vv
3
+
4
+ testpaths = app/modules tests
5
+
6
+ python_files = test_*.py test.py *_test.py
7
+ python_classes = Test*
8
+ python_functions = test_*
@@ -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,2 @@
1
+ [console_scripts]
2
+ fastapi-setup = fastapi_clean_archi.cli:create_core
@@ -0,0 +1,11 @@
1
+ fastapi>=0.121.3
2
+ passlib
3
+ uvicorn
4
+ SQLAlchemy
5
+ PyJWT
6
+ pytest
7
+ alembic
8
+ python-dotenv
9
+ bcrypt==4.0.1
10
+ typer
11
+ httpx
@@ -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"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+