├── ch06 ├── __init__.py ├── 6_3_usecase │ ├── __init__.py │ ├── fastapi.py │ ├── unit_test.py │ └── calculate_average_usecase.py ├── 6_4_gateway │ ├── __init__.py │ ├── README.md │ ├── dependency_injection.py │ ├── fastapi.py │ ├── presenter.py │ ├── controllers.py │ ├── repository.py │ └── usecase.py ├── 6_6_entity │ ├── __init__.py │ ├── infrastructure.py │ ├── fastapi.py │ ├── dependency_injection.py │ ├── presenter.py │ ├── repository.py │ ├── README.md │ ├── controllers.py │ ├── entity.py │ └── usecase.py ├── 6_1_framework │ ├── __init__.py │ ├── click │ │ ├── __init__.py │ │ └── main.py │ └── fastapi │ │ ├── __init__.py │ │ └── main.py ├── 6_2_controller │ ├── __init__.py │ ├── type1 │ │ ├── __init__.py │ │ ├── controller │ │ │ ├── __init__.py │ │ │ └── calculater_score_controller.py │ │ └── presentation │ │ │ ├── __init__.py │ │ │ ├── cli.py │ │ │ └── fastapi.py │ └── type2 │ │ ├── __init__.py │ │ └── presentation │ │ ├── __init__.py │ │ └── fastapi.py ├── 6_5_external │ ├── __init__.py │ ├── README.md │ ├── infrastructure.py │ ├── fastapi.py │ ├── presenter.py │ ├── repository.py │ ├── controllers.py │ ├── test_mocks.py │ ├── dependency_injection.py │ └── usecase.py └── 6_7_presenter │ ├── __init__.py │ ├── __pycache__ │ ├── views.cpython-313.pyc │ ├── entity.cpython-313.pyc │ ├── fastapi.cpython-313.pyc │ ├── usecase.cpython-313.pyc │ ├── __init__.cpython-313.pyc │ ├── presenter.cpython-313.pyc │ ├── repository.cpython-313.pyc │ ├── viewmodels.cpython-313.pyc │ ├── controllers.cpython-313.pyc │ ├── infrastructure.cpython-313.pyc │ └── dependency_injection.cpython-313.pyc │ ├── viewmodels.py │ ├── infrastructure.py │ ├── README.md │ ├── repository.py │ ├── views.py │ ├── fastapi.py │ ├── controllers.py │ ├── presenter.py │ ├── dependency_injection.py │ ├── entity.py │ └── usecase.py ├── ch07 ├── domain │ ├── __init__.py │ ├── value_objects │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── money.cpython-313.pyc │ │ │ ├── __init__.cpython-313.pyc │ │ │ └── order_status.cpython-313.pyc │ │ ├── order_status.py │ │ └── money.py │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ ├── entities │ │ ├── __pycache__ │ │ │ ├── order.cpython-313.pyc │ │ │ ├── __init__.cpython-313.pyc │ │ │ ├── coffee.cpython-313.pyc │ │ │ └── order_item.cpython-313.pyc │ │ ├── __init__.py │ │ ├── order_item.py │ │ ├── coffee.py │ │ └── order.py │ └── exceptions │ │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ │ └── __init__.py ├── tests │ ├── __init__.py │ ├── api │ │ └── __init__.py │ ├── unit │ │ ├── __init__.py │ │ ├── controller │ │ │ ├── __init__.py │ │ │ └── __pycache__ │ │ │ │ ├── __init__.cpython-313.pyc │ │ │ │ └── test_create_order_controller.cpython-313.pyc │ │ ├── entity │ │ │ ├── __init__.py │ │ │ ├── __pycache__ │ │ │ │ └── test_order.cpython-313.pyc │ │ │ └── test_order.py │ │ ├── usecase │ │ │ ├── __init__.py │ │ │ └── __pycache__ │ │ │ │ └── test_create_order_usecase.cpython-313.pyc │ │ └── __pycache__ │ │ │ └── __init__.cpython-313.pyc │ ├── scripts │ │ ├── run_gateway_test.sh │ │ └── run_unit_test.sh │ └── __pycache__ │ │ └── __init__.cpython-313.pyc ├── adapter │ ├── __init__.py │ ├── presenter │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-313.pyc │ │ │ └── create_order_presenter.cpython-313.pyc │ │ └── create_order_presenter.py │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ └── repository │ │ └── __pycache__ │ │ └── __init__.cpython-313.pyc ├── application │ ├── __init__.py │ ├── ports │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ └── __init__.cpython-313.pyc │ │ ├── inbound │ │ │ ├── __pycache__ │ │ │ │ └── __init__.cpython-313.pyc │ │ │ └── __init__.py │ │ ├── outbound │ │ │ ├── __pycache__ │ │ │ │ └── __init__.cpython-313.pyc │ │ │ └── __init__.py │ │ └── repository │ │ │ ├── __pycache__ │ │ │ └── __init__.cpython-313.pyc │ │ │ └── __init__.py │ ├── usecases │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-313.pyc │ │ │ └── create_order_usecase.cpython-313.pyc │ │ └── create_order_usecase.py │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ └── dtos │ │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ │ └── __init__.py ├── infrastructure │ ├── web │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── fastapi.cpython-313.pyc │ │ │ └── __init__.cpython-313.pyc │ │ └── fastapi.py │ ├── persistence │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── tables.cpython-313.pyc │ │ │ └── __init__.cpython-313.pyc │ │ └── tables.py │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ └── __init__.py ├── package.json ├── requirements.txt ├── Pipfile └── domain_class_diagram.puml ├── ch09 ├── domain │ ├── __init__.py │ ├── value_objects │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── money.cpython-313.pyc │ │ │ ├── __init__.cpython-313.pyc │ │ │ └── order_status.cpython-313.pyc │ │ ├── order_status.py │ │ └── money.py │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ ├── entities │ │ ├── __pycache__ │ │ │ ├── order.cpython-313.pyc │ │ │ ├── __init__.cpython-313.pyc │ │ │ ├── coffee.cpython-313.pyc │ │ │ └── order_item.cpython-313.pyc │ │ ├── __init__.py │ │ ├── order_item.py │ │ └── coffee.py │ ├── exceptions │ │ ├── __pycache__ │ │ │ └── __init__.cpython-313.pyc │ │ └── __init__.py │ ├── shared │ │ └── aggregate_root.py │ └── events │ │ └── order_events.py ├── tests │ ├── __init__.py │ ├── api │ │ └── __init__.py │ ├── unit │ │ ├── __init__.py │ │ ├── controller │ │ │ ├── __init__.py │ │ │ └── __pycache__ │ │ │ │ ├── __init__.cpython-313.pyc │ │ │ │ └── test_create_order_controller.cpython-313.pyc │ │ ├── entity │ │ │ ├── __init__.py │ │ │ ├── __pycache__ │ │ │ │ └── test_order.cpython-313.pyc │ │ │ └── test_order.py │ │ ├── usecase │ │ │ ├── __init__.py │ │ │ └── __pycache__ │ │ │ │ └── test_create_order_usecase.cpython-313.pyc │ │ └── __pycache__ │ │ │ └── __init__.cpython-313.pyc │ ├── scripts │ │ ├── run_gateway_test.sh │ │ └── run_unit_test.sh │ └── __pycache__ │ │ └── __init__.cpython-313.pyc ├── adapter │ ├── __init__.py │ ├── presenter │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-313.pyc │ │ │ └── create_order_presenter.cpython-313.pyc │ │ └── create_order_presenter.py │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ ├── repository │ │ └── __pycache__ │ │ │ └── __init__.cpython-313.pyc │ ├── events │ │ ├── __init__.py │ │ └── sns_publisher.py │ └── gateway │ │ └── __init__.py ├── application │ ├── __init__.py │ ├── ports │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ └── __init__.cpython-313.pyc │ │ ├── inbound │ │ │ ├── __pycache__ │ │ │ │ └── __init__.cpython-313.pyc │ │ │ └── __init__.py │ │ ├── outbound │ │ │ ├── __pycache__ │ │ │ │ └── __init__.cpython-313.pyc │ │ │ └── __init__.py │ │ └── repository │ │ │ ├── __pycache__ │ │ │ └── __init__.cpython-313.pyc │ │ │ └── __init__.py │ ├── usecases │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-313.pyc │ │ │ └── create_order_usecase.cpython-313.pyc │ │ └── process_payment_usecase.py │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ └── dtos │ │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ │ └── __init__.py ├── infrastructure │ ├── web │ │ ├── __init__.py │ │ └── __pycache__ │ │ │ ├── fastapi.cpython-313.pyc │ │ │ └── __init__.cpython-313.pyc │ ├── persistence │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── tables.cpython-313.pyc │ │ │ └── __init__.cpython-313.pyc │ │ └── tables.py │ ├── __pycache__ │ │ └── __init__.cpython-313.pyc │ ├── __init__.py │ └── handlers │ │ └── sns_payment_handler.py └── requirements.txt └── ch10 ├── chain-of-thought-prompt-with-guardrail ├── src │ ├── __init__.py │ ├── config │ │ ├── __init__.py │ │ └── database.py │ ├── domain │ │ ├── __init__.py │ │ ├── entities │ │ │ ├── __init__.py │ │ │ ├── customer.py │ │ │ ├── menu.py │ │ │ └── order.py │ │ ├── repositories │ │ │ ├── customer_repository.py │ │ │ ├── menu_repository.py │ │ │ └── order_repository.py │ │ ├── dtos │ │ │ ├── menu_dtos.py │ │ │ ├── customer_dtos.py │ │ │ └── order_dtos.py │ │ └── usecases │ │ │ ├── menu_usecases.py │ │ │ └── customer_usecases.py │ ├── infrastructure │ │ ├── __init__.py │ │ ├── repositories │ │ │ ├── __init__.py │ │ │ ├── mysql_customer_repository.py │ │ │ ├── dynamodb_customer_repository.py │ │ │ ├── mysql_order_repository.py │ │ │ ├── dynamodb_order_repository.py │ │ │ └── dynamodb_menu_repository.py │ │ └── database │ │ │ └── models │ │ │ ├── customer.py │ │ │ ├── menu.py │ │ │ ├── order.py │ │ │ └── base.py │ ├── main.py │ └── presentation │ │ ├── schemas │ │ └── api_response.py │ │ └── controllers │ │ ├── menu_controller.py │ │ └── customer_controller.py ├── requirements-dev.txt ├── pytest.ini ├── requirements.txt ├── alembic.ini ├── setup.py ├── pyproject.toml ├── alembic │ └── env.py └── tests │ └── unit │ ├── test_menu_usecase.py │ └── test_customer_usecase.py ├── chain-of-thought-prompt ├── requirements.txt ├── src │ ├── main.py │ ├── infrastructure │ │ ├── presenters │ │ │ └── json_presenter.py │ │ └── repositories │ │ │ └── dynamodb_models.py │ ├── domain │ │ ├── entities │ │ │ ├── menu.py │ │ │ └── order.py │ │ ├── usecases │ │ │ ├── menu_usecases.py │ │ │ └── order_usecases.py │ │ └── interfaces │ │ │ ├── repositories.py │ │ │ └── boundaries.py │ ├── application │ │ └── controllers │ │ │ └── menu_controller.py │ └── config │ │ └── dependencies.py ├── README.md └── serverless.yml ├── few-shot-prompt-with-constraints ├── requirements.txt ├── src │ ├── domain │ │ ├── entities │ │ │ ├── menu.py │ │ │ └── order.py │ │ ├── usecases │ │ │ ├── menu_usecases.py │ │ │ └── order_usecases.py │ │ └── interfaces │ │ │ ├── repositories.py │ │ │ └── boundaries.py │ ├── infrastructure │ │ ├── presenters │ │ │ └── json_presenter.py │ │ └── repositories │ │ │ └── dynamodb_models.py │ ├── application │ │ └── controllers │ │ │ └── menu_controller.py │ ├── config │ │ └── dependencies.py │ └── main.py ├── tests │ └── unit │ │ └── test_menu_usecase.py ├── README.md └── serverless.yml ├── simple-prompt ├── requirements.txt ├── src │ ├── interfaces │ │ └── schemas.py │ ├── domain │ │ ├── entities.py │ │ └── repositories.py │ ├── infrastructure │ │ └── models.py │ └── application │ │ └── usecases.py ├── serverless.yml └── tests │ └── unit │ └── test_usecases.py └── few-shot-prompt ├── requirements.txt ├── src ├── application │ └── exceptions.py ├── interfaces │ └── schemas.py ├── infrastructure │ └── models.py └── domain │ ├── repositories.py │ └── entities.py ├── README.md └── serverless.yml /ch06/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/domain/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/domain/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_3_usecase/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_4_gateway/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_6_entity/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/adapter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/application/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/tests/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/adapter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/application/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/tests/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_1_framework/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_2_controller/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_5_external/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_7_presenter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_1_framework/click/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_1_framework/fastapi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_2_controller/type1/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_2_controller/type2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/adapter/presenter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/application/ports/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/application/usecases/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/infrastructure/web/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/tests/scripts/run_gateway_test.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/tests/unit/controller/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/tests/unit/entity/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/tests/unit/usecase/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/adapter/presenter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/application/ports/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/application/usecases/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/infrastructure/web/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/tests/scripts/run_gateway_test.sh: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/tests/unit/controller/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/tests/unit/entity/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/tests/unit/usecase/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/infrastructure/persistence/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch09/infrastructure/persistence/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_2_controller/type1/controller/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_2_controller/type1/presentation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch06/6_2_controller/type2/presentation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/domain/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/domain/entities/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/infrastructure/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/infrastructure/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ch07/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "serverless-python-requirements": "^6.1.2" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.68.1 2 | mangum==0.12.3 3 | pynamodb==5.2.1 4 | pydantic==1.8.2 5 | uvicorn==0.15.0 -------------------------------------------------------------------------------- /ch07/domain/value_objects/__init__.py: -------------------------------------------------------------------------------- 1 | from .money import Money 2 | from .order_status import OrderStatus 3 | 4 | __all__ = ["Money", "OrderStatus"] 5 | -------------------------------------------------------------------------------- /ch09/domain/value_objects/__init__.py: -------------------------------------------------------------------------------- 1 | from .money import Money 2 | from .order_status import OrderStatus 3 | 4 | __all__ = ["Money", "OrderStatus"] 5 | -------------------------------------------------------------------------------- /ch07/tests/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/tests/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/tests/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/tests/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/adapter/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/adapter/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/domain/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/domain/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/adapter/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/adapter/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/domain/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/domain/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch06/6_7_presenter/__pycache__/views.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch06/6_7_presenter/__pycache__/views.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/tests/unit/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/tests/unit/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/tests/unit/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/tests/unit/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch06/6_7_presenter/__pycache__/entity.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch06/6_7_presenter/__pycache__/entity.cpython-313.pyc -------------------------------------------------------------------------------- /ch06/6_7_presenter/__pycache__/fastapi.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch06/6_7_presenter/__pycache__/fastapi.cpython-313.pyc -------------------------------------------------------------------------------- /ch06/6_7_presenter/__pycache__/usecase.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch06/6_7_presenter/__pycache__/usecase.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/application/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/application/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/domain/entities/__pycache__/order.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/domain/entities/__pycache__/order.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/application/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/application/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/domain/entities/__pycache__/order.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/domain/entities/__pycache__/order.cpython-313.pyc -------------------------------------------------------------------------------- /ch10/few-shot-prompt-with-constraints/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.104.1 2 | mangum==0.17.0 3 | pynamodb==5.5.0 4 | pydantic==2.5.2 5 | uvicorn==0.24.0 6 | python-dotenv==1.0.0 -------------------------------------------------------------------------------- /ch06/6_7_presenter/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch06/6_7_presenter/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch06/6_7_presenter/__pycache__/presenter.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch06/6_7_presenter/__pycache__/presenter.cpython-313.pyc -------------------------------------------------------------------------------- /ch06/6_7_presenter/__pycache__/repository.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch06/6_7_presenter/__pycache__/repository.cpython-313.pyc -------------------------------------------------------------------------------- /ch06/6_7_presenter/__pycache__/viewmodels.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch06/6_7_presenter/__pycache__/viewmodels.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/domain/entities/__init__.py: -------------------------------------------------------------------------------- 1 | from .coffee import Coffee 2 | from .order import Order 3 | from .order_item import OrderItem 4 | 5 | __all__ = ["Coffee", "Order", "OrderItem"] 6 | -------------------------------------------------------------------------------- /ch07/domain/entities/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/domain/entities/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/domain/entities/__pycache__/coffee.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/domain/entities/__pycache__/coffee.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/infrastructure/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/infrastructure/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/domain/entities/__init__.py: -------------------------------------------------------------------------------- 1 | from .coffee import Coffee 2 | from .order import Order 3 | from .order_item import OrderItem 4 | 5 | __all__ = ["Coffee", "Order", "OrderItem"] 6 | -------------------------------------------------------------------------------- /ch09/domain/entities/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/domain/entities/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/domain/entities/__pycache__/coffee.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/domain/entities/__pycache__/coffee.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/infrastructure/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/infrastructure/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch06/6_7_presenter/__pycache__/controllers.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch06/6_7_presenter/__pycache__/controllers.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/adapter/presenter/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/adapter/presenter/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/application/dtos/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/application/dtos/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/application/ports/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/application/ports/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/domain/entities/__pycache__/order_item.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/domain/entities/__pycache__/order_item.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/domain/exceptions/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/domain/exceptions/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/domain/value_objects/__pycache__/money.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/domain/value_objects/__pycache__/money.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/infrastructure/web/__pycache__/fastapi.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/infrastructure/web/__pycache__/fastapi.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/adapter/presenter/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/adapter/presenter/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/application/dtos/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/application/dtos/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/application/ports/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/application/ports/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/domain/entities/__pycache__/order_item.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/domain/entities/__pycache__/order_item.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/domain/exceptions/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/domain/exceptions/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/domain/value_objects/__pycache__/money.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/domain/value_objects/__pycache__/money.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/infrastructure/web/__pycache__/fastapi.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/infrastructure/web/__pycache__/fastapi.cpython-313.pyc -------------------------------------------------------------------------------- /ch06/6_7_presenter/__pycache__/infrastructure.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch06/6_7_presenter/__pycache__/infrastructure.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/adapter/repository/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/adapter/repository/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/application/usecases/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/application/usecases/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/domain/value_objects/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/domain/value_objects/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/infrastructure/web/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/infrastructure/web/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/tests/unit/entity/__pycache__/test_order.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/tests/unit/entity/__pycache__/test_order.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/adapter/repository/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/adapter/repository/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/application/usecases/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/application/usecases/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/domain/value_objects/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/domain/value_objects/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/infrastructure/web/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/infrastructure/web/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/tests/unit/entity/__pycache__/test_order.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/tests/unit/entity/__pycache__/test_order.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/tests/unit/controller/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/tests/unit/controller/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/tests/unit/controller/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/tests/unit/controller/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch06/6_7_presenter/__pycache__/dependency_injection.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch06/6_7_presenter/__pycache__/dependency_injection.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/application/ports/inbound/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/application/ports/inbound/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/domain/value_objects/__pycache__/order_status.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/domain/value_objects/__pycache__/order_status.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/infrastructure/persistence/__pycache__/tables.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/infrastructure/persistence/__pycache__/tables.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/application/ports/inbound/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/application/ports/inbound/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/domain/value_objects/__pycache__/order_status.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/domain/value_objects/__pycache__/order_status.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/infrastructure/persistence/__pycache__/tables.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/infrastructure/persistence/__pycache__/tables.cpython-313.pyc -------------------------------------------------------------------------------- /ch10/simple-prompt/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.104.1 2 | mangum==0.17.0 3 | pynamodb==5.5.0 4 | pydantic==2.5.2 5 | uvicorn==0.24.0 6 | pytest==7.4.3 7 | pytest-asyncio==0.21.1 8 | python-dotenv==1.0.0 -------------------------------------------------------------------------------- /ch07/application/ports/outbound/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/application/ports/outbound/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/infrastructure/persistence/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/infrastructure/persistence/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/application/ports/outbound/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/application/ports/outbound/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/infrastructure/persistence/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/infrastructure/persistence/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/application/ports/repository/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/application/ports/repository/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3==1.40.5 2 | botocore==1.40.5 3 | fastapi==0.88.0 4 | mangum==0.19.0 5 | pydantic==1.10.12 6 | pynamodb==6.1.0 7 | requests==2.32.4 8 | uvicorn==0.35.0 9 | httpx==0.27.2 10 | -------------------------------------------------------------------------------- /ch09/application/ports/repository/__pycache__/__init__.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/application/ports/repository/__pycache__/__init__.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3==1.40.5 2 | botocore==1.40.5 3 | fastapi==0.116.1 4 | mangum==0.19.0 5 | pydantic==2.11.7 6 | pynamodb==6.1.0 7 | requests==2.32.4 8 | uvicorn==0.35.0 9 | httpx==0.27.2 10 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # Code quality tools 2 | pre-commit>=3.6.0 3 | black>=24.1.0 4 | isort>=5.13.2 5 | ruff>=0.2.0 6 | mypy>=1.8.0 7 | bandit>=1.7.7 8 | types-all>=1.0.0 -------------------------------------------------------------------------------- /ch07/adapter/presenter/__pycache__/create_order_presenter.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/adapter/presenter/__pycache__/create_order_presenter.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/application/usecases/__pycache__/create_order_usecase.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/application/usecases/__pycache__/create_order_usecase.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/adapter/presenter/__pycache__/create_order_presenter.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/adapter/presenter/__pycache__/create_order_presenter.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/application/usecases/__pycache__/create_order_usecase.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/application/usecases/__pycache__/create_order_usecase.cpython-313.pyc -------------------------------------------------------------------------------- /ch07/tests/unit/usecase/__pycache__/test_create_order_usecase.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/tests/unit/usecase/__pycache__/test_create_order_usecase.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/tests/unit/usecase/__pycache__/test_create_order_usecase.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/tests/unit/usecase/__pycache__/test_create_order_usecase.cpython-313.pyc -------------------------------------------------------------------------------- /ch10/few-shot-prompt/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.104.1 2 | mangum==0.17.0 3 | pynamodb==5.5.0 4 | pydantic==2.5.2 5 | uvicorn==0.24.0 6 | python-dotenv==1.0.0 7 | pytest==7.4.3 8 | pytest-mock==3.12.0 9 | pytest-asyncio==0.21.1 -------------------------------------------------------------------------------- /ch07/domain/value_objects/order_status.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class OrderStatus(Enum): 5 | PENDING = "pending" 6 | PREPARING = "preparing" 7 | COMPLETED = "completed" 8 | CANCELLED = "cancelled" 9 | -------------------------------------------------------------------------------- /ch09/domain/value_objects/order_status.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class OrderStatus(Enum): 5 | PENDING = "pending" 6 | PREPARING = "preparing" 7 | COMPLETED = "completed" 8 | CANCELLED = "cancelled" 9 | -------------------------------------------------------------------------------- /ch07/tests/unit/controller/__pycache__/test_create_order_controller.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch07/tests/unit/controller/__pycache__/test_create_order_controller.cpython-313.pyc -------------------------------------------------------------------------------- /ch09/tests/unit/controller/__pycache__/test_create_order_controller.cpython-313.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wikibook/clean-architecture-guide/main/ch09/tests/unit/controller/__pycache__/test_create_order_controller.cpython-313.pyc -------------------------------------------------------------------------------- /ch06/6_7_presenter/viewmodels.py: -------------------------------------------------------------------------------- 1 | # ViewModels Layer 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class ScoreViewModel: 7 | student_id: str 8 | average: str 9 | status: str 10 | grade: str 11 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = tests 3 | python_files = test_*.py 4 | python_classes = Test* 5 | python_functions = test_* 6 | addopts = -v --strict-markers 7 | pythonpath = . 8 | markers = 9 | unit: Unit tests 10 | integration: Integration tests -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/requirements.txt: -------------------------------------------------------------------------------- 1 | # FastAPI and related 2 | fastapi>=0.109.0 3 | pydantic>=2.6.0 4 | mangum>=0.17.0 5 | uvicorn>=0.27.0 6 | 7 | # Database 8 | SQLAlchemy>=2.0.0 9 | alembic>=1.13.0 10 | mysqlclient>=2.2.0 11 | python-dotenv>=1.0.0 12 | 13 | # Testing 14 | pytest>=8.0.0 15 | pytest-mock>=3.12.0 16 | httpx>=0.26.0 -------------------------------------------------------------------------------- /ch07/application/ports/inbound/__init__.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | from application.dtos import InputDto 4 | 5 | 6 | class InputBoundary(metaclass=ABCMeta): 7 | @abstractmethod 8 | def execute(self, input_dto: InputDto) -> None: 9 | pass 10 | 11 | 12 | class CreateOrderInputBoundary(InputBoundary): 13 | pass 14 | -------------------------------------------------------------------------------- /ch09/application/ports/inbound/__init__.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | from application.dtos import InputDto 4 | 5 | 6 | class InputBoundary(metaclass=ABCMeta): 7 | @abstractmethod 8 | def execute(self, input_dto: InputDto) -> None: 9 | pass 10 | 11 | 12 | class CreateOrderInputBoundary(InputBoundary): 13 | pass 14 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/domain/repositories/customer_repository.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Optional 3 | 4 | from ..entities.customer import Customer 5 | 6 | 7 | class CustomerRepository(ABC): 8 | @abstractmethod 9 | def get_by_id(self, customer_id: str) -> Optional[Customer]: 10 | pass 11 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/domain/dtos/menu_dtos.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | 4 | 5 | @dataclass 6 | class MenuResponseDTO: 7 | menu_id: str 8 | name: str 9 | price: int 10 | category: str 11 | 12 | 13 | @dataclass 14 | class MenuListResponseDTO: 15 | menus: List[MenuResponseDTO] 16 | -------------------------------------------------------------------------------- /ch07/application/ports/outbound/__init__.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | from application.dtos import OutputDto 4 | 5 | 6 | class OutputBoundary(metaclass=ABCMeta): 7 | @abstractmethod 8 | def set_result(self, output_dto: OutputDto) -> None: 9 | pass 10 | 11 | @abstractmethod 12 | def present(self) -> dict: 13 | pass 14 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/domain/entities/customer.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from typing import Optional 4 | 5 | 6 | @dataclass 7 | class Customer: 8 | customer_id: str 9 | name: str 10 | email: str 11 | phone: str 12 | created_at: datetime 13 | updated_at: Optional[datetime] = None 14 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/domain/dtos/customer_dtos.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from typing import Optional 4 | 5 | 6 | @dataclass 7 | class CustomerResponseDTO: 8 | customer_id: str 9 | name: str 10 | email: str 11 | phone: str 12 | created_at: datetime 13 | updated_at: Optional[datetime] = None 14 | -------------------------------------------------------------------------------- /ch06/6_5_external/README.md: -------------------------------------------------------------------------------- 1 | ## 실행 방법 2 | 3 | ### 1. FastAPI 서버 실행 4 | ```bash 5 | cd /Users/alphahacker/clean-architecture-with-python-test-3 6 | python -m uvicorn ch06.6_5_external.fastapi:app --reload --port 8000 7 | ``` 8 | 9 | ### 2. API 테스트 10 | ```bash 11 | curl -X POST "http://localhost:8000/calculate" \ 12 | -H "Content-Type: application/json" \ 13 | -d '{"student_id": "student001", "calculation_type": "average"}' 14 | ``` -------------------------------------------------------------------------------- /ch07/domain/entities/order_item.py: -------------------------------------------------------------------------------- 1 | from ..value_objects.money import Money 2 | 3 | 4 | class OrderItem: 5 | def __init__(self, id: str, order_id: str, coffee_id: str, quantity: int, unit_price: Money): 6 | self.id = id 7 | self.order_id = order_id 8 | self.coffee_id = coffee_id 9 | self.quantity = quantity 10 | self.unit_price = unit_price 11 | 12 | def calculate_subtotal(self) -> Money: 13 | return self.unit_price * self.quantity 14 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/domain/repositories/menu_repository.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, Optional 3 | 4 | from ..entities.menu import Menu 5 | 6 | 7 | class MenuRepository(ABC): 8 | @abstractmethod 9 | def get_all(self) -> List[Menu]: 10 | pass 11 | 12 | @abstractmethod 13 | def get_by_id(self, menu_id: str) -> Optional[Menu]: 14 | pass 15 | 16 | @abstractmethod 17 | def update(self, menu: Menu) -> None: 18 | pass 19 | -------------------------------------------------------------------------------- /ch10/simple-prompt/src/interfaces/schemas.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from typing import List, Optional 3 | from datetime import datetime 4 | 5 | class MenuResponse(BaseModel): 6 | id: str 7 | name: str 8 | price: int 9 | category: str 10 | 11 | class CreateOrderRequest(BaseModel): 12 | customer_id: str 13 | menu_id: str 14 | quantity: int 15 | 16 | class OrderResponse(BaseModel): 17 | id: str 18 | status: str 19 | created_at: datetime 20 | updated_at: Optional[datetime] = None -------------------------------------------------------------------------------- /ch09/domain/entities/order_item.py: -------------------------------------------------------------------------------- 1 | from ..value_objects.money import Money 2 | 3 | 4 | class OrderItem: 5 | def __init__(self, id: str, order_id: str, coffee_id: str, quantity: int, unit_price: Money): 6 | self.id = id 7 | self.order_id = order_id 8 | self.coffee_id = coffee_id 9 | self.quantity = quantity 10 | self.unit_price = unit_price 11 | 12 | def calculate_subtotal(self) -> Money: 13 | """주문 항목의 단가와 수량을 곱해 소계를 계산한다""" 14 | return self.unit_price * self.quantity 15 | -------------------------------------------------------------------------------- /ch06/6_4_gateway/README.md: -------------------------------------------------------------------------------- 1 | ### FastAPI 실행 2 | cd /Users/alphahacker/clean-architecture-with-python-test-3 && python -m uvicorn ch06.6_4_gateway.fastapi:app --reload --host 0.0.0.0 --port 8000 3 | 4 | 5 | ### 호출 테스트 6 | curl -X POST "http://localhost:8000/calculate" \ 7 | -H "Content-Type: application/json" \ 8 | -d '{ 9 | "student_id": "student123", 10 | "calculation_type": "average" 11 | }' 12 | 13 | 14 | ### 응답 예시 15 | { 16 | "student_id": "student001", 17 | "result": 85.5, 18 | "status": "success" 19 | } -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt/src/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from mangum import Mangum 3 | from .config.dependencies import Dependencies 4 | 5 | # FastAPI 애플리케이션 생성 6 | app = FastAPI( 7 | title="Coffee Shop API", 8 | description="Clean Architecture를 적용한 커피 주문 API", 9 | version="1.0.0" 10 | ) 11 | 12 | # 의존성 설정 13 | menu_controller, order_controller = Dependencies.configure() 14 | 15 | # 라우터 등록 16 | menu_controller.register_routes(app) 17 | order_controller.register_routes(app) 18 | 19 | # AWS Lambda 핸들러 20 | handler = Mangum(app) -------------------------------------------------------------------------------- /ch10/few-shot-prompt/src/application/exceptions.py: -------------------------------------------------------------------------------- 1 | class ApplicationError(Exception): 2 | """애플리케이션 기본 예외 클래스""" 3 | pass 4 | 5 | 6 | class MenuNotFoundError(ApplicationError): 7 | """메뉴를 찾을 수 없을 때 발생하는 예외""" 8 | pass 9 | 10 | 11 | class InsufficientStockError(ApplicationError): 12 | """재고가 부족할 때 발생하는 예외""" 13 | pass 14 | 15 | 16 | class OrderNotFoundError(ApplicationError): 17 | """주문을 찾을 수 없을 때 발생하는 예외""" 18 | pass 19 | 20 | 21 | class DatabaseError(ApplicationError): 22 | """데이터베이스 오류 발생 시 발생하는 예외""" 23 | pass -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/infrastructure/database/models/customer.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import String 2 | from sqlalchemy.orm import Mapped, mapped_column 3 | 4 | from .base import Base 5 | 6 | 7 | class CustomerModel(Base): 8 | """고객 테이블 모델""" 9 | 10 | customer_id: Mapped[str] = mapped_column(String(36), unique=True, nullable=False) 11 | name: Mapped[str] = mapped_column(String(100), nullable=False) 12 | email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False) 13 | phone: Mapped[str] = mapped_column(String(20), nullable=False) 14 | -------------------------------------------------------------------------------- /ch10/few-shot-prompt-with-constraints/src/domain/entities/menu.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | 5 | @dataclass 6 | class Menu: 7 | menu_id: str 8 | name: str 9 | price: int 10 | category: str 11 | stock: int 12 | 13 | def has_sufficient_stock(self, quantity: int) -> bool: 14 | return self.stock >= quantity 15 | 16 | def decrease_stock(self, quantity: int) -> None: 17 | if not self.has_sufficient_stock(quantity): 18 | raise ValueError("Insufficient stock") 19 | self.stock -= quantity -------------------------------------------------------------------------------- /ch06/6_3_usecase/fastapi.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel 3 | from .calculate_average_usecase import CalculateAverageUseCase, ScoreInputDTO 4 | 5 | app = FastAPI() 6 | 7 | 8 | class ScoreRequest(BaseModel): 9 | student_id: str 10 | calculation_type: str 11 | 12 | 13 | @app.post("/calculate") 14 | async def calculate_score(request: ScoreRequest): 15 | input_dto = ScoreInputDTO(student_id=request.student_id) 16 | 17 | usecase = CalculateAverageUseCase() 18 | result = usecase.execute(input_dto) # 유스케이스 호출 19 | 20 | # 응답 변환 21 | return result.present() 22 | -------------------------------------------------------------------------------- /ch06/6_4_gateway/dependency_injection.py: -------------------------------------------------------------------------------- 1 | from .controllers import CalculateScoreController 2 | from .usecase import CalculateAverageUseCase 3 | from .presenter import ConsolePresenter 4 | from .repository import DdbScoreRepository 5 | 6 | 7 | def initialize_application() -> CalculateScoreController: 8 | # Repository와 Presenter 초기화 9 | repository = DdbScoreRepository() 10 | presenter = ConsolePresenter() 11 | 12 | # 유스케이스 초기화 13 | usecase = CalculateAverageUseCase(repository, presenter) 14 | 15 | # 컨트롤러 초기화 16 | controller = CalculateScoreController(usecase) 17 | return controller 18 | -------------------------------------------------------------------------------- /ch06/6_1_framework/click/main.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | 4 | @click.command() 5 | @click.option("--student-id", required=True, type=str, help="Student ID") 6 | @click.option("--calculation-type", required=True, type=str, help="Calculation type (average/total)") 7 | def calculate_score(student_id: str, calculation_type: str): 8 | # 컨트롤러로 요청 전달 (6.2 섹션에서 구현) 9 | print(f"Received request: student_id={student_id}, calculation_type={calculation_type}") 10 | 11 | 12 | if __name__ == "__main__": 13 | calculate_score() 14 | 15 | 16 | # 실행 17 | # 경로는 ch06/cli 들어와서 18 | # python main.py --student-id test --calculation-type average 19 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/domain/dtos/order_dtos.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from typing import Dict, Optional 4 | 5 | 6 | @dataclass 7 | class CreateOrderRequestDTO: 8 | customer_id: str 9 | menu_id: str 10 | quantity: int 11 | options: Dict[str, str] 12 | 13 | 14 | @dataclass 15 | class OrderResponseDTO: 16 | order_id: str 17 | status: str 18 | created_at: datetime 19 | updated_at: Optional[datetime] = None 20 | 21 | 22 | @dataclass 23 | class DeleteOrderResponseDTO: 24 | order_id: str 25 | success: bool 26 | message: str 27 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/infrastructure/database/models/menu.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Integer, String 2 | from sqlalchemy.orm import Mapped, mapped_column 3 | 4 | from .base import Base 5 | 6 | 7 | class MenuModel(Base): 8 | """메뉴 테이블 모델""" 9 | 10 | menu_id: Mapped[str] = mapped_column(String(36), unique=True, nullable=False) 11 | name: Mapped[str] = mapped_column(String(100), nullable=False) 12 | price: Mapped[int] = mapped_column(Integer, nullable=False) 13 | category: Mapped[str] = mapped_column(String(50), nullable=False) 14 | stock: Mapped[int] = mapped_column(Integer, nullable=False, default=0) 15 | -------------------------------------------------------------------------------- /ch10/few-shot-prompt-with-constraints/src/infrastructure/presenters/json_presenter.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from ...domain.interfaces.boundaries import OutputBoundary, ResponseDTO 3 | 4 | 5 | class JSONPresenter(OutputBoundary): 6 | def present_success(self, data: Any, message: str) -> ResponseDTO: 7 | return ResponseDTO( 8 | status="success", 9 | data=data, 10 | message=message 11 | ) 12 | 13 | def present_error(self, message: str) -> ResponseDTO: 14 | return ResponseDTO( 15 | status="error", 16 | data=None, 17 | message=message 18 | ) -------------------------------------------------------------------------------- /ch10/simple-prompt/src/domain/entities.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from enum import Enum 4 | from typing import Optional 5 | 6 | class OrderStatus(Enum): 7 | PENDING = "PENDING" 8 | PREPARING = "PREPARING" 9 | COMPLETED = "COMPLETED" 10 | CANCELLED = "CANCELLED" 11 | 12 | @dataclass 13 | class Menu: 14 | id: str 15 | name: str 16 | price: int 17 | category: str 18 | stock: int 19 | 20 | @dataclass 21 | class Order: 22 | id: str 23 | customer_id: str 24 | menu_id: str 25 | quantity: int 26 | status: OrderStatus 27 | created_at: datetime 28 | updated_at: Optional[datetime] = None -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt/src/infrastructure/presenters/json_presenter.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from ...domain.interfaces.boundaries import OutputBoundary, ResponseDTO 3 | 4 | class JSONPresenter(OutputBoundary): 5 | def present_success(self, data: Any, message: str) -> ResponseDTO: 6 | """성공 응답 생성""" 7 | return ResponseDTO( 8 | status="success", 9 | data=data, 10 | message=message 11 | ) 12 | 13 | def present_error(self, message: str) -> ResponseDTO: 14 | """에러 응답 생성""" 15 | return ResponseDTO( 16 | status="error", 17 | data=None, 18 | message=message 19 | ) -------------------------------------------------------------------------------- /ch06/6_6_entity/infrastructure.py: -------------------------------------------------------------------------------- 1 | # Framework & Drivers Layer (Infrastructure) 2 | import boto3 3 | from botocore.exceptions import ClientError 4 | from typing import Dict, Any 5 | 6 | 7 | class DynamoDBClient: 8 | def __init__(self, region: str = "ap-northeast-2"): 9 | self.client = boto3.resource("dynamodb", region_name=region) 10 | 11 | def get_item(self, table_name: str, key: Dict[str, Any]) -> Dict[str, Any]: 12 | try: 13 | table = self.client.Table(table_name) 14 | response = table.get_item(Key=key) 15 | return response 16 | except ClientError as e: 17 | raise ValueError(f"Failed to retrieve data: {str(e)}") 18 | -------------------------------------------------------------------------------- /ch06/6_4_gateway/fastapi.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel 3 | from .dependency_injection import initialize_application 4 | from .controllers import ScoreRequestDTO 5 | 6 | app = FastAPI() 7 | 8 | # 애플리케이션 초기화 9 | controller = initialize_application() 10 | 11 | 12 | class ScoreRequest(BaseModel): 13 | student_id: str 14 | calculation_type: str 15 | 16 | 17 | @app.post("/calculate") 18 | async def calculate_score(request: ScoreRequest): 19 | # 요청을 DTO로 변환 20 | request_dto = ScoreRequestDTO(student_id=request.student_id, calculation_type=request.calculation_type) 21 | 22 | # 컨트롤러를 통해 유스케이스 실행 및 응답 23 | return controller.execute(request_dto) 24 | -------------------------------------------------------------------------------- /ch06/6_5_external/infrastructure.py: -------------------------------------------------------------------------------- 1 | # Framework & Drivers Layer (Infrastructure) 2 | import boto3 3 | from botocore.exceptions import ClientError 4 | from typing import Dict, Any 5 | 6 | 7 | class DynamoDBClient: 8 | def __init__(self, region: str = "ap-northeast-2"): 9 | self.client = boto3.resource("dynamodb", region_name=region) 10 | 11 | def get_item(self, table_name: str, key: Dict[str, Any]) -> Dict[str, Any]: 12 | try: 13 | table = self.client.Table(table_name) 14 | response = table.get_item(Key=key) 15 | return response 16 | except ClientError as e: 17 | raise ValueError(f"Failed to retrieve data: {str(e)}") 18 | -------------------------------------------------------------------------------- /ch06/6_6_entity/fastapi.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel 3 | from .dependency_injection import initialize_application 4 | from .controllers import ScoreRequestDTO 5 | 6 | app = FastAPI() 7 | 8 | # 애플리케이션 초기화 9 | controller = initialize_application() 10 | 11 | 12 | class ScoreRequest(BaseModel): 13 | student_id: str 14 | calculation_type: str 15 | 16 | 17 | @app.post("/calculate") 18 | async def calculate_score(request: ScoreRequest): 19 | # 요청을 DTO로 변환 20 | request_dto = ScoreRequestDTO(student_id=request.student_id, calculation_type=request.calculation_type) 21 | 22 | # 컨트롤러를 통해 유스케이스 실행 및 응답 23 | return controller.execute(request_dto) 24 | -------------------------------------------------------------------------------- /ch06/6_7_presenter/infrastructure.py: -------------------------------------------------------------------------------- 1 | # Framework & Drivers Layer (Infrastructure) 2 | import boto3 3 | from botocore.exceptions import ClientError 4 | from typing import Dict, Any 5 | 6 | 7 | class DynamoDBClient: 8 | def __init__(self, region: str = "ap-northeast-2"): 9 | self.client = boto3.resource("dynamodb", region_name=region) 10 | 11 | def get_item(self, table_name: str, key: Dict[str, Any]) -> Dict[str, Any]: 12 | try: 13 | table = self.client.Table(table_name) 14 | response = table.get_item(Key=key) 15 | return response 16 | except ClientError as e: 17 | raise ValueError(f"Failed to retrieve data: {str(e)}") 18 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/config/database.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Generator 3 | 4 | from sqlalchemy import create_engine 5 | from sqlalchemy.orm import Session, sessionmaker 6 | 7 | # 환경 변수에서 데이터베이스 설정 읽기 8 | DATABASE_URL = os.getenv( 9 | "DATABASE_URL", "mysql://root:password@localhost:3306/coffee_order" # 기본값 10 | ) 11 | 12 | engine = create_engine(DATABASE_URL) 13 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) 14 | 15 | 16 | def get_db() -> Generator[Session, None, None]: 17 | """데이터베이스 세션을 생성하고 관리하는 의존성 주입 함수""" 18 | db = SessionLocal() 19 | try: 20 | yield db 21 | finally: 22 | db.close() 23 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/domain/repositories/order_repository.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Optional 3 | 4 | from ..entities.order import Order 5 | 6 | 7 | class OrderRepository(ABC): 8 | @abstractmethod 9 | def save(self, order: Order) -> None: 10 | pass 11 | 12 | @abstractmethod 13 | def get_by_id(self, order_id: str) -> Optional[Order]: 14 | pass 15 | 16 | @abstractmethod 17 | def delete(self, order_id: str) -> bool: 18 | """주문을 삭제합니다. 19 | 20 | Args: 21 | order_id: 삭제할 주문의 ID 22 | 23 | Returns: 24 | bool: 삭제 성공 여부 25 | """ 26 | pass 27 | -------------------------------------------------------------------------------- /ch06/6_3_usecase/unit_test.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import Mock 2 | from calculate_average_usecase import ( 3 | ScoreRepository, 4 | OutputBoundary, 5 | CalculateAverageUseCase, 6 | ScoreInputDTO, 7 | ScoreOutputDTO, 8 | ) 9 | 10 | # 테스트용 모의 객체 11 | mock_repository = Mock(spec=ScoreRepository) 12 | mock_repository.get_scores.return_value = [90.0, 85.0, 95.0] 13 | mock_presenter = Mock(spec=OutputBoundary) 14 | 15 | # 유스케이스 테스트 16 | usecase = CalculateAverageUseCase(mock_repository, mock_presenter) 17 | usecase.execute(ScoreInputDTO(student_id="123")) 18 | 19 | # 결과 검증 20 | mock_presenter.set_result.assert_called_with(ScoreOutputDTO(student_id="123", average=90.00, status="success")) 21 | -------------------------------------------------------------------------------- /ch06/6_5_external/fastapi.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel 3 | from .dependency_injection import initialize_application 4 | from .controllers import ScoreRequestDTO 5 | 6 | app = FastAPI() 7 | 8 | # 애플리케이션 초기화 9 | controller = initialize_application() 10 | 11 | 12 | class ScoreRequest(BaseModel): 13 | student_id: str 14 | calculation_type: str 15 | 16 | 17 | @app.post("/calculate") 18 | async def calculate_score(request: ScoreRequest): 19 | # 요청을 DTO로 변환 20 | request_dto = ScoreRequestDTO(student_id=request.student_id, calculation_type=request.calculation_type) 21 | 22 | # 컨트롤러를 통해 유스케이스 실행 및 응답 23 | return controller.execute(request_dto) 24 | -------------------------------------------------------------------------------- /ch06/6_7_presenter/README.md: -------------------------------------------------------------------------------- 1 | ## 실행 방법 2 | 3 | ### 1. FastAPI 서버 실행 4 | ```bash 5 | cd /Users/alphahacker/clean-architecture-with-python-test-3 6 | python -m uvicorn ch06.6_7_presenter.fastapi:app --reload --port 8000 7 | ``` 8 | 9 | ### 2. API 테스트 10 | 11 | #### POST 요청 (각 형식별 엔드포인트) 12 | ```bash 13 | # API 형식 14 | curl -X POST "http://localhost:8000/calculate/api" \ 15 | -H "Content-Type: application/json" \ 16 | -d '{"student_id": "student001", "calculation_type": "average"}' 17 | 18 | # Web 형식 (HTML) 19 | curl -X POST "http://localhost:8000/calculate/web" \ 20 | -H "Content-Type: application/json" \ 21 | -d '{"student_id": "student001", "calculation_type": "average"}' 22 | ``` 23 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/infrastructure/database/models/order.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import JSON, Integer, String 2 | from sqlalchemy.orm import Mapped, mapped_column 3 | 4 | from .base import Base 5 | 6 | 7 | class OrderModel(Base): 8 | """주문 테이블 모델""" 9 | 10 | order_id: Mapped[str] = mapped_column(String(36), unique=True, nullable=False) 11 | customer_id: Mapped[str] = mapped_column(String(36), nullable=False) 12 | menu_id: Mapped[str] = mapped_column(String(36), nullable=False) 13 | quantity: Mapped[int] = mapped_column(Integer, nullable=False) 14 | status: Mapped[str] = mapped_column(String(20), nullable=False) 15 | options: Mapped[dict] = mapped_column(JSON, nullable=True) 16 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/domain/entities/menu.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from typing import Optional 4 | 5 | 6 | @dataclass 7 | class Menu: 8 | menu_id: str 9 | name: str 10 | price: int 11 | category: str 12 | stock: int 13 | created_at: datetime 14 | updated_at: Optional[datetime] = None 15 | 16 | def is_available(self, quantity: int) -> bool: 17 | return self.stock >= quantity 18 | 19 | def decrease_stock(self, quantity: int) -> None: 20 | if not self.is_available(quantity): 21 | raise ValueError("Insufficient stock") 22 | self.stock -= quantity 23 | self.updated_at = datetime.utcnow() 24 | -------------------------------------------------------------------------------- /ch06/6_6_entity/dependency_injection.py: -------------------------------------------------------------------------------- 1 | from .controllers import CalculateScoreController 2 | from .usecase import CalculateAverageUseCase 3 | from .presenter import ConsolePresenter 4 | from .repository import DdbScoreRepository 5 | from .infrastructure import DynamoDBClient 6 | 7 | 8 | def initialize_application() -> CalculateScoreController: 9 | # 인프라스트럭쳐 초기화 10 | dynamodb_client = DynamoDBClient(region="ap-northeast-2") 11 | 12 | # 인터페이스 어댑터 초기화 13 | repository = DdbScoreRepository(dynamodb_client) 14 | presenter = ConsolePresenter() 15 | 16 | # 유스케이스 초기화 17 | usecase = CalculateAverageUseCase(repository, presenter) 18 | 19 | # 컨트롤러 초기화 20 | controller = CalculateScoreController(usecase) 21 | return controller 22 | -------------------------------------------------------------------------------- /ch10/few-shot-prompt/src/interfaces/schemas.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from typing import List, Optional, Dict 3 | from datetime import datetime 4 | 5 | 6 | class MenuResponse(BaseModel): 7 | menu_id: str 8 | name: str 9 | price: int 10 | category: str 11 | 12 | 13 | class OrderOptions(BaseModel): 14 | size: str 15 | temperature: str 16 | 17 | 18 | class CreateOrderRequest(BaseModel): 19 | customer_id: str 20 | menu_id: str 21 | quantity: int 22 | options: OrderOptions 23 | 24 | 25 | class OrderResponse(BaseModel): 26 | order_id: str 27 | status: str 28 | created_at: datetime 29 | 30 | 31 | class APIResponse(BaseModel): 32 | status: str 33 | data: Optional[dict] = None 34 | message: str -------------------------------------------------------------------------------- /ch06/6_2_controller/type1/presentation/cli.py: -------------------------------------------------------------------------------- 1 | import click 2 | from ..controller.calculater_score_controller import CalculateScoreController, ScoreRequestDTO 3 | 4 | 5 | @click.command() 6 | @click.option("--student-id", required=True, type=str, help="Student ID") 7 | @click.option("--calculation-type", required=True, type=str, help="Calculation type (average)") 8 | def calculate_score(student_id: str, calculation_type: str): 9 | controller = CalculateScoreController(...) # 의존성 주입은 별도 모듈에서 처리 10 | request_dto = ScoreRequestDTO(student_id=student_id, calculation_type=calculation_type) 11 | response_dto = controller.execute(request_dto) 12 | print(f"Student ID: {response_dto.student_id}, Result: {response_dto.result}, Status: {response_dto.status}") 13 | -------------------------------------------------------------------------------- /ch06/6_4_gateway/presenter.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | 5 | # 출력 인터페이스 (Presenter) 6 | class OutputBoundary(ABC): 7 | @abstractmethod 8 | def set_result(self, output_dto) -> None: 9 | pass 10 | 11 | @abstractmethod 12 | def present(self) -> Any: 13 | pass 14 | 15 | 16 | class ConsolePresenter(OutputBoundary): 17 | def __init__(self): 18 | self.contents = {} 19 | 20 | def set_result(self, output_dto: "ScoreOutputDTO"): 21 | self.contents = { 22 | "student_id": output_dto.student_id, 23 | "average": output_dto.average, 24 | "status": output_dto.status, 25 | } 26 | 27 | def present(self) -> Any: 28 | return self.contents 29 | -------------------------------------------------------------------------------- /ch06/6_6_entity/presenter.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | 5 | # 출력 인터페이스 (Presenter) 6 | class OutputBoundary(ABC): 7 | @abstractmethod 8 | def set_result(self, output_dto) -> None: 9 | pass 10 | 11 | @abstractmethod 12 | def present(self) -> Any: 13 | pass 14 | 15 | 16 | class ConsolePresenter(OutputBoundary): 17 | def __init__(self): 18 | self.contents = {} 19 | 20 | def set_result(self, output_dto: "ScoreOutputDTO"): 21 | self.contents = { 22 | "student_id": output_dto.student_id, 23 | "average": output_dto.average, 24 | "status": output_dto.status, 25 | } 26 | 27 | def present(self) -> Any: 28 | return self.contents 29 | -------------------------------------------------------------------------------- /ch07/domain/entities/coffee.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from ..value_objects.money import Money 3 | 4 | 5 | class Coffee: 6 | def __init__(self, id: str, name: str, price: Money, description: Optional[str] = None, stock: int = 0): 7 | self.id = id 8 | self.name = name 9 | self.price = price 10 | self.description = description 11 | self.stock = stock 12 | 13 | def is_available(self) -> bool: 14 | return self.stock > 0 15 | 16 | def reserve_stock(self, quantity: int) -> None: 17 | if quantity <= 0: 18 | raise ValueError("수량은 1 이상이어야 합니다") 19 | if quantity > self.stock: 20 | raise ValueError(f"재고 부족: {self.name} (남은 수량: {self.stock})") 21 | self.stock -= quantity 22 | -------------------------------------------------------------------------------- /ch06/6_2_controller/type1/presentation/fastapi.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel 3 | from ..controller.calculater_score_controller import CalculateScoreController, ScoreRequestDTO 4 | 5 | app = FastAPI() 6 | 7 | 8 | class ScoreRequest(BaseModel): 9 | student_id: str 10 | calculation_type: str 11 | 12 | 13 | @app.post("/calculate") 14 | async def calculate_score(request: ScoreRequest): 15 | controller = CalculateScoreController(...) # 의존성 주입은 별도 모듈에서 처리 16 | request_dto = ScoreRequestDTO(student_id=request.student_id, calculation_type=request.calculation_type) 17 | response_dto = controller.execute(request_dto) 18 | return {"student_id": response_dto.student_id, "result": response_dto.result, "status": response_dto.status} 19 | -------------------------------------------------------------------------------- /ch06/6_5_external/presenter.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | 5 | # 출력 인터페이스 (Presenter) 6 | class OutputBoundary(ABC): 7 | @abstractmethod 8 | def set_result(self, output_dto) -> None: 9 | pass 10 | 11 | @abstractmethod 12 | def present(self) -> Any: 13 | pass 14 | 15 | 16 | class ConsolePresenter(OutputBoundary): 17 | def __init__(self): 18 | self.contents = {} 19 | 20 | def set_result(self, output_dto: "ScoreOutputDTO"): 21 | self.contents = { 22 | "student_id": output_dto.student_id, 23 | "average": output_dto.average, 24 | "status": output_dto.status, 25 | } 26 | 27 | def present(self) -> Any: 28 | return self.contents 29 | -------------------------------------------------------------------------------- /ch10/few-shot-prompt-with-constraints/src/domain/usecases/menu_usecases.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from ..interfaces.boundaries import GetMenusInputBoundary, MenuOutputDTO 3 | from ..interfaces.repositories import MenuRepository 4 | 5 | 6 | class GetMenusUseCase(GetMenusInputBoundary): 7 | def __init__(self, menu_repository: MenuRepository): 8 | self._menu_repository = menu_repository 9 | 10 | def execute(self) -> List[MenuOutputDTO]: 11 | menus = self._menu_repository.get_all() 12 | return [ 13 | MenuOutputDTO( 14 | menu_id=menu.menu_id, 15 | name=menu.name, 16 | price=menu.price, 17 | category=menu.category 18 | ) 19 | for menu in menus 20 | ] -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/alembic.ini: -------------------------------------------------------------------------------- 1 | [alembic] 2 | script_location = alembic 3 | sqlalchemy.url = mysql://root:password@localhost:3306/coffee_order 4 | 5 | [loggers] 6 | keys = root,sqlalchemy,alembic 7 | 8 | [handlers] 9 | keys = console 10 | 11 | [formatters] 12 | keys = generic 13 | 14 | [logger_root] 15 | level = WARN 16 | handlers = console 17 | qualname = 18 | 19 | [logger_sqlalchemy] 20 | level = WARN 21 | handlers = 22 | qualname = sqlalchemy.engine 23 | 24 | [logger_alembic] 25 | level = INFO 26 | handlers = 27 | qualname = alembic 28 | 29 | [handler_console] 30 | class = StreamHandler 31 | args = (sys.stderr,) 32 | level = NOTSET 33 | formatter = generic 34 | 35 | [formatter_generic] 36 | format = %(levelname)-5.5s [%(name)s] %(message)s 37 | datefmt = %H:%M:%S -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt/src/domain/entities/menu.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | from datetime import datetime 4 | 5 | @dataclass 6 | class Menu: 7 | menu_id: str 8 | name: str 9 | price: int 10 | category: str 11 | stock: int 12 | created_at: Optional[datetime] = None 13 | updated_at: Optional[datetime] = None 14 | 15 | def is_available(self, quantity: int) -> bool: 16 | """주문 가능한 재고가 있는지 확인""" 17 | return self.stock >= quantity 18 | 19 | def decrease_stock(self, quantity: int) -> None: 20 | """재고 감소""" 21 | if not self.is_available(quantity): 22 | raise ValueError("Insufficient stock") 23 | self.stock -= quantity 24 | self.updated_at = datetime.utcnow() -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt/src/domain/usecases/menu_usecases.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from ..interfaces.boundaries import MenuInputBoundary, MenuOutputDTO 3 | from ..interfaces.repositories import MenuRepository 4 | 5 | class MenuUseCases(MenuInputBoundary): 6 | def __init__(self, menu_repository: MenuRepository): 7 | self._menu_repository = menu_repository 8 | 9 | def get_all_menus(self) -> List[MenuOutputDTO]: 10 | """모든 메뉴 조회""" 11 | menus = self._menu_repository.get_all() 12 | return [ 13 | MenuOutputDTO( 14 | menu_id=menu.menu_id, 15 | name=menu.name, 16 | price=menu.price, 17 | category=menu.category 18 | ) 19 | for menu in menus 20 | ] -------------------------------------------------------------------------------- /ch06/6_1_framework/fastapi/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel 3 | 4 | app = FastAPI() 5 | 6 | 7 | class ScoreRequest(BaseModel): 8 | student_id: str 9 | calculation_type: str # 예: "average", "total" 10 | 11 | 12 | @app.post("/calculate") 13 | async def calculate_score(request: ScoreRequest): 14 | # 컨트롤러로 요청 전달 (6.2 섹션에서 구현) 15 | result = {"student_id": request.student_id, "calculation_type": request.calculation_type} 16 | return {"status": "success", "data": result} 17 | 18 | 19 | # 실행 방법 20 | # root에서 uvicorn ch06.framework:app --reload 21 | 22 | # 호출 예시 23 | # curl -X POST "http://127.0.0.1:8000/calculate" -H "Content-Type: application/json" -d '{"student_id": "test_id", "calculation_type": "total"}' 24 | 25 | # 호출할때 경로는 project root에서 26 | -------------------------------------------------------------------------------- /ch07/application/ports/repository/__init__.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Optional, List 3 | 4 | from domain.entities.coffee import Coffee 5 | from domain.entities.order import Order 6 | 7 | 8 | class CoffeeRepository(ABC): 9 | @abstractmethod 10 | def find_by_id(self, coffee_id: str) -> Optional[Coffee]: 11 | pass 12 | 13 | @abstractmethod 14 | def find_all_available(self) -> List[Coffee]: 15 | pass 16 | 17 | @abstractmethod 18 | def save(self, coffee: Coffee) -> None: 19 | pass 20 | 21 | 22 | class OrderRepository(ABC): 23 | @abstractmethod 24 | def find_by_id(self, order_id: str) -> Optional[Order]: 25 | pass 26 | 27 | @abstractmethod 28 | def save(self, order: Order) -> None: 29 | pass 30 | -------------------------------------------------------------------------------- /ch09/application/ports/repository/__init__.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Optional, List 3 | 4 | from domain.entities.coffee import Coffee 5 | from domain.entities.order import Order 6 | 7 | 8 | class CoffeeRepository(ABC): 9 | @abstractmethod 10 | def find_by_id(self, coffee_id: str) -> Optional[Coffee]: 11 | pass 12 | 13 | @abstractmethod 14 | def find_all_available(self) -> List[Coffee]: 15 | pass 16 | 17 | @abstractmethod 18 | def save(self, coffee: Coffee) -> None: 19 | pass 20 | 21 | 22 | class OrderRepository(ABC): 23 | @abstractmethod 24 | def find_by_id(self, order_id: str) -> Optional[Order]: 25 | pass 26 | 27 | @abstractmethod 28 | def save(self, order: Order) -> None: 29 | pass 30 | -------------------------------------------------------------------------------- /ch07/tests/scripts/run_unit_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Running pre-commit tests (UseCase and Controller, Entity)..." 4 | 5 | if command -v pipenv >/dev/null 2>&1; then 6 | pipenv run python -m unittest \ 7 | tests/unit/usecase/test_create_order_usecase.py \ 8 | tests/unit/controller/test_create_order_controller.py \ 9 | tests/unit/entity/test_order.py -v 10 | else 11 | # pipenv가 없으면 python3 시도 12 | python3 -m unittest \ 13 | tests/unit/usecase/test_create_order_usecase.py \ 14 | tests/unit/controller/test_create_order_controller.py \ 15 | tests/unit/entity/test_order.py -v 16 | fi 17 | 18 | # 테스트 결과 확인 19 | if [ $? -eq 0 ]; then 20 | echo "Pre-commit tests passed!" 21 | exit 0 22 | else 23 | echo "Pre-commit tests failed!" 24 | exit 1 25 | fi -------------------------------------------------------------------------------- /ch09/tests/scripts/run_unit_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Running pre-commit tests (UseCase and Controller, Entity)..." 4 | 5 | if command -v pipenv >/dev/null 2>&1; then 6 | pipenv run python -m unittest \ 7 | tests/unit/usecase/test_create_order_usecase.py \ 8 | tests/unit/controller/test_create_order_controller.py \ 9 | tests/unit/entity/test_order.py -v 10 | else 11 | # pipenv가 없으면 python3 시도 12 | python3 -m unittest \ 13 | tests/unit/usecase/test_create_order_usecase.py \ 14 | tests/unit/controller/test_create_order_controller.py \ 15 | tests/unit/entity/test_order.py -v 16 | fi 17 | 18 | # 테스트 결과 확인 19 | if [ $? -eq 0 ]; then 20 | echo "Pre-commit tests passed!" 21 | exit 0 22 | else 23 | echo "Pre-commit tests failed!" 24 | exit 1 25 | fi -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | setup( 4 | name="coffee-order-api", 5 | version="0.1.0", 6 | packages=find_packages(), 7 | install_requires=[ 8 | "fastapi>=0.109.0", 9 | "pydantic>=2.6.0", 10 | "mangum>=0.17.0", 11 | "uvicorn>=0.27.0", 12 | "pynamodb>=5.5.0", 13 | "python-dotenv>=1.0.0", 14 | ], 15 | extras_require={ 16 | "dev": [ 17 | "pytest>=8.0.0", 18 | "pytest-mock>=3.12.0", 19 | "httpx>=0.26.0", 20 | "pre-commit>=3.6.0", 21 | "black>=24.1.0", 22 | "isort>=5.13.2", 23 | "ruff>=0.2.0", 24 | "mypy>=1.8.0", 25 | "bandit>=1.7.7", 26 | ], 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /ch07/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | annotated-types = "==0.7.0" 8 | anyio = "==4.10.0" 9 | boto3 = "==1.40.5" 10 | botocore = "==1.40.5" 11 | certifi = "==2025.8.3" 12 | charset-normalizer = "==3.4.2" 13 | click = "==8.2.1" 14 | fastapi = "==0.88.0" 15 | h11 = "==0.16.0" 16 | idna = "==3.10" 17 | jmespath = "==1.0.1" 18 | mangum = "==0.19.0" 19 | pydantic = "==1.10.12" 20 | pynamodb = "==6.1.0" 21 | python-dateutil = "==2.9.0.post0" 22 | requests = "==2.32.4" 23 | s3transfer = "==0.13.1" 24 | six = "==1.17.0" 25 | sniffio = "==1.3.1" 26 | starlette = "==0.47.2" 27 | typing-inspection = "==0.4.1" 28 | typing-extensions = "==4.14.1" 29 | urllib3 = "==2.5.0" 30 | uvicorn = "==0.35.0" 31 | 32 | [dev-packages] 33 | 34 | [requires] 35 | python_version = "3.13" 36 | -------------------------------------------------------------------------------- /ch09/domain/entities/coffee.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from ..value_objects.money import Money 3 | 4 | 5 | class Coffee: 6 | def __init__(self, id: str, name: str, price: Money, description: Optional[str] = None, stock: int = 0): 7 | self.id = id 8 | self.name = name 9 | self.price = price 10 | self.description = description 11 | self.stock = stock 12 | 13 | def is_available(self) -> bool: 14 | """고객이 주문 가능한 재고가 있는지 확인한다""" 15 | return self.stock > 0 16 | 17 | def reserve_stock(self, quantity: int) -> None: 18 | """주문 처리를 위해 요청된 수량만큼 재고를 예약한다""" 19 | if quantity <= 0: 20 | raise ValueError("수량은 1 이상이어야 합니다") 21 | if quantity > self.stock: 22 | raise ValueError(f"재고 부족: {self.name} (남은 수량: {self.stock})") 23 | self.stock -= quantity 24 | -------------------------------------------------------------------------------- /ch10/simple-prompt/src/domain/repositories.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, Optional 3 | from .entities import Menu, Order 4 | 5 | class MenuRepository(ABC): 6 | @abstractmethod 7 | async def get_all(self) -> List[Menu]: 8 | pass 9 | 10 | @abstractmethod 11 | async def get_by_id(self, menu_id: str) -> Optional[Menu]: 12 | pass 13 | 14 | @abstractmethod 15 | async def update_stock(self, menu_id: str, quantity: int) -> bool: 16 | pass 17 | 18 | class OrderRepository(ABC): 19 | @abstractmethod 20 | async def create(self, order: Order) -> Order: 21 | pass 22 | 23 | @abstractmethod 24 | async def get_by_id(self, order_id: str) -> Optional[Order]: 25 | pass 26 | 27 | @abstractmethod 28 | async def update(self, order: Order) -> Order: 29 | pass -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/infrastructure/database/models/base.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from sqlalchemy.ext.declarative import declared_attr 4 | from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column 5 | from sqlalchemy.sql import func 6 | 7 | 8 | class Base(DeclarativeBase): 9 | """모든 SQLAlchemy 모델의 기본 클래스""" 10 | 11 | @declared_attr 12 | @classmethod 13 | def __tablename__(cls) -> str: 14 | """테이블 이름을 클래스 이름의 소문자 버전으로 자동 생성""" 15 | return cls.__name__.lower() 16 | 17 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) 18 | created_at: Mapped[datetime] = mapped_column(server_default=func.now()) 19 | updated_at: Mapped[datetime] = mapped_column( 20 | server_default=func.now(), 21 | onupdate=func.now(), 22 | nullable=True, 23 | ) 24 | -------------------------------------------------------------------------------- /ch06/6_5_external/repository.py: -------------------------------------------------------------------------------- 1 | # Interface Adapters Layer 2 | from abc import ABC, abstractmethod 3 | from typing import List 4 | from .infrastructure import DynamoDBClient 5 | 6 | 7 | class ScoreRepository(ABC): 8 | @abstractmethod 9 | def get_scores(self, student_id: str) -> List[float]: 10 | pass 11 | 12 | 13 | class DdbScoreRepository(ScoreRepository): 14 | def __init__(self, dynamodb_client: DynamoDBClient): 15 | self.dynamodb_client = dynamodb_client 16 | self.table_name = "scores" 17 | 18 | def get_scores(self, student_id: str) -> List[float]: 19 | response = self.dynamodb_client.get_item(table_name=self.table_name, key={"student_id": student_id}) 20 | if "Item" in response and "scores" in response["Item"]: 21 | return [float(score) for score in response["Item"]["scores"]["L"]] 22 | return [] 23 | -------------------------------------------------------------------------------- /ch06/6_6_entity/repository.py: -------------------------------------------------------------------------------- 1 | # Interface Adapters Layer 2 | from abc import ABC, abstractmethod 3 | from typing import List 4 | from .infrastructure import DynamoDBClient 5 | 6 | 7 | class ScoreRepository(ABC): 8 | @abstractmethod 9 | def get_scores(self, student_id: str) -> List[float]: 10 | pass 11 | 12 | 13 | class DdbScoreRepository(ScoreRepository): 14 | def __init__(self, dynamodb_client: DynamoDBClient): 15 | self.dynamodb_client = dynamodb_client 16 | self.table_name = "scores" 17 | 18 | def get_scores(self, student_id: str) -> List[float]: 19 | response = self.dynamodb_client.get_item(table_name=self.table_name, key={"student_id": student_id}) 20 | if "Item" in response and "scores" in response["Item"]: 21 | return [float(score) for score in response["Item"]["scores"]["L"]] 22 | return [] 23 | -------------------------------------------------------------------------------- /ch06/6_7_presenter/repository.py: -------------------------------------------------------------------------------- 1 | # Interface Adapters Layer 2 | from abc import ABC, abstractmethod 3 | from typing import List 4 | from .infrastructure import DynamoDBClient 5 | 6 | 7 | class ScoreRepository(ABC): 8 | @abstractmethod 9 | def get_scores(self, student_id: str) -> List[float]: 10 | pass 11 | 12 | 13 | class DdbScoreRepository(ScoreRepository): 14 | def __init__(self, dynamodb_client: DynamoDBClient): 15 | self.dynamodb_client = dynamodb_client 16 | self.table_name = "scores" 17 | 18 | def get_scores(self, student_id: str) -> List[float]: 19 | response = self.dynamodb_client.get_item(table_name=self.table_name, key={"student_id": student_id}) 20 | if "Item" in response and "scores" in response["Item"]: 21 | return [float(score) for score in response["Item"]["scores"]["L"]] 22 | return [] 23 | -------------------------------------------------------------------------------- /ch07/tests/unit/entity/test_order.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from domain.entities.order import Order 4 | from domain.entities.coffee import Coffee 5 | from domain.value_objects.money import Money 6 | 7 | 8 | class TestOrder(unittest.TestCase): 9 | def test_add_coffee(self): 10 | # Given 11 | coffee = Coffee(id="coffee-1", name="Latte", price=Money(500), stock=10) 12 | order = Order(id="order-1", customer_id="cust-1") 13 | 14 | # When 15 | order.add_coffee(coffee, 2) 16 | 17 | # Then 18 | self.assertEqual(len(order.items), 1) # (1) 항목 추가 확인 19 | self.assertEqual(order.items[0].quantity, 2) # (2) 수량 확인 20 | self.assertEqual(order.total_amount.amount, 1000) # (3) 총액 확인 21 | self.assertEqual(coffee.stock, 8) # (4) 재고 감소 확인 22 | 23 | 24 | if __name__ == "__main__": 25 | unittest.main() 26 | -------------------------------------------------------------------------------- /ch09/tests/unit/entity/test_order.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from domain.entities.order import Order 4 | from domain.entities.coffee import Coffee 5 | from domain.value_objects.money import Money 6 | 7 | 8 | class TestOrder(unittest.TestCase): 9 | def test_add_coffee(self): 10 | # Given 11 | coffee = Coffee(id="coffee-1", name="Latte", price=Money(500), stock=10) 12 | order = Order(id="order-1", customer_id="cust-1") 13 | 14 | # When 15 | order.add_coffee(coffee, 2) 16 | 17 | # Then 18 | self.assertEqual(len(order.items), 1) # (1) 항목 추가 확인 19 | self.assertEqual(order.items[0].quantity, 2) # (2) 수량 확인 20 | self.assertEqual(order.total_amount.amount, 1000) # (3) 총액 확인 21 | self.assertEqual(coffee.stock, 8) # (4) 재고 감소 확인 22 | 23 | 24 | if __name__ == "__main__": 25 | unittest.main() 26 | -------------------------------------------------------------------------------- /ch07/application/dtos/__init__.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | 4 | 5 | @dataclass 6 | class InputDto: 7 | pass 8 | 9 | 10 | @dataclass 11 | class OutputDto: 12 | pass 13 | 14 | 15 | @dataclass 16 | class CreateOrderInputDto(InputDto): 17 | customer_id: str 18 | coffee_id: str 19 | quantity: int 20 | 21 | 22 | @dataclass 23 | class CreateOrderOutputDto(OutputDto): 24 | order_id: str 25 | customer_id: str 26 | total_amount: int 27 | currency: str 28 | status: str 29 | items: List[dict] 30 | 31 | 32 | @dataclass 33 | class GetOrderInputDto(InputDto): 34 | order_id: str 35 | 36 | 37 | @dataclass 38 | class GetOrderOutputDto(OutputDto): 39 | order_id: str 40 | customer_id: str 41 | total_amount: int 42 | currency: str 43 | status: str 44 | items: List[dict] 45 | created_at: str 46 | -------------------------------------------------------------------------------- /ch09/application/dtos/__init__.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | 4 | 5 | @dataclass 6 | class InputDto: 7 | pass 8 | 9 | 10 | @dataclass 11 | class OutputDto: 12 | pass 13 | 14 | 15 | @dataclass 16 | class CreateOrderInputDto(InputDto): 17 | customer_id: str 18 | coffee_id: str 19 | quantity: int 20 | 21 | 22 | @dataclass 23 | class CreateOrderOutputDto(OutputDto): 24 | order_id: str 25 | customer_id: str 26 | total_amount: int 27 | currency: str 28 | status: str 29 | items: List[dict] 30 | 31 | 32 | @dataclass 33 | class GetOrderInputDto(InputDto): 34 | order_id: str 35 | 36 | 37 | @dataclass 38 | class GetOrderOutputDto(OutputDto): 39 | order_id: str 40 | customer_id: str 41 | total_amount: int 42 | currency: str 43 | status: str 44 | items: List[dict] 45 | created_at: str 46 | -------------------------------------------------------------------------------- /ch10/few-shot-prompt-with-constraints/src/domain/interfaces/repositories.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, Optional 3 | from ..entities.menu import Menu 4 | from ..entities.order import Order 5 | 6 | 7 | class MenuRepository(ABC): 8 | @abstractmethod 9 | def get_all(self) -> List[Menu]: 10 | pass 11 | 12 | @abstractmethod 13 | def get_by_id(self, menu_id: str) -> Optional[Menu]: 14 | pass 15 | 16 | @abstractmethod 17 | def update(self, menu: Menu) -> None: 18 | pass 19 | 20 | 21 | class OrderRepository(ABC): 22 | @abstractmethod 23 | def create(self, order: Order) -> None: 24 | pass 25 | 26 | @abstractmethod 27 | def get_by_id(self, order_id: str) -> Optional[Order]: 28 | pass 29 | 30 | @abstractmethod 31 | def update(self, order: Order) -> None: 32 | pass -------------------------------------------------------------------------------- /ch10/simple-prompt/src/infrastructure/models.py: -------------------------------------------------------------------------------- 1 | from pynamodb.models import Model 2 | from pynamodb.attributes import UnicodeAttribute, NumberAttribute, UTCDateTimeAttribute 3 | from datetime import datetime 4 | 5 | class MenuModel(Model): 6 | class Meta: 7 | table_name = 'Menu' 8 | region = 'ap-northeast-2' 9 | 10 | id = UnicodeAttribute(hash_key=True) 11 | name = UnicodeAttribute() 12 | price = NumberAttribute() 13 | category = UnicodeAttribute() 14 | stock = NumberAttribute() 15 | 16 | class OrderModel(Model): 17 | class Meta: 18 | table_name = 'Order' 19 | region = 'ap-northeast-2' 20 | 21 | id = UnicodeAttribute(hash_key=True) 22 | customer_id = UnicodeAttribute() 23 | menu_id = UnicodeAttribute() 24 | quantity = NumberAttribute() 25 | status = UnicodeAttribute() 26 | created_at = UTCDateTimeAttribute() 27 | updated_at = UTCDateTimeAttribute(null=True) -------------------------------------------------------------------------------- /ch10/few-shot-prompt-with-constraints/src/domain/entities/order.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from typing import Optional, Dict 4 | 5 | 6 | @dataclass 7 | class Order: 8 | order_id: str 9 | customer_id: str 10 | menu_id: str 11 | quantity: int 12 | status: str 13 | options: Dict[str, str] 14 | created_at: datetime 15 | 16 | @classmethod 17 | def create(cls, order_id: str, customer_id: str, menu_id: str, 18 | quantity: int, options: Dict[str, str]) -> 'Order': 19 | return cls( 20 | order_id=order_id, 21 | customer_id=customer_id, 22 | menu_id=menu_id, 23 | quantity=quantity, 24 | status="created", 25 | options=options, 26 | created_at=datetime.utcnow() 27 | ) 28 | 29 | def update_status(self, new_status: str) -> None: 30 | self.status = new_status -------------------------------------------------------------------------------- /ch10/few-shot-prompt-with-constraints/src/application/controllers/menu_controller.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException 2 | from ...domain.interfaces.boundaries import GetMenusInputBoundary, OutputBoundary 3 | 4 | router = APIRouter() 5 | 6 | 7 | class MenuController: 8 | def __init__( 9 | self, 10 | get_menus_usecase: GetMenusInputBoundary, 11 | presenter: OutputBoundary 12 | ): 13 | self._get_menus_usecase = get_menus_usecase 14 | self._presenter = presenter 15 | 16 | async def get_menus(self): 17 | try: 18 | menus = self._get_menus_usecase.execute() 19 | return self._presenter.present_success( 20 | data=menus, 21 | message="Menus retrieved successfully" 22 | ) 23 | except Exception as e: 24 | return self._presenter.present_error( 25 | message="Database error occurred" 26 | ) -------------------------------------------------------------------------------- /ch06/6_2_controller/type2/presentation/fastapi.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException 2 | from pydantic import BaseModel 3 | from .usecases import CalculateAverageUseCase # CalculateAverageUseCase 클래스가 존재한다고 가정 4 | 5 | app = FastAPI() 6 | 7 | 8 | class ScoreRequest(BaseModel): 9 | student_id: str 10 | calculation_type: str 11 | 12 | 13 | @app.post("/calculate") 14 | async def calculate_score(request: ScoreRequest): 15 | try: 16 | if request.calculation_type != "average": 17 | raise HTTPException(status_code=400, detail="Unsupported calculation type") 18 | 19 | # 유스케이스 직접 호출 20 | usecase = CalculateAverageUseCase() 21 | average = usecase.execute(request.student_id) 22 | 23 | # 응답 변환 24 | return {"student_id": request.student_id, "result": average, "status": "success"} 25 | except Exception as e: 26 | raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}") 27 | -------------------------------------------------------------------------------- /ch06/6_4_gateway/controllers.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from .usecase import ScoreInputDTO 3 | 4 | 5 | @dataclass 6 | class ScoreRequestDTO: 7 | student_id: str 8 | calculation_type: str 9 | 10 | 11 | @dataclass 12 | class ScoreResponseDTO: 13 | student_id: str 14 | result: float 15 | status: str 16 | 17 | 18 | class CalculateScoreController: 19 | def __init__(self, calculate_average_usecase: "CalculateAverageUseCase"): 20 | self.calculate_average_usecase = calculate_average_usecase 21 | 22 | def execute(self, request): 23 | if request.calculation_type != "average": 24 | raise ValueError("Unsupported calculation type") 25 | 26 | # 입력 DTO를 유스케이스에 전달 27 | input_dto = ScoreInputDTO(student_id=request.student_id) 28 | self.calculate_average_usecase.execute(input_dto) 29 | 30 | # 프레젠터에서 결과 가져와서 응답 31 | return self.calculate_average_usecase.presenter.present() 32 | -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/main.py: -------------------------------------------------------------------------------- 1 | from contextlib import asynccontextmanager 2 | from typing import AsyncGenerator 3 | 4 | from fastapi import FastAPI 5 | from mangum import Mangum 6 | 7 | from .config.database import get_db 8 | from .config.dependencies import Dependencies 9 | 10 | 11 | @asynccontextmanager 12 | async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: 13 | # Startup: Register routes 14 | db = next(get_db()) 15 | menu_controller, order_controller, customer_controller = Dependencies.configure(db) 16 | app.include_router(menu_controller.register_routes()) 17 | app.include_router(order_controller.register_routes()) 18 | app.include_router(customer_controller.register_routes()) 19 | yield 20 | # Shutdown: Clean up resources 21 | db.close() 22 | 23 | 24 | app = FastAPI( 25 | title="Coffee Order API", 26 | lifespan=lifespan, 27 | ) 28 | 29 | # AWS Lambda handler 30 | handler = Mangum(app) 31 | -------------------------------------------------------------------------------- /ch09/application/ports/outbound/__init__.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod, ABC 2 | 3 | from application.dtos import OutputDto 4 | from domain.shared.aggregate_root import DomainEvent 5 | 6 | 7 | class OutputBoundary(metaclass=ABCMeta): 8 | @abstractmethod 9 | def set_result(self, output_dto: OutputDto) -> None: 10 | pass 11 | 12 | @abstractmethod 13 | def present(self) -> dict: 14 | pass 15 | 16 | 17 | class DomainEventPublisher(ABC): 18 | """도메인 이벤트를 버스/구독자에게 발행하기 위한 아웃바운드 포트입니다.""" 19 | 20 | @abstractmethod 21 | def publish(self, event: DomainEvent) -> None: 22 | pass 23 | 24 | @abstractmethod 25 | def publish_all(self, events: list[DomainEvent]) -> None: 26 | pass 27 | 28 | 29 | class PaymentGateway(ABC): 30 | """외부 결제 게이트웨이 연동을 위한 아웃바운드 포트입니다.""" 31 | 32 | @abstractmethod 33 | def approve(self, order_id: str, amount: int, currency: str) -> bool: 34 | """결제 승인 시도. 성공 여부를 반환합니다.""" 35 | pass 36 | -------------------------------------------------------------------------------- /ch10/few-shot-prompt/src/infrastructure/models.py: -------------------------------------------------------------------------------- 1 | from pynamodb.models import Model 2 | from pynamodb.attributes import ( 3 | UnicodeAttribute, 4 | NumberAttribute, 5 | UTCDateTimeAttribute, 6 | MapAttribute, 7 | ) 8 | from datetime import datetime 9 | 10 | 11 | class MenuModel(Model): 12 | class Meta: 13 | table_name = "menus" 14 | region = "ap-northeast-2" 15 | 16 | menu_id = UnicodeAttribute(hash_key=True) 17 | name = UnicodeAttribute() 18 | price = NumberAttribute() 19 | category = UnicodeAttribute() 20 | stock = NumberAttribute() 21 | 22 | 23 | class OrderModel(Model): 24 | class Meta: 25 | table_name = "orders" 26 | region = "ap-northeast-2" 27 | 28 | order_id = UnicodeAttribute(hash_key=True) 29 | customer_id = UnicodeAttribute() 30 | menu_id = UnicodeAttribute() 31 | quantity = NumberAttribute() 32 | options = MapAttribute() 33 | status = UnicodeAttribute() 34 | created_at = UTCDateTimeAttribute() -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt-with-guardrail/src/domain/usecases/menu_usecases.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from ..dtos.menu_dtos import MenuListResponseDTO, MenuResponseDTO 4 | from ..repositories.menu_repository import MenuRepository 5 | 6 | 7 | class GetMenuListUseCase(ABC): 8 | @abstractmethod 9 | def execute(self) -> MenuListResponseDTO: 10 | pass 11 | 12 | 13 | class GetMenuListUseCaseImpl(GetMenuListUseCase): 14 | def __init__(self, menu_repository: MenuRepository): 15 | self.menu_repository = menu_repository 16 | 17 | def execute(self) -> MenuListResponseDTO: 18 | menus = self.menu_repository.get_all() 19 | menu_dtos = [ 20 | MenuResponseDTO( 21 | menu_id=menu.menu_id, 22 | name=menu.name, 23 | price=menu.price, 24 | category=menu.category, 25 | ) 26 | for menu in menus 27 | ] 28 | return MenuListResponseDTO(menus=menu_dtos) 29 | -------------------------------------------------------------------------------- /ch10/few-shot-prompt/README.md: -------------------------------------------------------------------------------- 1 | # Coffee Order API 2 | 3 | 클린 아키텍처를 적용한 온라인 커피 주문 API 서비스입니다. 4 | 5 | ## 기술 스택 6 | - Python 3.9+ 7 | - FastAPI 8 | - DynamoDB (pynamodb) 9 | - AWS Lambda 10 | - Serverless Framework 11 | 12 | ## 프로젝트 구조 13 | ``` 14 | src/ 15 | ├── domain/ # 엔티티와 리포지토리 인터페이스 16 | ├── infrastructure/ # 리포지토리 구현체 17 | ├── application/ # 유스케이스 18 | └── interfaces/ # API 엔드포인트와 스키마 19 | tests/ 20 | ├── unit/ # 단위 테스트 21 | └── integration/ # 통합 테스트 22 | ``` 23 | 24 | ## 설치 및 실행 25 | 26 | 1. 의존성 설치: 27 | ```bash 28 | pip install -r requirements.txt 29 | ``` 30 | 31 | 2. 환경 변수 설정: 32 | ```bash 33 | cp .env.example .env 34 | # .env 파일을 적절히 수정 35 | ``` 36 | 37 | 3. 로컬 실행: 38 | ```bash 39 | uvicorn src.interfaces.api:app --reload 40 | ``` 41 | 42 | 4. 배포: 43 | ```bash 44 | serverless deploy 45 | ``` 46 | 47 | ## API 엔드포인트 48 | 49 | - GET /menu - 메뉴 목록 조회 50 | - POST /order - 커피 주문 생성 51 | - GET /order/{orderId} - 주문 상태 확인 52 | 53 | ## 테스트 실행 54 | 55 | ```bash 56 | pytest tests/ 57 | ``` -------------------------------------------------------------------------------- /ch10/chain-of-thought-prompt/src/domain/interfaces/repositories.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, Optional 3 | from ..entities.menu import Menu 4 | from ..entities.order import Order 5 | 6 | class MenuRepository(ABC): 7 | @abstractmethod 8 | def get_all(self) -> List[Menu]: 9 | """모든 메뉴 조회""" 10 | pass 11 | 12 | @abstractmethod 13 | def get_by_id(self, menu_id: str) -> Optional[Menu]: 14 | """ID로 메뉴 조회""" 15 | pass 16 | 17 | @abstractmethod 18 | def update(self, menu: Menu) -> None: 19 | """메뉴 정보 업데이트""" 20 | pass 21 | 22 | class OrderRepository(ABC): 23 | @abstractmethod 24 | def create(self, order: Order) -> None: 25 | """주문 생성""" 26 | pass 27 | 28 | @abstractmethod 29 | def get_by_id(self, order_id: str) -> Optional[Order]: 30 | """ID로 주문 조회""" 31 | pass 32 | 33 | @abstractmethod 34 | def update(self, order: Order) -> None: 35 | """주문 정보 업데이트""" 36 | pass -------------------------------------------------------------------------------- /ch06/6_6_entity/README.md: -------------------------------------------------------------------------------- 1 | ## 실행 방법 2 | 3 | ### 1. FastAPI 서버 실행 4 | ```bash 5 | cd /Users/alphahacker/clean-architecture-with-python-test-3 6 | python -m uvicorn ch06.6_6_entity.fastapi:app --reload --port 8000 7 | ``` 8 | 9 | ### 2. API 테스트 10 | ```bash 11 | curl -X POST "http://localhost:8000/calculate" \ 12 | -H "Content-Type: application/json" \ 13 | -d '{"student_id": "student001", "calculation_type": "average"}' 14 | 15 | # 유효하지 않은 점수 (음수) 16 | curl -X POST "http://localhost:8000/calculate" \ 17 | -H "Content-Type: application/json" \ 18 | -d '{"student_id": "student_invalid", "calculation_type": "average"}' 19 | 20 | # 유효하지 않은 점수 (100점 초과) 21 | curl -X POST "http://localhost:8000/calculate" \ 22 | -H "Content-Type: application/json" \ 23 | -d '{"student_id": "student_over100", "calculation_type": "average"}' 24 | 25 | # 빈 데이터 26 | curl -X POST "http://localhost:8000/calculate" \ 27 | -H "Content-Type: application/json" \ 28 | -d '{"student_id": "student_empty", "calculation_type": "average"}' 29 | ``` -------------------------------------------------------------------------------- /ch09/adapter/events/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, List, DefaultDict 2 | from collections import defaultdict 3 | 4 | from application.ports.outbound import DomainEventPublisher 5 | from domain.shared.aggregate_root import DomainEvent 6 | 7 | 8 | class InMemoryEventBus(DomainEventPublisher): 9 | """데모/테스트를 위한 최소한의 인메모리 퍼브/섭(pub/sub) 버스입니다. 10 | 11 | 프로덕션 용도는 아니며, 이벤트 드리븐 흐름을 설명하기 위한 간단한 구현입니다. 12 | """ 13 | 14 | def __init__(self) -> None: 15 | self._subscribers: DefaultDict[str, List[Callable[[DomainEvent], None]]] = defaultdict(list) 16 | 17 | def subscribe(self, event_name: str, handler: Callable[[DomainEvent], None]) -> None: 18 | self._subscribers[event_name].append(handler) 19 | 20 | def publish(self, event: DomainEvent) -> None: 21 | for handler in list(self._subscribers.get(event.name, [])): 22 | handler(event) 23 | 24 | def publish_all(self, events: List[DomainEvent]) -> None: 25 | for event in events: 26 | self.publish(event) 27 | 28 | -------------------------------------------------------------------------------- /ch06/6_7_presenter/views.py: -------------------------------------------------------------------------------- 1 | # Views Layer 2 | from .viewmodels import ScoreViewModel 3 | 4 | 5 | class WebScoreView: 6 | def display(self, view_model: ScoreViewModel) -> str: 7 | styled_grade = f"{view_model.grade}" 8 | return f""" 9 |
평균: {view_model.average}
13 |등급: {styled_grade}
14 |상태: {view_model.status}
15 |