From b11cc2f725025dcc84065164d4fba816e4da92b9 Mon Sep 17 00:00:00 2001 From: "h.nejati" Date: Sat, 1 Mar 2025 12:36:01 +0330 Subject: [PATCH 1/2] change docs --- .gitignore | 2 + README.md | 120 ++++++++-------- docs/source/api_reference/adapters.rst | 82 +++-------- docs/source/api_reference/configs.rst | 28 ++-- docs/source/api_reference/helpers.rst | 124 ++++------------- docs/source/api_reference/index.rst | 11 +- docs/source/api_reference/models.rst | 83 ++--------- docs/source/api_reference/utils.rst | 115 ++-------------- docs/source/architecture.rst | 103 ++++---------- docs/source/conf.py | 100 +++++++------- docs/source/contributing.rst | 12 +- docs/source/development.rst | 165 ++++------------------ docs/source/features.rst | 130 +++++------------- docs/source/index.rst | 20 ++- docs/source/installation.rst | 63 ++++----- docs/source/license.rst | 2 +- docs/source/usage.rst | 182 +++++++++++-------------- scripts/project_tree.py | 100 ++++++++++++++ 18 files changed, 521 insertions(+), 921 deletions(-) create mode 100644 scripts/project_tree.py diff --git a/.gitignore b/.gitignore index 4004d65a..94852a26 100644 --- a/.gitignore +++ b/.gitignore @@ -238,3 +238,5 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +.ruff_cache diff --git a/README.md b/README.md index bd1282bb..e92e6a11 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,15 @@ [![Repo Size](https://img.shields.io/github/repo-size/SyntaxArc/ArchiPy)](https://github.com/SyntaxArc/ArchiPy) [![Code Size](https://img.shields.io/github/languages/code-size/SyntaxArc/ArchiPy)](https://github.com/SyntaxArc/ArchiPy) -## **Perfect for Structured Design** +## **Structured Python Development Made Simple** -ArchiPy provides a robust architecture framework for building scalable and maintainable Python applications. It integrates modern Python tools and libraries to streamline development, testing, and deployment processes. +ArchiPy is a Python framework designed to provide a standardized, scalable, and maintainable architecture for modern applications. Built with Python 3.13+, it offers a suite of tools, utilities, and best practices to streamline configuration management, testing, and development workflows while adhering to clean architecture principles. --- ## πŸ“‹ Table of Contents +- [Goals](#-goals) - [Features](#-features) - [Prerequisites](#-prerequisites) - [Installation](#-installation) @@ -41,31 +42,55 @@ ArchiPy provides a robust architecture framework for building scalable and maint --- +## 🎯 Goals + +ArchiPy is built with the following objectives in mind: + +1. **Configuration Management & Injection** + Simplify and standardize configuration handling with tools like `base_config.py` and dependency injection, ensuring consistent and reusable setups across projects. + +2. **Common Adapters & Mocks** + Provide ready-to-use adapters (e.g., Redis, SQLAlchemy, email) with corresponding mocks for seamless delegation and testing without external dependencies. + +3. **Standardized Entities & DTOs** + Offer base entities, data transfer objects (DTOs), and type definitions to enforce consistency and reduce boilerplate in data modeling. + +4. **Common Helpers for Everyday Tasks** + Include a rich set of utilities, decorators (e.g., retry, singleton), and interceptors (e.g., FastAPI rate limiting, gRPC tracing) to simplify routine development work. + +5. **Behavior-Driven Development (BDD) Support** + Integrate `behave` with pre-configured examples for synchronous and asynchronous scenario testing, enabling robust feature validation. + +6. **Best Practices & Development Structure** + Leverage `poetry`, `pre-commit`, `pyproject.toml`, and tools like `ruff` and `black` to enforce coding standards and provide an optimal Python development structure. + +--- + ## ✨ Features -- **Modern Python Stack**: Built with Python 3.13 and leveraging tools like `pydantic`, `fastapi`, `gRPC` and `sqlalchemy`. -- **Modular Design**: Optional dependencies for Redis, gRPC, PostgreSQL, Prometheus, and more, including `fakeredis` for mock Redis testing. -- **Type Safety**: Enforced by `mypy` and `pydantic` for robust, error-resistant code. -- **Comprehensive Testing**: Integrated with `behave` for behavior-driven development. -- **Code Quality Tools**: Uses `ruff` and `black` for clean and consistent code. -- **Pre-commit Hooks**: Automates code quality checks before commits. -- **Dependency Management**: Managed by `poetry` for reproducible builds. +- **Config Standardization**: Tools for managing and injecting configurations effortlessly (`base_config`, `config_template`). +- **Adapters & Mocks**: Pre-built adapters for Redis, SQLAlchemy, and email, with mocks like `redis_mocks` and `sqlalchemy_mocks` for testing. +- **Data Standardization**: Base entities (`base_entities.py`), DTOs (e.g., `pagination_dto`, `error_dto`), and type safety with `pydantic` and `mypy`. +- **Helper Utilities**: A collection of reusable tools including `datetime_utils`, `jwt_utils`, `password_utils`, and decorators like `sqlalchemy_atomic` and `timing_decorator`. +- **BDD Testing**: Fully integrated `behave` setup with feature files (e.g., `app_utils.feature`) and step definitions for sync/async testing. +- **Modern Tooling**: Dependency management with `poetry`, code quality with `ruff` and `black`, and pre-commit hooks for automated checks. +- **Modular Design**: Optional dependencies for Redis, FastAPI, gRPC, PostgreSQL, and more, installable via `archipy[feature]`. --- ## πŸ› οΈ Prerequisites -Before starting with ArchiPy, ensure you have: +Before using ArchiPy, ensure you have: - **Python 3.13 or higher** - ArchiPy is compatible with Python 3.13+. + Check your version with: ```bash python --version ``` - If your Python version is lower than 3.13, [download and install the latest version of Python](https://www.python.org/downloads/). + [Download Python 3.13+](https://www.python.org/downloads/) if needed. -- **Poetry** (for dependency management) - Poetry is required to manage dependencies and install the project. If you don't have Poetry installed, follow the [official installation guide](https://python-poetry.org/docs/). +- **Poetry** + Install Poetry for dependency management: [Poetry Installation Guide](https://python-poetry.org/docs/). --- @@ -73,20 +98,18 @@ Before starting with ArchiPy, ensure you have: ### From PyPI -The simplest way to install ArchiPy: +Install ArchiPy easily with `pip` or `poetry`: ```bash # Basic installation pip install archipy -# With optional dependencies +# With optional dependencies (e.g., Redis and FastAPI) pip install archipy[redis,fastapi] ``` -Or using Poetry: - +Using Poetry: ```bash -# Basic installation poetry add archipy # With optional dependencies @@ -95,20 +118,17 @@ poetry add archipy[redis,fastapi] ### From Source -For development or the latest features: - -1. **Clone the Repository** +For development or cutting-edge features: +1. Clone the repository: ```bash git clone https://github.com/SyntaxArc/ArchiPy.git cd ArchiPy ``` - -2. **Set Up the Project** +2. Set up the project: ```bash make setup ``` - -3. **Install Dependencies** +3. Install dependencies: ```bash make install ``` @@ -119,7 +139,7 @@ For development or the latest features: ### Optional Dependencies -ArchiPy provides modular functionality through optional dependencies: +ArchiPy’s modular design lets you install only what you need: | Feature | Installation Command | Description | |----------------------|---------------------------------|--------------------------------------------------| @@ -137,32 +157,20 @@ ArchiPy provides modular functionality through optional dependencies: | aiosqlite | `archipy[aiosqlite]` | Asynchronous SQLite database support | | FakeRedis | `archipy[fakeredis]` | Mock Redis client for testing without a server | -### Troubleshooting Installation - -If you encounter installation issues, check that: - -1. Your Python version is **3.13 or higher** -2. Your package manager (`pip` or `poetry`) is up to date -3. You have the necessary build tools installed (`setuptools`, `wheel`) - --- ## πŸ› οΈ Development ### Common Commands -Run `make help` to see all available commands. Here are some frequently used ones: - -- **Format Code** 🧹 `make format` -- **Run Linters** πŸ” `make lint` -- **Run Tests** πŸ§ͺ `make behave` -- **Build the Project** πŸ—οΈ `make build` -- **Clean Build Artifacts** 🧽 `make clean` -- **Run All Checks** `make check` -- **Run CI Pipeline Locally** `make ci` -- **Update Dependencies** `make update` +Run `make help` for all commands. Key ones include: +- **Format Code**: `make format` +- **Run Tests**: `make behave` +- **Lint Code**: `make lint` +- **Build Project**: `make build` +- **Update Dependencies**: `make update` -### Version Management +### Versioning We follow [Semantic Versioning (SemVer)](https://semver.org/) principles: @@ -181,13 +189,7 @@ For more detailed information about development processes, refer to our [contrib ## 🀝 Contributing -We welcome contributions to ArchiPy! Please check out our [contribution guidelines](CONTRIBUTING.md) for details on: - -- Setting up your development environment -- Development workflow -- Submitting effective pull requests -- Code style expectations -- Testing requirements +Contributions are welcome! See our [contribution guidelines](CONTRIBUTING.md) for details on setup, workflow, and standards. --- @@ -214,10 +216,8 @@ For questions or feedback, feel free to reach out: ## πŸ”— Links -- **GitHub Repository**: [https://github.com/SyntaxArc/ArchiPy](https://github.com/SyntaxArc/ArchiPy) -- **Documentation**: [https://archipy.readthedocs.io/](https://archipy.readthedocs.io/) -- **Bug Tracker**: [https://github.com/SyntaxArc/ArchiPy/issues](https://github.com/SyntaxArc/ArchiPy/issues) -- **Contributing Guidelines**: [https://github.com/SyntaxArc/ArchiPy/blob/master/CONTRIBUTING.md](https://github.com/SyntaxArc/ArchiPy/blob/master/CONTRIBUTING.md) -- **Code of Conduct**: [https://github.com/SyntaxArc/ArchiPy/blob/master/CODE_OF_CONDUCT.md](https://github.com/SyntaxArc/ArchiPy/blob/master/CODE_OF_CONDUCT.md) - ---- +- [GitHub](https://github.com/SyntaxArc/ArchiPy) +- [Documentation](https://archipy.readthedocs.io/) +- [Issues](https://github.com/SyntaxArc/ArchiPy/issues) +- [Contributing](https://github.com/SyntaxArc/ArchiPy/blob/master/CONTRIBUTING.md) +- [Code of Conduct](https://github.com/SyntaxArc/ArchiPy/blob/master/CODE_OF_CONDUCT.md) diff --git a/docs/source/api_reference/adapters.rst b/docs/source/api_reference/adapters.rst index 8ed31d6d..cb489129 100644 --- a/docs/source/api_reference/adapters.rst +++ b/docs/source/api_reference/adapters.rst @@ -1,16 +1,18 @@ .. _api_adapters: Adapters -======= +======== Overview -------- -The adapters module contains implementations of ports that connect the application to external systems. +Adapters connect ArchiPy to external systems (e.g., databases, Redis, email) via well-defined ports. This module supports the goal of providing common adapters with mocks for delegation and testing, enabling seamless integration and isolated unit tests. +Adapters +-------- ORM Adapters (SQLAlchemy) -~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters :members: @@ -28,7 +30,7 @@ ORM Adapters (SQLAlchemy) :show-inheritance: Redis Adapters -~~~~~~~~~~~~ +~~~~~~~~~~~~~~ .. automodule:: archipy.adapters.redis.redis_adapters :members: @@ -36,7 +38,7 @@ Redis Adapters :show-inheritance: Email Adapters -~~~~~~~~~~~~ +~~~~~~~~~~~~~~ .. automodule:: archipy.adapters.email.email_adapter :members: @@ -44,10 +46,12 @@ Email Adapters :show-inheritance: Ports ----- +----- + +Define interfaces for adapters, ensuring flexibility and testability. ORM Ports -~~~~~~~~ +~~~~~~~~~ .. automodule:: archipy.adapters.orm.sqlalchemy.sqlalchemy_ports :members: @@ -60,7 +64,7 @@ ORM Ports :show-inheritance: Redis Ports -~~~~~~~~~ +~~~~~~~~~~~ .. automodule:: archipy.adapters.redis.redis_ports :members: @@ -68,18 +72,18 @@ Redis Ports :show-inheritance: Email Ports -~~~~~~~~~ +~~~~~~~~~~~ .. automodule:: archipy.adapters.email.email_port :members: :undoc-members: :show-inheritance: -SQLAlchemy Adapter Classes ------------------------- +Key Classes +----------- SqlAlchemyAdapter -~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~ .. autoclass:: archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters.SqlAlchemyAdapter :members: @@ -87,34 +91,15 @@ SqlAlchemyAdapter :show-inheritance: AsyncSqlAlchemyAdapter -~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters.AsyncSqlAlchemyAdapter :members: :undoc-members: :show-inheritance: -SessionManagerAdapter -~~~~~~~~~~~~~~~~~~ - -.. autoclass:: archipy.adapters.orm.sqlalchemy.session_manager_adapters.SessionManagerAdapter - :members: - :undoc-members: - :show-inheritance: - -AsyncSessionManagerAdapter -~~~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: archipy.adapters.orm.sqlalchemy.session_manager_adapters.AsyncSessionManagerAdapter - :members: - :undoc-members: - :show-inheritance: - -Redis Adapter Classes ------------------- - RedisAdapter -~~~~~~~~~~ +~~~~~~~~~~~~ .. autoclass:: archipy.adapters.redis.redis_adapters.RedisAdapter :members: @@ -122,44 +107,17 @@ RedisAdapter :show-inheritance: AsyncRedisAdapter -~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~ .. autoclass:: archipy.adapters.redis.redis_adapters.AsyncRedisAdapter :members: :undoc-members: :show-inheritance: -Email Adapter Classes ------------------- - EmailAdapter -~~~~~~~~~~ +~~~~~~~~~~~~ .. autoclass:: archipy.adapters.email.email_adapter.EmailAdapter :members: :undoc-members: :show-inheritance: - -EmailConnectionManager -~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: archipy.adapters.email.email_adapter.EmailConnectionManager - :members: - :undoc-members: - :show-inheritance: - -EmailConnectionPool -~~~~~~~~~~~~~~~~ - -.. autoclass:: archipy.adapters.email.email_adapter.EmailConnectionPool - :members: - :undoc-members: - :show-inheritance: - -AttachmentHandler -~~~~~~~~~~~~~~ - -.. autoclass:: archipy.adapters.email.email_adapter.AttachmentHandler - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api_reference/configs.rst b/docs/source/api_reference/configs.rst index 0939196f..b7084c92 100644 --- a/docs/source/api_reference/configs.rst +++ b/docs/source/api_reference/configs.rst @@ -1,16 +1,18 @@ .. _api_configs: Configs -====== +======= Overview -------- -The configs module contains configuration templates for various services used in the application. +The configs module provides tools for standardized configuration management and injection, supporting consistent setup across services like databases, Redis, and email. +Configuration Classes +--------------------- Base Config -~~~~~~~~~ +~~~~~~~~~~~ .. automodule:: archipy.configs.base_config :members: @@ -18,26 +20,18 @@ Base Config :show-inheritance: Config Templates -~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~ .. automodule:: archipy.configs.config_template :members: :undoc-members: :show-inheritance: -Environment Type -~~~~~~~~~~~~~ - -.. automodule:: archipy.configs.environment_type - :members: - :undoc-members: - :show-inheritance: - Key Classes ---------- +----------- BaseConfig -~~~~~~~~ +~~~~~~~~~~ .. autoclass:: archipy.configs.base_config.BaseConfig :members: @@ -45,7 +39,7 @@ BaseConfig :show-inheritance: SqlAlchemyConfig -~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~ .. autoclass:: archipy.configs.config_template.SqlAlchemyConfig :members: @@ -53,7 +47,7 @@ SqlAlchemyConfig :show-inheritance: RedisConfig -~~~~~~~~~ +~~~~~~~~~~~ .. autoclass:: archipy.configs.config_template.RedisConfig :members: @@ -61,7 +55,7 @@ RedisConfig :show-inheritance: EmailConfig -~~~~~~~~~ +~~~~~~~~~~~ .. autoclass:: archipy.configs.config_template.EmailConfig :members: diff --git a/docs/source/api_reference/helpers.rst b/docs/source/api_reference/helpers.rst index dc5e6998..8b4a8c35 100644 --- a/docs/source/api_reference/helpers.rst +++ b/docs/source/api_reference/helpers.rst @@ -1,51 +1,44 @@ .. _api_helpers: Helpers -====== +======= Overview -------- -The helpers module contains utility classes and functions for various tasks. +The helpers module offers utilities, decorators, and interceptors to enhance productivity and simplify common development tasks, such as retry logic, rate limiting, and tracing. +Decorators +---------- -Interceptors -~~~~~~~~~~ - -FastAPI Interceptors -^^^^^^^^^^^^^^^^^^ - -.. automodule:: archipy.helpers.interceptors.fastapi.rate_limit.fastapi_rest_rate_limit_handler +.. automodule:: archipy.helpers.decorators.retry :members: :undoc-members: :show-inheritance: -gRPC Interceptors -^^^^^^^^^^^^^^^ - -Base -'''' - -.. automodule:: archipy.helpers.interceptors.grpc.base.base_grpc_client_interceptor +.. automodule:: archipy.helpers.decorators.singleton :members: :undoc-members: :show-inheritance: -.. automodule:: archipy.helpers.interceptors.grpc.base.base_grpc_server_interceptor +.. automodule:: archipy.helpers.decorators.sqlalchemy_atomic :members: :undoc-members: :show-inheritance: -Metrics -''''''' +Interceptors +------------ + +FastAPI Interceptors +~~~~~~~~~~~~~~~~~~~~ -.. automodule:: archipy.helpers.interceptors.grpc.metric.grpc_server_metric_interceptor +.. automodule:: archipy.helpers.interceptors.fastapi.rate_limit.fastapi_rest_rate_limit_handler :members: :undoc-members: :show-inheritance: -Tracing -''''''' +gRPC Interceptors +~~~~~~~~~~~~~~~~~ .. automodule:: archipy.helpers.interceptors.grpc.trace.grpc_client_trace_interceptor :members: @@ -58,100 +51,33 @@ Tracing :show-inheritance: Metaclasses -~~~~~~~~~ +----------- .. automodule:: archipy.helpers.metaclasses.singleton :members: :undoc-members: :show-inheritance: -Utils -~~~~ - -.. automodule:: archipy.helpers.utils.app_utils - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: archipy.helpers.utils.base_utils - :members: - :undoc-members: - :show-inheritance: - Key Classes ---------- +----------- -FastAPI Rate Limit Handler -~~~~~~~~~~~~~~~~~~~~~~~ +Retry Decorator +~~~~~~~~~~~~~~~ -.. autoclass:: archipy.helpers.interceptors.fastapi.rate_limit.fastapi_rest_rate_limit_handler.FastAPIRestRateLimitHandler - :members: - :undoc-members: - :show-inheritance: - -gRPC Client Interceptors -~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: archipy.helpers.interceptors.grpc.base.base_grpc_client_interceptor.BaseGrpcClientInterceptor - :members: - :undoc-members: - :show-inheritance: +.. autofunction:: archipy.helpers.decorators.retry.retry -.. autoclass:: archipy.helpers.interceptors.grpc.base.base_grpc_client_interceptor.BaseAsyncGrpcClientInterceptor - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.helpers.interceptors.grpc.trace.grpc_client_trace_interceptor.GrpcClientTraceInterceptor - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.helpers.interceptors.grpc.trace.grpc_client_trace_interceptor.AsyncGrpcClientTraceInterceptor - :members: - :undoc-members: - :show-inheritance: - -gRPC Server Interceptors -~~~~~~~~~~~~~~~~~~~~~ - -.. autoclass:: archipy.helpers.interceptors.grpc.base.base_grpc_server_interceptor.BaseGrpcServerInterceptor - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.helpers.interceptors.grpc.trace.grpc_server_trace_interceptor.GrpcServerTraceInterceptor - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.helpers.interceptors.grpc.metric.grpc_server_metric_interceptor.GrpcServerMetricInterceptor - :members: - :undoc-members: - :show-inheritance: - -Singleton Metaclass -~~~~~~~~~~~~~~~~~ +Singleton +~~~~~~~~~ .. autoclass:: archipy.helpers.metaclasses.singleton.Singleton :members: :undoc-members: :show-inheritance: -Application Utils -~~~~~~~~~~~~~~ +FastAPIRestRateLimitHandler +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. autoclass:: archipy.helpers.utils.app_utils.AppUtils - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.helpers.utils.app_utils.FastAPIUtils - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.helpers.utils.app_utils.FastAPIExceptionHandler +.. autoclass:: archipy.helpers.interceptors.fastapi.rate_limit.fastapi_rest_rate_limit_handler.FastAPIRestRateLimitHandler :members: :undoc-members: :show-inheritance: diff --git a/docs/source/api_reference/index.rst b/docs/source/api_reference/index.rst index 913f3454..78ca6c8e 100644 --- a/docs/source/api_reference/index.rst +++ b/docs/source/api_reference/index.rst @@ -1,9 +1,16 @@ .. _api_reference: API Reference -============ +============= -This section provides detailed API documentation for ArchiPy's modules and components. +This section documents ArchiPy’s modules and classes, designed to support a standardized, testable, and scalable Python architecture. The API reflects the project’s core goals: + +- **Configuration Management**: Standardized config handling and injection. +- **Adapters & Mocks**: Interfaces and implementations for external systems with testing mocks. +- **Data Standardization**: Base entities, DTOs, and types for consistency. +- **Helper Utilities**: Tools, decorators, and interceptors for productivity. +- **BDD Support**: Integrated with testing utilities (see `features/` for examples). +- **Best Practices**: Structured for modern Python development. .. toctree:: :maxdepth: 2 diff --git a/docs/source/api_reference/models.rst b/docs/source/api_reference/models.rst index 3c57d7be..1ba435da 100644 --- a/docs/source/api_reference/models.rst +++ b/docs/source/api_reference/models.rst @@ -1,16 +1,15 @@ .. _api_models: Models -===== +====== Overview -------- -The models module contains data structures used throughout the application. - +The models module standardizes data structures with base entities, DTOs, errors, and types, ensuring consistency across the application. DTOs (Data Transfer Objects) -~~~~~~~~~~~~~~~~~~~~~~~~~ +--------------------------- .. automodule:: archipy.models.dtos.base_dtos :members: @@ -27,11 +26,6 @@ DTOs (Data Transfer Objects) :undoc-members: :show-inheritance: -.. automodule:: archipy.models.dtos.fastapi_exception_response_dto - :members: - :undoc-members: - :show-inheritance: - .. automodule:: archipy.models.dtos.pagination_dto :members: :undoc-members: @@ -53,7 +47,7 @@ DTOs (Data Transfer Objects) :show-inheritance: Entities -~~~~~~~ +-------- .. automodule:: archipy.models.entities.sqlalchemy.base_entities :members: @@ -61,7 +55,7 @@ Entities :show-inheritance: Errors -~~~~~ +------ .. automodule:: archipy.models.errors.custom_errors :members: @@ -69,7 +63,7 @@ Errors :show-inheritance: Types -~~~~ +----- .. automodule:: archipy.models.types.base_types :members: @@ -97,81 +91,28 @@ Types :show-inheritance: Key Classes ---------- +----------- -Base DTOs -~~~~~~~~ +BaseDTO +~~~~~~~ .. autoclass:: archipy.models.dtos.base_dtos.BaseDTO :members: :undoc-members: :show-inheritance: -Base Entities -~~~~~~~~~~~ +BaseEntity +~~~~~~~~~~ .. autoclass:: archipy.models.entities.sqlalchemy.base_entities.BaseEntity :members: :undoc-members: :show-inheritance: -.. autoclass:: archipy.models.entities.sqlalchemy.base_entities.UpdatableEntity - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.models.entities.sqlalchemy.base_entities.DeletableEntity - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.models.entities.sqlalchemy.base_entities.AdminEntity - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.models.entities.sqlalchemy.base_entities.ManagerEntity - :members: - :undoc-members: - :show-inheritance: - -Base Errors +BaseError ~~~~~~~~~ .. autoclass:: archipy.models.errors.custom_errors.BaseError :members: :undoc-members: :show-inheritance: - -.. autoclass:: archipy.models.errors.custom_errors.NotFoundError - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.models.errors.custom_errors.InvalidArgumentError - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.models.errors.custom_errors.InternalError - :members: - :undoc-members: - :show-inheritance: - -Base Types -~~~~~~~~ - -.. autoclass:: archipy.models.types.base_types.BaseType - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.models.types.sort_order_type.SortOrderType - :members: - :undoc-members: - :show-inheritance: - -.. autoclass:: archipy.models.types.base_types.FilterOperationType - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api_reference/utils.rst b/docs/source/api_reference/utils.rst index acb626ba..bf2776e2 100644 --- a/docs/source/api_reference/utils.rst +++ b/docs/source/api_reference/utils.rst @@ -1,145 +1,60 @@ .. _api_utils: Utils -==== +===== Overview -------- -The utils module contains utility functions for various common tasks. +The utils module provides helper functions for common tasks, enhancing productivity in areas like datetime handling, JWT management, and password processing. -Base Utils -~~~~~~~~ - -.. automodule:: archipy.helpers.utils.base_utils - :members: - :undoc-members: - :show-inheritance: - -Datetime Utils -~~~~~~~~~~~ +Utilities +--------- .. automodule:: archipy.helpers.utils.datetime_utils :members: :undoc-members: :show-inheritance: -Error Utils -~~~~~~~~~ - -.. automodule:: archipy.helpers.utils.error_utils - :members: - :undoc-members: - :show-inheritance: - -File Utils -~~~~~~~~ - .. automodule:: archipy.helpers.utils.file_utils :members: :undoc-members: :show-inheritance: -JWT Utils -~~~~~~~ - .. automodule:: archipy.helpers.utils.jwt_utils :members: :undoc-members: :show-inheritance: -Password Utils -~~~~~~~~~~~ - .. automodule:: archipy.helpers.utils.password_utils :members: :undoc-members: :show-inheritance: -String Utils -~~~~~~~~~~ - .. automodule:: archipy.helpers.utils.string_utils :members: :undoc-members: :show-inheritance: -.. automodule:: archipy.helpers.utils.string_utils_constants - :members: - :undoc-members: - :show-inheritance: - -TOTP Utils -~~~~~~~~ - .. automodule:: archipy.helpers.utils.totp_utils :members: :undoc-members: :show-inheritance: -Key Classes ---------- - -BaseUtils -~~~~~~~ - -.. autoclass:: archipy.helpers.utils.base_utils.BaseUtils - :members: - :undoc-members: - :show-inheritance: - -DatetimeUtils -~~~~~~~~~~ +Key Functions +------------- -.. autoclass:: archipy.helpers.utils.datetime_utils.DatetimeUtils - :members: - :undoc-members: - :show-inheritance: - -ErrorUtils -~~~~~~~~ - -.. autoclass:: archipy.helpers.utils.error_utils.ErrorUtils - :members: - :undoc-members: - :show-inheritance: - -FileUtils -~~~~~~~ - -.. autoclass:: archipy.helpers.utils.file_utils.FileUtils - :members: - :undoc-members: - :show-inheritance: - -JWTUtils -~~~~~~ - -.. autoclass:: archipy.helpers.utils.jwt_utils.JWTUtils - :members: - :undoc-members: - :show-inheritance: - -PasswordUtils -~~~~~~~~~~ +get_utc_now +~~~~~~~~~~~ -.. autoclass:: archipy.helpers.utils.password_utils.PasswordUtils - :members: - :undoc-members: - :show-inheritance: +.. autofunction:: archipy.helpers.utils.datetime_utils.get_utc_now -StringUtils -~~~~~~~~ +generate_jwt +~~~~~~~~~~~~ -.. autoclass:: archipy.helpers.utils.string_utils.StringUtils - :members: - :undoc-members: - :show-inheritance: +.. autofunction:: archipy.helpers.utils.jwt_utils.generate_jwt -TOTPUtils -~~~~~~~ +hash_password +~~~~~~~~~~~~~ -.. autoclass:: archipy.helpers.utils.totp_utils.TOTPUtils - :members: - :undoc-members: - :show-inheritance: +.. autofunction:: archipy.helpers.utils.password_utils.hash_password diff --git a/docs/source/architecture.rst b/docs/source/architecture.rst index e4657dd2..5aad8325 100644 --- a/docs/source/architecture.rst +++ b/docs/source/architecture.rst @@ -6,16 +6,16 @@ Architecture Overview -------- -ArchiPy follows a clean, hexagonal architecture pattern that separates concerns and promotes testability. The architecture is designed around the following key components: +ArchiPy follows a hexagonal (Ports and Adapters) architecture, promoting separation of concerns and testability. .. image:: https://img.shields.io/badge/Architecture-Hexagonal-brightgreen :alt: Hexagonal Architecture Key Components -------------- +-------------- Adapters -~~~~~~~ +~~~~~~~~ Adapters are implementations of ports that connect the application to external systems: @@ -35,9 +35,9 @@ Ports define interfaces that adapters must implement: - **EmailPort**: Interface for email operations Models -~~~~~ +~~~~~~ -Models represent data structures: +Standardize data: - **Entities**: Database models (e.g., SQLAlchemy models) - **DTOs**: Data Transfer Objects (using Pydantic) @@ -45,93 +45,42 @@ Models represent data structures: - **Errors**: Custom error classes Helpers -~~~~~~ +~~~~~~~ -Utility classes and functions: +Enhance productivity: - **Utils**: Various utility functions +- **Decorators**: Various decorators for methods and classes - **Interceptors**: Middleware for gRPC, FastAPI, etc. - **Metaclasses**: Special classes like Singleton Configs -~~~~~~ - -Configuration templates for various services: - -- **BaseConfig**: Base configuration class -- **SqlAlchemyConfig**: Database configuration -- **RedisConfig**: Redis configuration -- **EmailConfig**: Email service configuration -- **FastAPIConfig**: FastAPI configuration -- **GrpcConfig**: gRPC configuration - -Class Diagram ------------- - -Below is a simplified class diagram of the main components: - -.. code-block:: - - +-----------------+ +------------------+ +----------------+ - | Ports |<----+ Adapters +---->| Models | - +-----------------+ +------------------+ +----------------+ - ^ ^ ^ - | | | - | | | - v v v - +-----------------+ +------------------+ +----------------+ - | Helpers | | Configs | | Utils | - +-----------------+ +------------------+ +----------------+ +~~~~~~~ -Hexagonal Architecture --------------------- +Standardize setup: -ArchiPy follows the Hexagonal Architecture (also known as Ports and Adapters) pattern: +- **BaseConfig**: `base_config.py` +- **Templates**: `config_template.py` -- **Core Domain**: The central part of the application containing business logic -- **Ports**: Interfaces that define how to interact with the domain -- **Adapters**: Implementations of the ports connecting to external systems +Hexagonal Design +---------------- -Benefits of this architecture: +- **Core**: Business logic (models, helpers). +- **Ports**: Interfaces for external interaction. +- **Adapters**: Implementations for external systems. -1. **Separation of Concerns**: Business logic is separate from external systems -2. **Testability**: Easy to mock external dependencies -3. **Flexibility**: Easy to swap out adapters without changing the core domain -4. **Maintainability**: Clean boundaries between components +Benefits: +1. Testability with mocks. +2. Flexibility in swapping adapters. +3. Clear boundaries. Dependency Injection ------------------- +-------------------- -ArchiPy uses dependency injection to manage dependencies: +Example: .. code-block:: python - # Example of dependency injection - - class UserService: - def __init__(self, user_repository): - self.user_repository = user_repository - - # Adapter implementation - user_repository = SqlAlchemyAdapter(session_manager, UserEntity) - - # Inject the adapter - user_service = UserService(user_repository) - -Error Handling Strategy ---------------------- - -ArchiPy provides a comprehensive set of custom errors, all inheriting from a base ``BaseError`` class: - -- **Domain Errors**: Errors related to business logic -- **Infrastructure Errors**: Errors related to external systems -- **Validation Errors**: Errors related to data validation - -Key Design Principles -------------------- - -1. **Explicit is better than implicit** -2. **Composition over inheritance** -3. **Single Responsibility Principle** -4. **Interface Segregation Principle** -5. **Dependency Inversion Principle** + from archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters import SqlAlchemyAdapter + service_repository = MyServiceRepository(sqlalchemy_adapter=SqlAlchemyAdapter()) # Inject adapter + service = MyService(repository=service_repository) diff --git a/docs/source/conf.py b/docs/source/conf.py index 2cb1b8bf..46a1aec7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -5,81 +5,80 @@ import os import sys -sys.path.insert(0, os.path.abspath('../..')) + +sys.path.insert(0, os.path.abspath("../..")) # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'ArchiPy' -copyright = '2024, Mehdi Einali, Hossein Nejati' -author = 'Mehdi Einali, Hossein Nejati' -version = '0.1.0' -release = '0.1.0' +project = "ArchiPy" +copyright = "2025, Mehdi Einali, Hossein Nejati" +author = "Mehdi Einali, Hossein Nejati" +version = "0.1.0" +release = "0.1.0" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon', - 'sphinx_autodoc_typehints', - 'sphinx.ext.intersphinx', - 'sphinx.ext.autosectionlabel', - 'sphinx.ext.coverage', - 'sphinx.ext.todo', - 'sphinx.ext.autosummary', - + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx_autodoc_typehints", + "sphinx.ext.intersphinx", + "sphinx.ext.autosectionlabel", + "sphinx.ext.coverage", + "sphinx.ext.todo", + "sphinx.ext.autosummary", ] # AutoAPI settings for better code extraction -autoapi_type = 'python' -autoapi_dirs = ['../../archipy'] +autoapi_type = "python" +autoapi_dirs = ["../../archipy"] autoapi_options = [ - 'members', - 'undoc-members', - 'private-members', - 'special-members', - 'inherited-members', - 'show-inheritance', - 'show-module-summary', - 'imported-members', + "members", + "undoc-members", + "private-members", + "special-members", + "inherited-members", + "show-inheritance", + "show-module-summary", + "imported-members", ] -autoapi_python_class_content = 'both' -autoapi_member_order = 'groupwise' +autoapi_python_class_content = "both" +autoapi_member_order = "groupwise" autoapi_add_toctree_entry = False # We'll handle this in our own index -templates_path = ['_templates'] +templates_path = ["_templates"] exclude_patterns = [] # -- Options for autodoc ----------------------------------------------------- -autodoc_member_order = 'bysource' -autodoc_typehints = 'description' -autoclass_content = 'both' +autodoc_member_order = "bysource" +autodoc_typehints = "description" +autoclass_content = "both" autodoc_default_options = { - 'members': True, - 'member-order': 'bysource', + "members": True, + "member-order": "bysource", # 'special-members': True, # 'inherited-members': True, # 'show-inheritance': True, # 'undoc-members': True, - } # Enable auto-summary generation autosummary_generate = True # Include __init__ method docstring in class docstring -autoclass_content = 'both' +autoclass_content = "both" # Enable todos todo_include_todos = True # Inheritance diagrams -inheritance_graph_attrs = dict(rankdir="TB", size='"12.0, 12.0"', fontsize=14, ratio='compress') -inheritance_node_attrs = dict(shape='rect', fontsize=14, height=0.75, margin='"0.08, 0.05"') +inheritance_graph_attrs = dict(rankdir="TB", size='"12.0, 12.0"', fontsize=14, ratio="compress") +inheritance_node_attrs = dict(shape="rect", fontsize=14, height=0.75, margin='"0.08, 0.05"') # Enable documenting typehints -autodoc_typehints = 'description' +autodoc_typehints = "description" typehints_fully_qualified = False always_document_param_types = True @@ -101,8 +100,8 @@ # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'sphinx_rtd_theme' -html_static_path = ['_static'] +html_theme = "sphinx_rtd_theme" +html_static_path = [] html_logo = None html_favicon = None html_title = f"{project} {version} Documentation" @@ -110,9 +109,9 @@ # -- Options for intersphinx ------------------------------------------------- intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), - 'sqlalchemy': ('https://docs.sqlalchemy.org/en/14/', None), - 'pydantic': ('https://docs.pydantic.dev/', None), + "python": ("https://docs.python.org/3", None), + "sqlalchemy": ("https://docs.sqlalchemy.org/en/20/", None), + "pydantic": ("https://docs.pydantic.dev/latest/", None), } # -- Options for autosectionlabel -------------------------------------------- @@ -123,16 +122,15 @@ # -- Options for LaTeX output ------------------------------------------------ latex_elements = { - 'papersize': 'a4paper', - 'pointsize': '11pt', - 'preamble': '', - 'figure_align': 'htbp', + "papersize": "a4paper", + "pointsize": "11pt", + "preamble": "", + "figure_align": "htbp", } # Define master_doc (the main document) -master_doc = 'index' +master_doc = "index" latex_documents = [ - (master_doc, 'ArchiPy.tex', 'ArchiPy Documentation', - author, 'manual'), -] \ No newline at end of file + (master_doc, "ArchiPy.tex", "ArchiPy Documentation", author, "manual"), +] diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index cb410788..3bbe01a3 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -71,7 +71,7 @@ Run the tests to ensure your changes don't break existing functionality: make behave Documentation -~~~~~~~~~~~ +~~~~~~~~~~~~~ All new features or changes should be documented: @@ -87,7 +87,7 @@ Building the documentation locally: make html Commit Messages -~~~~~~~~~~~~ +~~~~~~~~~~~~~~~ ArchiPy follows the `Conventional Commits `_ specification for commit messages: @@ -106,7 +106,7 @@ Common types: - ``chore``: Maintenance tasks Pull Request Process ------------------ +-------------------- 1. **Update Your Branch** @@ -145,7 +145,7 @@ Pull Request Process Once your pull request is approved, it will be merged into the main branch. Bug Reports and Feature Requests ------------------------------- +-------------------------------- If you find a bug or have a feature request, please create an issue on the `GitHub issues page `_. @@ -166,11 +166,11 @@ When submitting a feature request, please include: - If possible, a sketch of how the feature might be implemented Code of Conduct -------------- +--------------- Please note that ArchiPy has a code of conduct. By participating in this project, you agree to abide by its terms. Thank You --------- +--------- Thank you for contributing to ArchiPy! Your efforts help make the project better for everyone. diff --git a/docs/source/development.rst b/docs/source/development.rst index f59a556d..15dd75bb 100644 --- a/docs/source/development.rst +++ b/docs/source/development.rst @@ -4,10 +4,10 @@ Development ========== Development Environment ---------------------- +----------------------- -Setting Up the Development Environment -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Set Up +~~~~~~ 1. Clone the repository: @@ -16,7 +16,7 @@ Setting Up the Development Environment git clone https://github.com/SyntaxArc/ArchiPy.git cd ArchiPy -2. Set up the project: +2. Initialize the project: .. code-block:: bash @@ -27,184 +27,69 @@ Setting Up the Development Environment .. code-block:: bash make install - -4. Install development dependencies: - - .. code-block:: bash - - make install-dev - -5. Install pre-commit hooks: - - .. code-block:: bash - + make install-dev # For dev tools poetry run pre-commit install -Development Workflow ------------------- +Workflow +-------- Code Quality -~~~~~~~~~~ +~~~~~~~~~~~~ -Run all code quality checks: +Run checks: .. code-block:: bash - make check - -This will run: - -- **Linters**: ruff for linting -- **Formatters**: black for code formatting -- **Type Checkers**: mypy for static type checking + make check # Runs ruff, black, mypy Testing -~~~~~~ +~~~~~~~ Run tests: .. code-block:: bash - make test - -Run BDD tests: + make behave # BDD tests + make ci # Full pipeline -.. code-block:: bash +BDD tests use `behave` with feature files in `features/` and steps in `features/steps/`. - make behave +Versioning +---------- -Run the full CI pipeline locally: +Follow `Semantic Versioning `_: .. code-block:: bash - make ci - -Pre-commit Hooks --------------- - -ArchiPy uses pre-commit hooks to ensure code quality before commits. These hooks run: + make bump-patch # Bug fixes + make bump-minor # New features + make bump-major # Breaking changes -- **Black**: For code formatting -- **Ruff**: For linting -- **Mypy**: For type checking -- **Additional Checks**: Various other code quality checks - -To run pre-commit hooks manually: +Add a message: .. code-block:: bash - poetry run pre-commit run --all-files + make bump-minor message="Added new utility" -Making Changes +Build & Docs ------------ -1. Create a new branch: - - .. code-block:: bash - - git checkout -b feature/your-feature-name - -2. Make your changes and ensure all tests pass: - - .. code-block:: bash - - make check - make test - -3. Commit your changes using conventional commit messages: - - .. code-block:: bash - - git commit -m "feat: add new feature" - - Common prefixes: - - - ``feat``: New feature - - ``fix``: Bug fix - - ``docs``: Documentation changes - - ``style``: Formatting changes - - ``refactor``: Code refactoring - - ``test``: Adding or modifying tests - - ``chore``: Maintenance tasks - -4. Push your changes: - - .. code-block:: bash - - git push origin feature/your-feature-name - -5. Create a pull request on GitHub - -Versioning ---------- - -ArchiPy follows `Semantic Versioning (SemVer) `_ principles. - -Version Bumping Commands: - -- Bump Patch Version (Bug fixes): - - .. code-block:: bash - - make bump-patch - -- Bump Minor Version (New features): - - .. code-block:: bash - - make bump-minor - -- Bump Major Version (Breaking changes): - - .. code-block:: bash - - make bump-major - -Custom Version Messages: - -.. code-block:: bash - - make bump-patch message="Your custom message" - -Build and Distribution --------------------- - Build the package: .. code-block:: bash make build + make clean # Remove artifacts -This will create: - -- A wheel file (.whl) -- A source distribution (.tar.gz) - -Clean build artifacts: - -.. code-block:: bash - - make clean - -Documentation ------------ - -Build the documentation: +Build docs: .. code-block:: bash cd docs make html -This will generate HTML documentation in the ``docs/build/html`` directory. - -Updating Dependencies -------------------- - Update dependencies: .. code-block:: bash make update - -This will update all dependencies to their latest compatible versions according to the constraints in ``pyproject.toml``. diff --git a/docs/source/features.rst b/docs/source/features.rst index 0accc486..bbca3b8b 100644 --- a/docs/source/features.rst +++ b/docs/source/features.rst @@ -1,117 +1,53 @@ .. _features: Features -======= +======== -Modern Python Stack ------------------- +ArchiPy provides a robust framework for structured Python development, focusing on standardization, testability, and productivity. -ArchiPy is built with modern Python technologies: +Configuration Management +------------------------ -- **Python 3.13+**: Leveraging the latest Python features -- **Type Annotations**: Comprehensive type hinting for better IDE support -- **Pydantic**: For data validation and settings management -- **SQLAlchemy**: ORM for database operations -- **FastAPI**: High-performance web framework -- **gRPC**: For efficient microservice communication +- **Standardized Configs**: Use `base_config` and `config_template` for consistent setup. +- **Injection**: Seamlessly inject configurations into components. -Modular Design -------------- - -ArchiPy is designed with modularity in mind: - -- **Optional Dependencies**: Install only what you need -- **Pluggable Components**: Easy to extend and customize -- **Clean Interfaces**: Well-defined APIs between components - -Database Support --------------- - -- **SQLAlchemy Integration**: Robust ORM support -- **Connection Pooling**: Efficient database connection management -- **Async Support**: Native async database operations -- **Migration Tools**: Database schema management -- **PostgreSQL**: First-class support for PostgreSQL - -Caching with Redis +Adapters & Mocks ---------------- -- **Redis Adapter**: Easy-to-use Redis client -- **Async Support**: Async Redis operations -- **Connection Pooling**: Efficient connection management -- **Serialization**: Automatic data serialization/deserialization - -API Development -------------- - -- **FastAPI Integration**: High-performance API development -- **Request Validation**: Automatic request validation with Pydantic -- **Rate Limiting**: Built-in rate limiting support -- **Authentication**: JWT authentication support -- **Swagger Documentation**: Automatic API documentation - -gRPC Support ----------- +- **Common Adapters**: Pre-built for Redis, SQLAlchemy, and email. +- **Mocks**: Testable mocks (e.g., `redis_mocks`, `sqlalchemy_mocks`) for isolated testing. +- **Async Support**: Synchronous and asynchronous implementations. -- **Interceptors**: Client and server interceptors -- **Tracing**: Distributed tracing support -- **Metrics**: Prometheus metrics integration -- **Error Handling**: Structured error handling +Data Standardization +-------------------- -Monitoring and Observability --------------------------- +- **Base Entities**: Standardized SQLAlchemy entities (`base_entities.py`). +- **DTOs**: Pydantic-based DTOs (e.g., `pagination_dto`, `error_dto`). +- **Type Safety**: Enforced via `pydantic` and `mypy`. -- **Prometheus Integration**: Metrics collection and monitoring -- **Elastic APM**: Application performance monitoring -- **Sentry**: Error tracking and reporting -- **Structured Logging**: Consistent log formatting - -Email Support ------------ - -- **SMTP Integration**: Send emails via SMTP -- **Template Support**: Email templating -- **Attachment Handling**: Support for email attachments -- **Connection Pooling**: Efficient SMTP connection management - -Type Safety ---------- - -- **Mypy Integration**: Static type checking -- **Runtime Type Checking**: With Pydantic validation -- **Type Annotations**: Comprehensive type hints -- **Generic Types**: Support for generic types - -Testing Support -------------- +Helper Utilities +---------------- -- **Unit Testing**: With pytest -- **Integration Testing**: For testing component interactions -- **BDD Testing**: With behave for behavior-driven development -- **Mocking**: Mocking frameworks for external dependencies -- **Fixtures**: Reusable test fixtures +- **Utilities**: Tools like `datetime_utils`, `jwt_utils`, `password_utils`. +- **Decorators**: `retry`, `singleton`, `sqlalchemy_atomic`, etc. +- **Interceptors**: Rate limiting (FastAPI), tracing (gRPC). -Documentation +BDD Testing ----------- -- **Docstrings**: Comprehensive docstrings -- **Type Hints**: For better IDE support -- **Sphinx Integration**: Automatic documentation generation -- **API Documentation**: Detailed API documentation -- **Usage Examples**: Code examples for common use cases +- **Behave Integration**: Pre-configured for sync/async scenarios. +- **Feature Files**: Examples like `app_utils.feature`, `totp_utils.feature`. +- **Step Definitions**: Comprehensive steps for testing (e.g., `jwt_utils_steps.py`). -Code Quality ----------- +Best Practices & Tooling +------------------------ -- **Linting**: With ruff for code quality checks -- **Formatting**: With black for consistent code formatting -- **Type Checking**: With mypy for static type checking -- **Pre-commit Hooks**: Automated code quality checks -- **CI/CD Integration**: Continuous integration and deployment +- **Poetry**: Dependency management for reproducible builds. +- **Pre-commit**: Automated checks with `ruff`, `black`, and `mypy`. +- **Structure**: Clean architecture with `pyproject.toml` for modern Python development. -Dependency Management -------------------- +Modular Design +-------------- -- **Poetry**: Modern dependency management -- **Versioning**: Semantic versioning -- **Virtual Environments**: Isolated development environments +- **Optional Dependencies**: Install only what you need (e.g., `archipy[redis]`). +- **Extensible**: Add custom adapters and helpers easily. diff --git a/docs/source/index.rst b/docs/source/index.rst index a1d83c60..5cee216c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,16 +3,26 @@ Welcome to ArchiPy's Documentation ================================== -**Architecture + Python – Perfect for Structured Design** +**Architecture + Python – Structured Development Simplified** -ArchiPy is a Python project designed to provide a robust and structured architecture for building scalable and -maintainable applications. It integrates modern Python tools and libraries to streamline development, testing, and -deployment. +ArchiPy is a Python framework designed to provide a standardized, scalable, and maintainable architecture for modern applications. Built with Python 3.13+, it offers tools and best practices for configuration management, testing, and development workflows, adhering to clean architecture principles. .. image:: https://img.shields.io/badge/python-3.13+-blue.svg :target: https://www.python.org/downloads/ :alt: Python 3.13+ +Goals +----- + +ArchiPy aims to: + +1. **Standardize Configuration**: Simplify config management and injection. +2. **Provide Adapters & Mocks**: Enable delegation with testable mocks. +3. **Unify Data Models**: Offer base entities and DTOs for consistency. +4. **Enhance Productivity**: Include helpers, utils, and decorators. +5. **Support BDD**: Facilitate behavior-driven testing for sync/async scenarios. +6. **Enforce Best Practices**: Use modern tooling for optimal Python development. + .. toctree:: :maxdepth: 2 :caption: Contents: @@ -38,7 +48,7 @@ Quick Start # Or with poetry poetry add archipy -Indices and tables +Indices and Tables ================== * :ref:`genindex` diff --git a/docs/source/installation.rst b/docs/source/installation.rst index e4545197..262e440a 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -4,62 +4,61 @@ Installation =========== Prerequisites ------------- +------------- -Before you begin, ensure you have the following installed: +Before starting, ensure you have: - **Python 3.13 or higher** - ArchiPy is compatible with Python 3.13 and above but does not support Python 4 or higher. - To check your Python version, run: + ArchiPy requires Python 3.13+. Check your version with: .. code-block:: bash python --version - If your Python version is lower than 3.13, `download and install the latest version of Python `_. + If needed, `download Python 3.13+ `_. - **Poetry** (for dependency management) - Poetry is required to manage dependencies and install the project. If you don't have Poetry installed, follow the `official installation guide `_. + Poetry manages dependencies and project setup. Install it via the `official guide `_. Installation Methods -------------------- +-------------------- Using pip ~~~~~~~~~ -To install the core library: +Install the core library: .. code-block:: bash pip install archipy -To install the library with optional dependencies (e.g., ``redis``, ``fastapi``, etc.): +With optional dependencies (e.g., Redis, FastAPI): .. code-block:: bash pip install archipy[redis,fastapi] -Using poetry -~~~~~~~~~~~ +Using Poetry +~~~~~~~~~~~~ -To add the core library to your project: +Add the core library: .. code-block:: bash poetry add archipy -To add the library with optional dependencies (e.g., ``redis``, ``fastapi``, etc.): +With optional dependencies: .. code-block:: bash poetry add archipy[redis,fastapi] Optional Dependencies -------------------- +--------------------- -The library provides optional dependencies for additional functionality. You can install them as needed: +ArchiPy supports modular features: .. list-table:: :header-rows: 1 @@ -69,8 +68,6 @@ The library provides optional dependencies for additional functionality. You can - Installation Command * - Redis - ``archipy[redis]`` - * - Elastic APM - - ``archipy[elastic-apm]`` * - FastAPI - ``archipy[fastapi]`` * - JWT @@ -89,13 +86,17 @@ The library provides optional dependencies for additional functionality. You can - ``archipy[grpc]`` * - PostgreSQL - ``archipy[postgres]`` - * - aiosqlite - - ``archipy[aiosqlite]`` + * - FakeRedis + - ``archipy[fakeredis]`` + * - JWT + - ``archipy[jwt]`` + * - Full list + - See `Usage `_ section Development Installation ------------------------ +------------------------ -For contributors and developers: +For contributors: .. code-block:: bash @@ -109,20 +110,14 @@ For contributors and developers: # Install dependencies make install - # Install development dependencies (optional) + # Optional: Install dev tools make install-dev -Troubleshooting Installation Issues ----------------------------------- - -If you encounter issues during installation, ensure that: +Troubleshooting +--------------- -1. Your Python version is **3.13 or higher**. -2. Your package manager (``pip`` or ``poetry``) is up to date. -3. You have the necessary build tools installed (e.g., ``setuptools``, ``wheel``). - -For example, to upgrade ``pip``, run: - -.. code-block:: bash +If issues arise, verify: - pip install --upgrade pip +1. Python version is 3.13+. +2. ``pip`` or ``poetry`` is updated (e.g., ``pip install --upgrade pip``). +3. Build tools (``setuptools``, ``wheel``) are installed. diff --git a/docs/source/license.rst b/docs/source/license.rst index b4bc47a0..77759baf 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -1,7 +1,7 @@ .. _license: License -====== +======= ArchiPy is licensed under the terms of the license file included in the repository. diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 89b08e0d..b2d429cc 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -6,162 +6,146 @@ Usage Getting Started -------------- -After installing ArchiPy, you can import it in your Python code: +After installing ArchiPy, import its components to leverage its structured architecture: .. code-block:: python import archipy -Basic Examples -------------- +Examples +-------- -Configuration -~~~~~~~~~~~~ +Configuration Management +~~~~~~~~~~~~~~~~~~~~~~~~ -ArchiPy provides configuration templates for various services: +Standardize and inject configurations: .. code-block:: python - from archipy.configs.config_template import SqlAlchemyConfig, RedisConfig + from archipy.configs.base_config import BaseConfig - # Set up database configuration - db_config = SqlAlchemyConfig( - url="postgresql://user:password@localhost:5432/db_name", - pool_size=5, - max_overflow=10 - ) + # Define a custom config + class MyAppConfig(BaseConfig): + database_url: str = "sqlite:///example.db" + redis_host: str = "localhost" - # Set up Redis configuration - redis_config = RedisConfig( - host="localhost", - port=6379, - db=0 - ) + config = MyAppConfig() + print(config.database_url) # "sqlite:///example.db" -Using SQLAlchemy Adapters -~~~~~~~~~~~~~~~~~~~~~~~ +Adapters & Mocks +~~~~~~~~~~~~~~~~ -.. code-block:: python +Use adapters for external systems with mocks for testing: - from archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters import SqlAlchemyAdapter - from archipy.adapters.orm.sqlalchemy.session_manager_adapters import SessionManagerAdapter +.. code-block:: python - # Create a session manager - session_manager = SessionManagerAdapter(db_config) + from archipy.adapters.redis.redis_adapters import AsyncRedisAdapter + from archipy.adapters.redis.redis_mocks import AsyncRedisMock - # Create a SQLAlchemy adapter - adapter = SqlAlchemyAdapter(session_manager, YourEntity) + # Production use + redis = AsyncRedisAdapter() + await redis.set("key", "value", ex=3600) + print(await redis.get("key")) # "value" - # Use the adapter - results = adapter.find_all() + # Testing with mock + mock_redis = AsyncRedisMock() + await mock_redis.set("key", "test") + print(await mock_redis.get("key")) # "test" -Working with Entities -~~~~~~~~~~~~~~~~~~~ +Entities & DTOs +~~~~~~~~~~~~~~~ -ArchiPy provides base entity classes for your database models: +Standardize data models: .. code-block:: python from sqlalchemy import Column, Integer, String from archipy.models.entities.sqlalchemy.base_entities import BaseEntity + from archipy.models.dtos.base_dtos import BaseDTO + # Entity class User(BaseEntity): __tablename__ = "users" - id = Column(Integer, primary_key=True) name = Column(String(100)) - email = Column(String(100)) - -Using DTOs (Data Transfer Objects) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from pydantic import Field - from archipy.models.dtos.base_dtos import BaseDTO + # DTO class UserDTO(BaseDTO): id: int name: str - email: str - age: int = Field(gt=0) - -Making Async Requests -~~~~~~~~~~~~~~~~~~~ - -ArchiPy supports async operations for database and Redis: - -.. code-block:: python - - import asyncio - from archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters import AsyncSqlAlchemyAdapter - - async def fetch_data(): - adapter = AsyncSqlAlchemyAdapter(async_session_manager, YourEntity) - results = await adapter.find_all() - return results - # Run the async function - asyncio.run(fetch_data()) + user = UserDTO(id=1, name="Alice") + print(user.model_dump()) # {'id': 1, 'name': 'Alice'} -Error Handling -~~~~~~~~~~~~ +Helper Utilities +~~~~~~~~~~~~~~~~ -ArchiPy provides a comprehensive set of custom errors: +Simplify tasks with utilities and decorators: .. code-block:: python - from archipy.models.errors.custom_errors import NotFoundError, InvalidArgumentError + from archipy.helpers.utils.datetime_utils import get_utc_now + from archipy.helpers.decorators.retry import retry - try: - # Your code here - if not valid_input: - raise InvalidArgumentError("Invalid input provided") - except NotFoundError as e: - # Handle not found error - print(f"Resource not found: {str(e)}") - except InvalidArgumentError as e: - # Handle invalid argument error - print(f"Invalid argument: {str(e)}") - -Available Commands ----------------- - -Run ``make help`` to see all available commands: + # Utility + now = get_utc_now() + print(now) # Current UTC time -.. code-block:: bash + # Decorator + @retry(max_attempts=3, delay=1) + def risky_operation(): + # Simulated failure + raise ValueError("Try again") - make help + try: + risky_operation() + except ValueError as e: + print(f"Failed after retries: {e}") -Common Commands -~~~~~~~~~~~~~ +BDD Testing +~~~~~~~~~~~ -Format Code: +Validate features with `behave`: .. code-block:: bash - make format + # Run BDD tests + make behave -Run Linters: +Example feature file (`features/app_utils.feature`): -.. code-block:: bash +.. code-block:: gherkin - make lint + Feature: Application Utilities + Scenario: Get UTC time + When I get the current UTC time + Then the result should be a valid datetime -Run Tests: +Async Operations +~~~~~~~~~~~~~~~~ -.. code-block:: bash +Support for asynchronous workflows: - make behave +.. code-block:: python -Build the Project: + import asyncio + from archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters import AsyncSqlAlchemyAdapter -.. code-block:: bash + async def fetch_users(): + adapter = AsyncSqlAlchemyAdapter(session_manager, User) + users = await adapter.execute_search_query(User, pagination=None, sort_info=None) + return users - make build + users, total = asyncio.run(fetch_users()) + print(users) # List of User entities -Clean Build Artifacts: +Available Commands +------------------ -.. code-block:: bash +Run ``make help`` for all commands. Common ones: - make clean +- **Format Code**: ``make format`` +- **Lint Code**: ``make lint`` +- **Run BDD Tests**: ``make behave`` +- **Build Project**: ``make build`` +- **Clean Artifacts**: ``make clean`` diff --git a/scripts/project_tree.py b/scripts/project_tree.py new file mode 100644 index 00000000..6d6fbdee --- /dev/null +++ b/scripts/project_tree.py @@ -0,0 +1,100 @@ +import importlib +import inspect +import os +import sys +from typing import get_type_hints + +# Set the project root to the parent directory of the scripts folder +PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(PROJECT_ROOT) # Add project root to sys.path for imports + + +# Load .gitignore patterns from the root directory +def load_gitignore(root_dir=PROJECT_ROOT): + gitignore_path = os.path.join(root_dir, ".gitignore") + ignore_patterns = [".venv", "__pycache__", ".idea", ".git"] # Default ignores including .git + if os.path.exists(gitignore_path): + with open(gitignore_path) as f: + for line in f: + line = line.strip() + if line and not line.startswith("#"): + ignore_patterns.append(line.rstrip("/")) + return ignore_patterns + + +IGNORE_PATTERNS = load_gitignore() + + +def is_ignored(path, root_dir=PROJECT_ROOT): + """Check if a path matches any .gitignore patterns or .git.""" + rel_path = os.path.relpath(path, root_dir) + for pattern in IGNORE_PATTERNS: + if pattern in rel_path or rel_path.endswith(pattern) or pattern == os.path.basename(rel_path): + return True + return False + + +def list_project_structure(root_dir=PROJECT_ROOT): + """Print the project directory tree, respecting .gitignore and excluding .git.""" + print(f"Project Tree for: {os.path.abspath(root_dir)}") + for root, dirs, files in os.walk(root_dir): + if is_ignored(root): + dirs[:] = [] # Stop walking this directory + continue + level = root.replace(root_dir, "").count(os.sep) + indent = " " * level + print(f"{indent}{os.path.basename(root)}/") + sub_indent = " " * (level + 1) + for f in sorted(files): + file_path = os.path.join(root, f) + if not is_ignored(file_path) and f.endswith((".py", ".feature")) and f != "__init__.py": + print(f"{sub_indent}{f}") + + +def list_classes_and_public_methods(module_path): + """List classes and their public methods with input/output types.""" + try: + module_name = module_path.replace(os.sep, ".").replace(".py", "").strip(".") + module = importlib.import_module(module_name) + print(f"File: {module_path}") + for name, obj in inspect.getmembers(module): + if inspect.isclass(obj) and obj.__module__ == module_name: + print(f" Class: {name}") + for method_name, method in inspect.getmembers(obj, inspect.isfunction): + if not method_name.startswith("_"): + print(f" Public Method: {method_name}") + try: + hints = get_type_hints(method) + sig = inspect.signature(method) + params = sig.parameters + if params: + print(" Inputs:") + for param_name, param in params.items(): + param_type = hints.get(param_name, "unspecified") + print(f" {param_name}: {param_type}") + else: + print(" Inputs: None") + return_type = hints.get("return", "unspecified") + print(f" Output: {return_type}") + except Exception as e: + print(f" (Type info unavailable: {e})") + except Exception as e: + print(f" Error loading module {module_name}: {e}") + + +def analyze_python_files(root_dir=PROJECT_ROOT): + """Find and analyze Python files, respecting .gitignore and excluding .git.""" + print("\nClasses and Public Methods in Python files:") + for root, _, files in os.walk(root_dir): + if is_ignored(root): + continue + for f in files: + file_path = os.path.join(root, f) + if not is_ignored(file_path) and f.endswith(".py") and f != "__init__.py": + module_path = os.path.relpath(file_path, PROJECT_ROOT) + list_classes_and_public_methods(module_path) + + +if __name__ == "__main__": + list_project_structure() + analyze_python_files() From 6c19b1cb72c573fc9247a35d052b4baae3cb8d98 Mon Sep 17 00:00:00 2001 From: "h.nejati" Date: Sun, 2 Mar 2025 12:50:39 +0330 Subject: [PATCH 2/2] Add examples to docs --- docs/source/architecture.rst | 301 +++++++++++++--- docs/source/conf.py | 24 ++ docs/source/examples/adapters/email.rst | 179 ++++++++++ docs/source/examples/adapters/index.rst | 25 ++ docs/source/examples/adapters/orm.rst | 178 ++++++++++ docs/source/examples/adapters/redis.rst | 185 ++++++++++ docs/source/examples/bdd_testing.rst | 330 ++++++++++++++++++ docs/source/examples/config_management.rst | 62 ++++ docs/source/examples/helpers/decorators.rst | 68 ++++ docs/source/examples/helpers/index.rst | 50 +++ docs/source/examples/helpers/interceptors.rst | 48 +++ docs/source/examples/helpers/metaclasses.rst | 34 ++ docs/source/examples/helpers/utils.rst | 229 ++++++++++++ docs/source/index.rst | 66 +++- docs/source/usage.rst | 151 +++++++- 15 files changed, 1855 insertions(+), 75 deletions(-) create mode 100644 docs/source/examples/adapters/email.rst create mode 100644 docs/source/examples/adapters/index.rst create mode 100644 docs/source/examples/adapters/orm.rst create mode 100644 docs/source/examples/adapters/redis.rst create mode 100644 docs/source/examples/bdd_testing.rst create mode 100644 docs/source/examples/config_management.rst create mode 100644 docs/source/examples/helpers/decorators.rst create mode 100644 docs/source/examples/helpers/index.rst create mode 100644 docs/source/examples/helpers/interceptors.rst create mode 100644 docs/source/examples/helpers/metaclasses.rst create mode 100644 docs/source/examples/helpers/utils.rst diff --git a/docs/source/architecture.rst b/docs/source/architecture.rst index 5aad8325..93f45f16 100644 --- a/docs/source/architecture.rst +++ b/docs/source/architecture.rst @@ -3,84 +3,277 @@ Architecture =========== -Overview --------- +Design Philosophy +---------------- -ArchiPy follows a hexagonal (Ports and Adapters) architecture, promoting separation of concerns and testability. +ArchiPy is designed to standardize and simplify Python application development by providing a flexible set of building blocks that work across different architectural approaches. Rather than enforcing a single architectural pattern, ArchiPy offers components that can be applied to: -.. image:: https://img.shields.io/badge/Architecture-Hexagonal-brightgreen - :alt: Hexagonal Architecture +* Layered Architecture +* Hexagonal Architecture (Ports & Adapters) +* Clean Architecture +* Domain-Driven Design +* Service-Oriented Architecture +* And more... -Key Components --------------- +These building blocks help maintain consistency, testability, and maintainability regardless of the specific architectural style chosen for your project. -Adapters -~~~~~~~~ +.. image:: ../assets/architecture_overview.png + :alt: ArchiPy Architecture Overview + :align: center + :width: 600px -Adapters are implementations of ports that connect the application to external systems: +Core Building Blocks +------------------ -- **ORM Adapters**: Connect to databases (SQLAlchemy, etc.) -- **Redis Adapters**: Connect to Redis for caching -- **Email Adapters**: Connect to email services -- **gRPC Adapters**: Connect to gRPC services +Configuration Management +~~~~~~~~~~~~~~~~~~~~~~~ -Ports -~~~~ +ArchiPy provides a standardized way to manage configuration across your application: -Ports define interfaces that adapters must implement: +.. code-block:: python -- **SessionManagerPort**: Interface for database session management -- **SqlAlchemyPort**: Interface for SQLAlchemy operations -- **RedisPort**: Interface for Redis operations -- **EmailPort**: Interface for email operations + from archipy.configs.base_config import BaseConfig -Models -~~~~~~ + class AppConfig(BaseConfig): + DATABASE = { + "HOST": "localhost", + "PORT": 5432, + "USERNAME": "user", + "PASSWORD": "password" + } -Standardize data: + DEBUG = True -- **Entities**: Database models (e.g., SQLAlchemy models) -- **DTOs**: Data Transfer Objects (using Pydantic) -- **Types**: Enums and custom types -- **Errors**: Custom error classes + # Set global configuration + config = AppConfig() + BaseConfig.set_global(config) -Helpers -~~~~~~~ +Adapters & Ports +~~~~~~~~~~~~~~ -Enhance productivity: +ArchiPy implements the ports and adapters pattern to isolate the application core from external dependencies: -- **Utils**: Various utility functions -- **Decorators**: Various decorators for methods and classes -- **Interceptors**: Middleware for gRPC, FastAPI, etc. -- **Metaclasses**: Special classes like Singleton +.. code-block:: python -Configs -~~~~~~~ + # Port: defines an interface (contract) + from typing import Protocol -Standardize setup: + class UserRepositoryPort(Protocol): + def get_by_id(self, user_id: str) -> User: ... + def create(self, user: User) -> User: ... -- **BaseConfig**: `base_config.py` -- **Templates**: `config_template.py` + # Adapter: implements the interface for a specific technology + class SqlAlchemyUserRepository: + def __init__(self, db_adapter: SqlAlchemyAdapter): + self.db_adapter = db_adapter -Hexagonal Design ----------------- + def get_by_id(self, user_id: str) -> User: + return self.db_adapter.get_by_uuid(User, user_id) + + def create(self, user: User) -> User: + return self.db_adapter.create(user) + + # Application core uses the port, not the adapter + class UserService: + def __init__(self, repository: UserRepositoryPort): + self.repository = repository + + def get_user(self, user_id: str) -> User: + return self.repository.get_by_id(user_id) + +Entity Models +~~~~~~~~~~~ + +Standardized entity models provide a consistent approach to domain modeling: + +.. code-block:: python + + from sqlalchemy import Column, String + from archipy.models.entities import BaseEntity + + class User(BaseEntity): + __tablename__ = "users" -- **Core**: Business logic (models, helpers). -- **Ports**: Interfaces for external interaction. -- **Adapters**: Implementations for external systems. + name = Column(String(100)) + email = Column(String(255), unique=True) -Benefits: -1. Testability with mocks. -2. Flexibility in swapping adapters. -3. Clear boundaries. +Data Transfer Objects (DTOs) +~~~~~~~~~~~~~~~~~~~~~~~~~ -Dependency Injection +Define consistent data structures for transferring data between layers: + +.. code-block:: python + + from pydantic import BaseModel, EmailStr + from archipy.models.dtos import BaseDTO + + class UserCreateDTO(BaseDTO): + name: str + email: EmailStr + + class UserResponseDTO(BaseDTO): + id: str + name: str + email: EmailStr + created_at: datetime + +Example Architectures -------------------- -Example: +Layered Architecture +~~~~~~~~~~~~~~~~~ + +ArchiPy can be used with a traditional layered architecture approach: + +.. code-block:: text + + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Presentation β”‚ API, UI, CLI + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ Application β”‚ Services, Workflows + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ Domain β”‚ Business Logic, Entities + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ Infrastructure β”‚ Adapters, Repositories, External Services + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +Clean Architecture +~~~~~~~~~~~~~~~ + +ArchiPy supports Clean Architecture principles: + +.. code-block:: text + + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Entities β”‚ + β”‚ Domain models, business rules β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ Use Cases β”‚ + β”‚ Application services, business workflows β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ Interfaces β”‚ + β”‚ Controllers, presenters, gateways β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ + β”‚ Frameworks β”‚ + β”‚ External libraries, UI, DB, devices β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +Hexagonal Architecture +~~~~~~~~~~~~~~~~~~ + +For projects using a Hexagonal (Ports & Adapters) approach: + +.. code-block:: text + + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ + β”‚ Application Core β”‚ + β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ Domain Logic / Models β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ + β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ Input β”‚ β”‚ Output Ports β”‚ β”‚ + β”‚ β”‚ Ports β”‚ β”‚ β”‚ β”‚ + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ + β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β–² β–² + β”‚ β”‚ + β”‚ β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ β”‚ + β”‚ Input Adapters β”‚ β”‚ Output Adapters β”‚ + β”‚ (Controllers) β”‚ β”‚ (Repositories, β”‚ + β”‚ β”‚ β”‚ Clients, etc.) β”‚ + β”‚ β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +Practical Implementation +--------------------- + +Let's see how a complete application might be structured using ArchiPy: + +.. code-block:: text + + my_app/ + β”œβ”€β”€ configs/ + β”‚ └── app_config.py # Application configuration + β”œβ”€β”€ adapters/ + β”‚ β”œβ”€β”€ db/ # Database adapters + β”‚ └── api/ # API adapters + β”œβ”€β”€ core/ + β”‚ β”œβ”€β”€ models/ # Domain models + β”‚ β”œβ”€β”€ ports/ # Interface definitions + β”‚ └── services/ # Business logic + β”œβ”€β”€ repositories/ # Data access + β”œβ”€β”€ api/ # API routes + └── main.py # Application entry point + +Code Example +----------- + +Here's how you might structure a FastAPI application using ArchiPy: .. code-block:: python - from archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters import SqlAlchemyAdapter - service_repository = MyServiceRepository(sqlalchemy_adapter=SqlAlchemyAdapter()) # Inject adapter - service = MyService(repository=service_repository) + # adapters/db/user_repository.py + from archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters import SqlAlchemyAdapter + from core.models.user import User + + class UserRepository: + def __init__(self, db_adapter: SqlAlchemyAdapter): + self.db_adapter = db_adapter + + def get_user_by_id(self, user_id: str) -> User: + return self.db_adapter.get_by_uuid(User, user_id) + + def create_user(self, user: User) -> User: + return self.db_adapter.create(user) + + # core/services/user_service.py + from core.models.user import User + from adapters.db.user_repository import UserRepository + + class UserService: + def __init__(self, user_repository: UserRepository): + self.user_repository = user_repository + + def register_user(self, name: str, email: str) -> User: + # Business logic and validation here + user = User(name=name, email=email) + return self.user_repository.create_user(user) + + # api/users.py + from fastapi import APIRouter, Depends + from core.services.user_service import UserService + from archipy.models.dtos import BaseDTO + + router = APIRouter() + + class UserCreateDTO(BaseDTO): + name: str + email: str + + @router.post("/users/") + def create_user( + data: UserCreateDTO, + user_service: UserService = Depends(get_user_service) + ): + user = user_service.register_user(data.name, data.email) + return {"id": str(user.test_uuid), "name": user.name, "email": user.email} + + # main.py + from fastapi import FastAPI + from archipy.helpers.utils.app_utils import AppUtils + from archipy.configs.base_config import BaseConfig + + app = AppUtils.create_fastapi_app(BaseConfig.global_config()) + app.include_router(users_router) + +By providing standardized building blocks rather than enforcing a specific architecture, ArchiPy helps teams maintain consistent development practices while allowing flexibility to choose the architectural pattern that best fits their needs. diff --git a/docs/source/conf.py b/docs/source/conf.py index 46a1aec7..a1fea9b3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -107,6 +107,30 @@ html_title = f"{project} {version} Documentation" html_short_title = project +# Add search functionality +html_theme_options = { + "navigation_depth": 4, + "titles_only": False, + "logo_only": False, + "display_version": True, + "prev_next_buttons_location": "bottom", + "style_external_links": False, + "style_nav_header_background": "#2980B9", +} + +# Add GitHub links +html_context = { + "display_github": True, + "github_user": "SyntaxArc", + "github_repo": "ArchiPy", + "github_version": "main", + "conf_py_path": "/docs/source/", +} + +# Enable search functionality +html_use_index = True +html_domain_indices = True + # -- Options for intersphinx ------------------------------------------------- intersphinx_mapping = { "python": ("https://docs.python.org/3", None), diff --git a/docs/source/examples/adapters/email.rst b/docs/source/examples/adapters/email.rst new file mode 100644 index 00000000..e044cba4 --- /dev/null +++ b/docs/source/examples/adapters/email.rst @@ -0,0 +1,179 @@ +.. _examples_adapters_email: + +Email Adapters +============ + +Sending emails with the email adapter. + +Configuration +------------ + +First, configure your email settings: + +.. code-block:: python + + from archipy.configs.base_config import BaseConfig + + class AppConfig(BaseConfig): + EMAIL = { + "SMTP_HOST": "smtp.gmail.com", + "SMTP_PORT": 587, + "SMTP_USERNAME": "your-email@gmail.com", + "SMTP_PASSWORD": "your-app-password", + "USE_TLS": True, + "DEFAULT_SENDER": "Your App " + } + + # Set global configuration + config = AppConfig() + BaseConfig.set_global(config) + +Basic Email Sending +---------------- + +Send a simple email: + +.. code-block:: python + + from archipy.adapters.email.email_adapter import EmailAdapter + + # Create email adapter + email_adapter = EmailAdapter() + + def send_welcome_email(user_email, user_name): + # Send a simple text email + email_adapter.send( + to=user_email, + subject="Welcome to Our App!", + body=f"Hello {user_name},\n\nWelcome to our application!", + body_type="plain" # or "html" for HTML content + ) + + # Usage + send_welcome_email("user@example.com", "John") + +HTML Emails with Attachments +------------------------- + +Send rich HTML emails with attachments: + +.. code-block:: python + + from archipy.adapters.email.email_adapter import EmailAdapter + + email_adapter = EmailAdapter() + + def send_invoice_email(user_email, invoice_number, invoice_pdf_path): + # HTML content + html_content = f""" + + +

Invoice #{invoice_number}

+

Thank you for your purchase. Your invoice is attached.

+

If you have any questions, please contact support.

+ + + """ + + # Attachments + attachments = [ + { + "file_path": invoice_pdf_path, + "filename": f"Invoice-{invoice_number}.pdf", + "mime_type": "application/pdf" + } + ] + + # Send email with attachment + email_adapter.send( + to=user_email, + subject=f"Your Invoice #{invoice_number}", + body=html_content, + body_type="html", + attachments=attachments + ) + + # Usage + send_invoice_email( + "customer@example.com", + "INV-12345", + "/path/to/invoices/INV-12345.pdf" + ) + +Multiple Recipients and CC/BCC +---------------------------- + +Send to multiple recipients with CC and BCC: + +.. code-block:: python + + from archipy.adapters.email.email_adapter import EmailAdapter + from pydantic import EmailStr + + email_adapter = EmailAdapter() + + def send_team_notification(subject, message, team_emails, cc_manager=True): + # Convert recipient list to expected format + recipients = [EmailStr(email) for email in team_emails] + + # Add CC recipients if needed + cc = [] + if cc_manager: + cc = [EmailStr("manager@example.com")] + + email_adapter.send( + to=recipients, + subject=subject, + body=message, + body_type="plain", + cc=cc, + bcc=[EmailStr("records@example.com")] # BCC for record keeping + ) + + # Usage + send_team_notification( + "Project Update", + "The project milestone has been completed.", + ["team1@example.com", "team2@example.com"] + ) + +Template-Based Emails +------------------ + +Send emails using templates: + +.. code-block:: python + + from archipy.adapters.email.email_adapter import EmailAdapter + import os + + email_adapter = EmailAdapter() + + def send_password_reset(user_email, reset_token, user_name): + # Read template from file + template_path = os.path.join("templates", "emails", "password_reset.html") + with open(template_path, "r") as file: + template = file.read() + + # Replace placeholders in template + html_content = template.replace("{{user_name}}", user_name) + html_content = html_content.replace("{{reset_token}}", reset_token) + html_content = html_content.replace( + "{{reset_link}}", + f"https://example.com/reset-password?token={reset_token}" + ) + + # Send email using template + email_adapter.send( + to=user_email, + subject="Password Reset Request", + body=html_content, + body_type="html" + ) + + # Usage + send_password_reset( + "user@example.com", + "abc123xyz789", + "John Doe" + ) diff --git a/docs/source/examples/adapters/index.rst b/docs/source/examples/adapters/index.rst new file mode 100644 index 00000000..98579d93 --- /dev/null +++ b/docs/source/examples/adapters/index.rst @@ -0,0 +1,25 @@ +.. _examples_adapters: + +Adapter Utilities +=============== + +ArchiPy provides adapters for interfacing with external systems: + +.. toctree:: + :maxdepth: 1 + :caption: Adapter Modules: + + orm + redis + email + +Overview +-------- + +Adapters isolate your core application from external dependencies: + +* **ORM Adapters**: Interfaces for database operations (SQLAlchemy) +* **Redis Adapters**: Key-value store and caching adapters +* **Email Adapters**: Email delivery interfaces + +All adapters follow the ports and adapters pattern, with corresponding interfaces (ports) and implementations (adapters). diff --git a/docs/source/examples/adapters/orm.rst b/docs/source/examples/adapters/orm.rst new file mode 100644 index 00000000..00065c80 --- /dev/null +++ b/docs/source/examples/adapters/orm.rst @@ -0,0 +1,178 @@ +.. _examples_adapters_orm: + +ORM Adapters +========== + +Database operations using SQLAlchemy adapters. + +Configuration +------------ + +First, configure your database settings: + +.. code-block:: python + + from archipy.configs.base_config import BaseConfig + + class AppConfig(BaseConfig): + SQLALCHEMY = { + "DRIVER_NAME": "postgresql", + "USERNAME": "postgres", + "PASSWORD": "password", + "HOST": "localhost", + "PORT": 5432, + "DATABASE": "myapp", + "POOL_SIZE": 10 + } + + # Set global configuration + config = AppConfig() + BaseConfig.set_global(config) + +Defining Models +------------- + +Define your database models: + +.. code-block:: python + + from uuid import uuid4 + from sqlalchemy import Column, String, ForeignKey + from sqlalchemy.orm import relationship + from archipy.models.entities import BaseEntity + + class User(BaseEntity): + __tablename__ = "users" + + name = Column(String(100)) + email = Column(String(100), unique=True) + + # Relationships + posts = relationship("Post", back_populates="author") + + class Post(BaseEntity): + __tablename__ = "posts" + + title = Column(String(255)) + content = Column(String(1000)) + + # Foreign keys + author_id = Column(String(36), ForeignKey("users.test_uuid")) + + # Relationships + author = relationship("User", back_populates="posts") + +Synchronous Operations +------------------- + +Examples of synchronous database operations: + +.. code-block:: python + + from archipy.adapters.orm.sqlalchemy.session_manager_adapters import SessionManagerAdapter + from archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters import SqlAlchemyAdapter + from sqlalchemy import select + + # Create session manager and adapter + session_manager = SessionManagerAdapter() + adapter = SqlAlchemyAdapter(session_manager) + + # Create a user + def create_user(name, email): + user = User(test_uuid=uuid4(), name=name, email=email) + return adapter.create(user) + + # Query users + def find_users_by_name(name): + query = select(User).where(User.name == name) + users, total = adapter.execute_search_query(User, query) + return users + + # Update a user + def update_user_email(user_id, new_email): + user = adapter.get_by_uuid(User, user_id) + if user: + user.email = new_email + return adapter.update(user) + return None + + # Delete a user + def delete_user(user_id): + user = adapter.get_by_uuid(User, user_id) + if user: + adapter.delete(user) + return True + return False + +Asynchronous Operations +-------------------- + +Examples of asynchronous database operations: + +.. code-block:: python + + import asyncio + from archipy.adapters.orm.sqlalchemy.session_manager_adapters import AsyncSessionManagerAdapter + from archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters import AsyncSqlAlchemyAdapter + + # Create async session manager and adapter + async_session_manager = AsyncSessionManagerAdapter() + async_adapter = AsyncSqlAlchemyAdapter(async_session_manager) + + # Create a user asynchronously + async def create_user_async(name, email): + user = User(test_uuid=uuid4(), name=name, email=email) + return await async_adapter.create(user) + + # Query users asynchronously + async def find_users_by_name_async(name): + query = select(User).where(User.name == name) + users, total = await async_adapter.execute_search_query(User, query) + return users + + # Usage with asyncio + async def main(): + # Create a user + user = await create_user_async("John Doe", "john@example.com") + + # Find users + users = await find_users_by_name_async("John Doe") + print(f"Found {len(users)} users") + + # Run the async function + asyncio.run(main()) + +Transactions +----------- + +Using transactions to ensure consistency: + +.. code-block:: python + + from archipy.helpers.utils.atomic_transaction import atomic_transaction, async_atomic_transaction + + # Synchronous transaction + def transfer_points(from_user_id, to_user_id, amount): + with atomic_transaction(adapter.session_manager): + from_user = adapter.get_by_uuid(User, from_user_id) + to_user = adapter.get_by_uuid(User, to_user_id) + + # Perform operations atomically + from_user.points -= amount + to_user.points += amount + + adapter.update(from_user) + adapter.update(to_user) + + # Asynchronous transaction + async def transfer_points_async(from_user_id, to_user_id, amount): + async with async_atomic_transaction(async_adapter.session_manager): + from_user = await async_adapter.get_by_uuid(User, from_user_id) + to_user = await async_adapter.get_by_uuid(User, to_user_id) + + # Perform operations atomically + from_user.points -= amount + to_user.points += amount + + await async_adapter.update(from_user) + await async_adapter.update(to_user) diff --git a/docs/source/examples/adapters/redis.rst b/docs/source/examples/adapters/redis.rst new file mode 100644 index 00000000..64d0f521 --- /dev/null +++ b/docs/source/examples/adapters/redis.rst @@ -0,0 +1,185 @@ +.. _examples_adapters_redis: + +Redis Adapters +============ + +Working with Redis for caching and messaging. + +Configuration +------------ + +First, configure your Redis settings: + +.. code-block:: python + + from archipy.configs.base_config import BaseConfig + + class AppConfig(BaseConfig): + REDIS = { + "MASTER_HOST": "localhost", + "PORT": 6379, + "PASSWORD": "password", + "DB": 0, + "DECODE_RESPONSES": True + } + + # Set global configuration + config = AppConfig() + BaseConfig.set_global(config) + +Synchronous Operations +------------------- + +Examples of synchronous Redis operations: + +.. code-block:: python + + import json + from archipy.adapters.redis.redis_adapters import RedisAdapter + + # Create Redis adapter + redis = RedisAdapter() + + # Basic operations + def cache_user(user_id, user_data): + # Cache user data with 1 hour expiry + redis.set(f"user:{user_id}", json.dumps(user_data), ex=3600) + + def get_cached_user(user_id): + data = redis.get(f"user:{user_id}") + if data: + return json.loads(data) + return None + + def delete_cached_user(user_id): + redis.delete(f"user:{user_id}") + + # List operations + def add_to_recent_users(user_id): + # Add to list and trim to 10 most recent + redis.lpush("recent_users", user_id) + redis.ltrim("recent_users", 0, 9) + + def get_recent_users(): + return redis.lrange("recent_users", 0, -1) + + # Set operations + def add_user_role(user_id, role): + redis.sadd(f"user:{user_id}:roles", role) + + def has_user_role(user_id, role): + return redis.sismember(f"user:{user_id}:roles", role) + + def get_user_roles(user_id): + return redis.smembers(f"user:{user_id}:roles") + +Asynchronous Operations +-------------------- + +Examples of asynchronous Redis operations: + +.. code-block:: python + + import asyncio + import json + from archipy.adapters.redis.redis_adapters import AsyncRedisAdapter + + # Create async Redis adapter + async_redis = AsyncRedisAdapter() + + # Basic async operations + async def cache_user_async(user_id, user_data): + # Cache user data with 1 hour expiry + await async_redis.set(f"user:{user_id}", json.dumps(user_data), ex=3600) + + async def get_cached_user_async(user_id): + data = await async_redis.get(f"user:{user_id}") + if data: + return json.loads(data) + return None + + # Hash operations + async def update_user_profile(user_id, **fields): + # Update multiple fields in a hash + await async_redis.hset(f"profile:{user_id}", mapping=fields) + + async def get_user_profile(user_id): + # Get all fields from a hash + return await async_redis.hgetall(f"profile:{user_id}") + + # Usage with asyncio + async def main(): + # Cache a user + await cache_user_async("123", {"name": "John", "email": "john@example.com"}) + + # Update profile + await update_user_profile("123", + first_name="John", + last_name="Doe", + age=30) + + # Get cached data + user = await get_cached_user_async("123") + profile = await get_user_profile("123") + + print(f"User: {user}") + print(f"Profile: {profile}") + + # Run the async function + asyncio.run(main()) + +Pub/Sub Messaging +--------------- + +Using Redis for publish/subscribe messaging: + +.. code-block:: python + + import asyncio + import json + from archipy.adapters.redis.redis_adapters import AsyncRedisAdapter + + async_redis = AsyncRedisAdapter() + + # Publisher + async def publish_event(event_type, data): + message = json.dumps({ + "type": event_type, + "data": data, + "timestamp": datetime.utcnow().isoformat() + }) + await async_redis.publish(f"events:{event_type}", message) + + # Subscriber + async def subscribe_to_events(): + # Create a new connection for the subscription + pubsub = async_redis.pubsub() + + # Subscribe to channels + await pubsub.subscribe("events:user_created", "events:user_updated") + + # Process messages + async for message in pubsub.listen(): + if message["type"] == "message": + data = json.loads(message["data"]) + print(f"Received event: {data['type']}") + # Process event based on type + if data["type"] == "user_created": + await process_user_created(data["data"]) + elif data["type"] == "user_updated": + await process_user_updated(data["data"]) + + # Usage + async def main(): + # Start subscriber in the background + asyncio.create_task(subscribe_to_events()) + + # Publish events + await publish_event("user_created", {"id": "123", "name": "Alice"}) + await publish_event("user_updated", {"id": "123", "name": "Alice Smith"}) + + # Keep running to receive messages + await asyncio.sleep(10) + + # Run the async function + asyncio.run(main()) diff --git a/docs/source/examples/bdd_testing.rst b/docs/source/examples/bdd_testing.rst new file mode 100644 index 00000000..3c6c7e4d --- /dev/null +++ b/docs/source/examples/bdd_testing.rst @@ -0,0 +1,330 @@ +.. _examples_bdd: + +BDD Testing with ArchiPy +======================= + +ArchiPy provides comprehensive support for Behavior-Driven Development testing using Behave. + +Feature File Example +------------------ + +.. code-block:: gherkin + + # features/user_management.feature + Feature: User Management + + Background: + Given the application database is initialized + + Scenario: Create a new user + When a new user "John Doe" with email "john@example.com" is created + Then the user should exist in the database + And the user should have the correct email "john@example.com" + + @async + Scenario: Retrieve user asynchronously + Given a user with email "jane@example.com" exists + When I retrieve the user by email asynchronously + Then the user details should be retrieved successfully + +Environment Setup +--------------- + +ArchiPy's BDD framework uses scenario context isolation and pooling: + +.. code-block:: python + + # features/environment.py + import logging + import uuid + from behave.model import Scenario + from behave.runner import Context + + from archipy.configs.base_config import BaseConfig + from features.scenario_context_pool_manager import ScenarioContextPoolManager + + # Define test configuration + class TestConfig(BaseConfig): + # Test-specific configurations + SQLALCHEMY = { + "DRIVER_NAME": "sqlite", + "DATABASE": ":memory:", + "ISOLATION_LEVEL": None + } + + def before_all(context: Context): + """Setup performed once at the start of the test run.""" + # Set up logging + context.logger = logging.getLogger("behave") + + # Create scenario context pool + context.scenario_context_pool = ScenarioContextPoolManager() + + # Set global configuration for tests + config = TestConfig() + BaseConfig.set_global(config) + + context.logger.info("Test environment initialized") + + def before_scenario(context: Context, scenario: Scenario): + """Setup performed before each scenario.""" + # Generate unique ID for scenario + scenario_id = uuid.uuid4() + scenario.id = scenario_id + + # Get isolated context for this scenario + scenario_context = context.scenario_context_pool.get_context(scenario_id) + scenario_context.store("test_config", BaseConfig.global_config()) + + context.logger.info(f"Preparing scenario: {scenario.name} (ID: {scenario_id})") + + def after_scenario(context: Context, scenario: Scenario): + """Cleanup performed after each scenario.""" + # Get the scenario ID + scenario_id = getattr(scenario, "id", "unknown") + context.logger.info(f"Cleaning up scenario: {scenario.name} (ID: {scenario_id})") + + # Clean up the scenario context + if hasattr(context, "scenario_context_pool"): + context.scenario_context_pool.cleanup_context(scenario_id) + +Scenario Context Management +------------------------- + +Create a scenario context to isolate test state: + +.. code-block:: python + + # features/scenario_context.py + class ScenarioContext: + """A storage class for scenario-specific objects.""" + + def __init__(self, scenario_id): + """Initialize with a unique scenario ID.""" + self.scenario_id = scenario_id + self.storage = {} + self.db_file = None + self.adapter = None + self.async_adapter = None + self.entities = {} + + def store(self, key, value): + """Store a value in the context.""" + self.storage[key] = value + + def get(self, key, default=None): + """Get a value from the context.""" + return self.storage.get(key, default) + + def cleanup(self): + """Clean up resources used by this context.""" + if self.adapter: + try: + self.adapter.session_manager.remove_session() + except Exception as e: + print(f"Error in cleanup: {e}") + + # Handle async resources too + if self.async_adapter: + import asyncio + try: + temp_loop = asyncio.new_event_loop() + try: + asyncio.set_event_loop(temp_loop) + temp_loop.run_until_complete(self.async_adapter.session_manager.remove_session()) + finally: + temp_loop.close() + except Exception as e: + print(f"Error in async cleanup: {e}") + +Context Pool Manager +------------------ + +Manage multiple scenario contexts: + +.. code-block:: python + + # features/scenario_context_pool_manager.py + from uuid import UUID + + from archipy.helpers.metaclasses.singleton import Singleton + from features.scenario_context import ScenarioContext + + class ScenarioContextPoolManager(metaclass=Singleton): + """Manager for scenario-specific context objects.""" + + def __init__(self): + """Initialize the pool manager.""" + self.context_pool = {} + + def get_context(self, scenario_id: UUID) -> ScenarioContext: + """Get or create a scenario context for the given ID.""" + if scenario_id not in self.context_pool: + self.context_pool[scenario_id] = ScenarioContext(scenario_id) + return self.context_pool[scenario_id] + + def cleanup_context(self, scenario_id: UUID) -> None: + """Clean up a specific scenario context.""" + if scenario_id in self.context_pool: + self.context_pool[scenario_id].cleanup() + del self.context_pool[scenario_id] + + def cleanup_all(self) -> None: + """Clean up all scenario contexts.""" + for scenario_id, context in list(self.context_pool.items()): + context.cleanup() + del self.context_pool[scenario_id] + +Step Implementation +----------------- + +Implementing steps with context management: + +.. code-block:: python + + # features/steps/user_steps.py + from behave import given, when, then + from sqlalchemy import select + + from archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters import SqlAlchemyAdapter, AsyncSqlAlchemyAdapter + from archipy.adapters.orm.sqlalchemy.sqlalchemy_mocks import SqlAlchemyMock, AsyncSqlAlchemyMock + from features.test_helpers import get_current_scenario_context + from features.test_entity import User + + @given("the application database is initialized") + def step_given_database_initialized(context): + # Get isolated context for this scenario + scenario_context = get_current_scenario_context(context) + + # Create mock adapter for testing + adapter = SqlAlchemyMock() + scenario_context.adapter = adapter + + # Create tables + User.__table__.create(adapter.session_manager.engine) + + # Store adapter in context + scenario_context.store("adapter", adapter) + + @when('a new user "{name}" with email "{email}" is created') + def step_when_create_user(context, name, email): + scenario_context = get_current_scenario_context(context) + adapter = scenario_context.get("adapter") + + # Create user with atomic transaction + from archipy.helpers.utils.atomic_transaction import atomic_transaction + + with atomic_transaction(adapter.session_manager): + user = User(name=name, email=email) + adapter.create(user) + scenario_context.store("user", user) + + @then("the user should exist in the database") + def step_then_user_exists(context): + scenario_context = get_current_scenario_context(context) + adapter = scenario_context.get("adapter") + user = scenario_context.get("user") + + # Query for user + stored_user = adapter.get_by_uuid(User, user.test_uuid) + assert stored_user is not None + + @given('a user with email "{email}" exists') + def step_given_user_exists(context, email): + scenario_context = get_current_scenario_context(context) + + # Create async mock adapter + adapter = AsyncSqlAlchemyMock() + scenario_context.async_adapter = adapter + + # Create tables + import asyncio + + async def setup_async_db(): + await User.__table__.create(adapter.session_manager.engine) + + # Create a test user + user = User(name="Test User", email=email) + await adapter.create(user) + return user + + # Run async setup + user = asyncio.run(setup_async_db()) + scenario_context.store("async_user", user) + scenario_context.store("async_adapter", adapter) + + @when("I retrieve the user by email asynchronously") + def step_when_retrieve_user_async(context): + import asyncio + scenario_context = get_current_scenario_context(context) + adapter = scenario_context.get("async_adapter") + user = scenario_context.get("async_user") + + async def retrieve_user(): + from archipy.helpers.utils.atomic_transaction import async_atomic_transaction + + async with async_atomic_transaction(adapter.session_manager): + query = select(User).where(User.email == user.email) + users, total = await adapter.execute_search_query(User, query) + return users[0] if users else None + + # Run async retrieval + retrieved_user = asyncio.run(retrieve_user()) + scenario_context.store("retrieved_user", retrieved_user) + + @then("the user details should be retrieved successfully") + def step_then_user_details_match(context): + scenario_context = get_current_scenario_context(context) + original_user = scenario_context.get("async_user") + retrieved_user = scenario_context.get("retrieved_user") + + assert retrieved_user is not None + assert retrieved_user.test_uuid == original_user.test_uuid + assert retrieved_user.email == original_user.email + +Helper Utilities +-------------- + +ArchiPy provides test helpers for BDD scenarios: + +.. code-block:: python + + # features/test_helpers.py + from behave.runner import Context + + def get_current_scenario_context(context: Context): + """Get the current scenario context from the context pool.""" + scenario_id = context.scenario._id + return context.scenario_context_pool.get_context(scenario_id) + + class SafeAsyncContextManager: + """A safe async context manager for use in both sync and async code.""" + + def __init__(self, context_manager_factory): + self.context_manager_factory = context_manager_factory + + async def __aenter__(self): + self.context_manager = self.context_manager_factory() + return await self.context_manager.__aenter__() + + async def __aexit__(self, exc_type, exc_val, exc_tb): + return await self.context_manager.__aexit__(exc_type, exc_val, exc_tb) + +Running BDD Tests +--------------- + +Execute tests with the behave command: + +.. code-block:: bash + + # Run all tests + behave + + # Run specific feature + behave features/user_management.feature + + # Run async scenarios + behave --tags=@async + + # Generate HTML report + behave -f html -o reports/behave-report.html diff --git a/docs/source/examples/config_management.rst b/docs/source/examples/config_management.rst new file mode 100644 index 00000000..8e26482c --- /dev/null +++ b/docs/source/examples/config_management.rst @@ -0,0 +1,62 @@ +.. _examples_config: + +Configuration Management Examples +================================ + +Basic Configuration +------------------ + +.. code-block:: python + + from archipy.configs.base_config import BaseConfig + + # Define a custom configuration + class MyAppConfig(BaseConfig): + # Override defaults + AUTH = { + "SECRET_KEY": "my-secure-key", + "TOKEN_LIFETIME_MINUTES": 30 + } + + # Add custom fields + APP_NAME = "My Application" + DEBUG = True + + # Set as global configuration + config = MyAppConfig() + BaseConfig.set_global(config) + + # Access from anywhere + from archipy.configs.base_config import BaseConfig + current_config = BaseConfig.global_config() + print(current_config.APP_NAME) # "My Application" + +Environment Variables +------------------- + +ArchiPy configs automatically load from environment variables: + +.. code-block:: bash + + # .env file + AUTH__SECRET_KEY=production-secret-key + AUTH__TOKEN_LIFETIME_MINUTES=15 + APP_NAME=Production App + +Hierarchical Configuration +------------------------- + +.. code-block:: python + + from archipy.configs.base_config import BaseConfig + from archipy.configs.config_template import RedisConfig, SqlAlchemyConfig + + class MyAppConfig(BaseConfig): + # Override specific Redis settings + REDIS = RedisConfig( + MASTER_HOST="redis.example.com", + PORT=6380, + PASSWORD="redis-password" + ) + + # Keep other defaults diff --git a/docs/source/examples/helpers/decorators.rst b/docs/source/examples/helpers/decorators.rst new file mode 100644 index 00000000..ed2bf6ee --- /dev/null +++ b/docs/source/examples/helpers/decorators.rst @@ -0,0 +1,68 @@ +.. _examples_helpers_decorators: + +Decorators +========= + +Examples of ArchiPy's decorator utilities: + +retry +----- + +Automatically retry functions that might fail temporarily: + +.. code-block:: python + + from archipy.helpers.decorators.retry import retry + import requests + + # Retry up to 3 times with exponential backoff + @retry(max_attempts=3, backoff_factor=2) + def fetch_data(url): + response = requests.get(url, timeout=5) + response.raise_for_status() + return response.json() + + try: + data = fetch_data("https://api.example.com/data") + except Exception as e: + print(f"Failed after retries: {e}") + +rate_limit +--------- + +Control the frequency of function calls: + +.. code-block:: python + + from archipy.helpers.decorators.rate_limit import rate_limit + + # Limit to 5 calls per minute + @rate_limit(max_calls=5, period=60) + def send_notification(user_id, message): + # Send notification logic + print(f"Sent to {user_id}: {message}") + + # Will automatically throttle if called too frequently + for i in range(10): + send_notification(f"user_{i}", "Important update!") + +deprecation_exception +------------------- + +Mark functions as deprecated: + +.. code-block:: python + + from archipy.helpers.decorators.deprecation_exception import method_deprecation_error, class_deprecation_error + + # Method deprecation + @method_deprecation_error(operation="get_user_by_username", lang="en") + def get_user_by_username(username): + # This will raise a DeprecationError when called + pass + + # Class deprecation + @class_deprecation_error(lang="en") + class OldUserService: + # Using this class will raise a DeprecationError + pass diff --git a/docs/source/examples/helpers/index.rst b/docs/source/examples/helpers/index.rst new file mode 100644 index 00000000..7f21d28c --- /dev/null +++ b/docs/source/examples/helpers/index.rst @@ -0,0 +1,50 @@ +.. _examples_helpers: + +Helper Utilities +=============== + +ArchiPy provides various helper utilities organized by module: + +.. toctree:: + :maxdepth: 1 + :caption: Helper Modules: + + decorators + utils + metaclasses + interceptors + +Overview +-------- + +Helpers are organized into four main categories: + +* **Decorators**: Function and class decorators for cross-cutting concerns +* **Utilities**: Common utility functions for routine tasks +* **Metaclasses**: Reusable class patterns through metaclasses +* **Interceptors**: Middleware components for request processing + +Quick Examples +------------- + +.. code-block:: python + + # Retry pattern for resilient operations + from archipy.helpers.decorators.retry import retry + + @retry(max_attempts=3) + def fetch_external_data(): + # Will retry up to 3 times if this fails + pass + + # Singleton pattern for global resources + from archipy.helpers.metaclasses.singleton import Singleton + + class DatabaseConnection(metaclass=Singleton): + # Only one instance will ever exist + pass + + # Date/time utilities for consistent handling + from archipy.helpers.utils.datetime_utils import get_utc_now + + timestamp = get_utc_now() # Always UTC-aware diff --git a/docs/source/examples/helpers/interceptors.rst b/docs/source/examples/helpers/interceptors.rst new file mode 100644 index 00000000..f2f4cad9 --- /dev/null +++ b/docs/source/examples/helpers/interceptors.rst @@ -0,0 +1,48 @@ +.. _examples_helpers_interceptors: + +Interceptors +=========== + +Examples of ArchiPy's interceptor utilities: + +fastapi_rest_rate_limit_handler +----------------------------- + +Control API request rates for FastAPI: + +.. code-block:: python + + from archipy.helpers.interceptors.fastapi.rate_limit.fastapi_rest_rate_limit_handler import FastAPIRestRateLimitHandler + from fastapi import FastAPI, Request, Response + + app = FastAPI() + + # Set up rate limiting + rate_limiter = FastAPIRestRateLimitHandler( + limit=100, # 100 requests + window_seconds=60, # per minute + key_func=lambda request: request.client.host # by IP address + ) + + @app.middleware("http") + async def rate_limit_middleware(request: Request, call_next): + # Check rate limit + limited, headers = await rate_limiter.check_rate_limit(request) + + if limited: + # Rate limit exceeded + response = Response( + content="Rate limit exceeded. Try again later.", + status_code=429, + headers=headers + ) + return response + + # Continue with request + response = await call_next(request) + + # Add rate limit headers to response + for key, value in headers.items(): + response.headers[key] = value + + return response diff --git a/docs/source/examples/helpers/metaclasses.rst b/docs/source/examples/helpers/metaclasses.rst new file mode 100644 index 00000000..74176661 --- /dev/null +++ b/docs/source/examples/helpers/metaclasses.rst @@ -0,0 +1,34 @@ +.. _examples_helpers_metaclasses: + +Metaclasses +========== + +Examples of ArchiPy's metaclass utilities: + +singleton +-------- + +Ensure only one instance of a class exists: + +.. code-block:: python + + from archipy.helpers.metaclasses.singleton import Singleton + + class ConfigManager(metaclass=Singleton): + def __init__(self): + self.settings = {} + + def load_config(self, path): + # Load configuration logic + self.settings = {"key": "value"} + + # Usage + config1 = ConfigManager() + config1.load_config("config.json") + + config2 = ConfigManager() + # Both variables reference the same instance + print(config1 is config2) # True + + # Settings are shared + print(config2.settings) # {"key": "value"} diff --git a/docs/source/examples/helpers/utils.rst b/docs/source/examples/helpers/utils.rst new file mode 100644 index 00000000..79fe1c30 --- /dev/null +++ b/docs/source/examples/helpers/utils.rst @@ -0,0 +1,229 @@ +.. _examples_helpers_utils: + +Utilities +======== + +Examples of ArchiPy's utility functions: + +datetime_utils +------------- + +Work with dates and times consistently: + +.. code-block:: python + + from archipy.helpers.utils.datetime_utils import DateTimeUtils + + # Get current UTC time + now = DateTimeUtils.get_utc_now() + + # Format for storage/transmission + date_str = DateTimeUtils.convert_datetime_to_string(now) + + # Parse date string + parsed = DateTimeUtils.convert_string_to_datetime(date_str) + + # Convert to Jalali (Persian) calendar + jalali_date = DateTimeUtils.get_jalali_date(now) + + # Check if date is a holiday in Iran + is_holiday = DateTimeUtils.is_holiday_in_iran(now) + +jwt_utils +-------- + +Generate and verify JWT tokens: + +.. code-block:: python + + from archipy.helpers.utils.jwt_utils import JWTUtils + from uuid import uuid4 + + # Generate a user access token + user_id = uuid4() + access_token = JWTUtils.generate_access_token(user_id) + + # Generate a refresh token + refresh_token = JWTUtils.generate_refresh_token(user_id) + + # Verify a token + try: + payload = JWTUtils.verify_jwt(access_token) + print(f"Token valid for user: {payload['sub']}") + except Exception as e: + print(f"Invalid token: {e}") + +password_utils +------------ + +Secure password handling: + +.. code-block:: python + + from archipy.helpers.utils.password_utils import PasswordUtils + + # Hash a password + password = "SecureP@ssword123" + hashed = PasswordUtils.hash_password(password) + + # Verify password + is_valid = PasswordUtils.verify_password(password, hashed) + print(f"Password valid: {is_valid}") + + # Generate a secure password + secure_password = PasswordUtils.generate_password(length=12) + print(f"Generated password: {secure_password}") + +file_utils +--------- + +Handle files securely: + +.. code-block:: python + + from archipy.helpers.utils.file_utils import FileUtils + + # Generate secure link to file + link = FileUtils.generate_secure_file_link("/path/to/document.pdf", expires_in=3600) + + # Validate file extension + is_valid = FileUtils.validate_file_extension("document.pdf", ["pdf", "docx", "txt"]) + print(f"File is valid: {is_valid}") + +base_utils +--------- + +Validate and sanitize data: + +.. code-block:: python + + from archipy.helpers.utils.base_utils import BaseUtils + + # Sanitize phone number + phone = BaseUtils.sanitize_phone_number("+989123456789") + print(phone) # 09123456789 + + # Validate Iranian national code + try: + BaseUtils.validate_iranian_national_code_pattern("1234567891") + print("National code is valid") + except Exception as e: + print(f"Invalid national code: {e}") + +error_utils +--------- + +Standardized exception handling: + +.. code-block:: python + + from archipy.helpers.utils.error_utils import ErrorUtils + from archipy.models.errors import BaseError + from archipy.models.types.exception_message_types import ExceptionMessageType + + # Create exception detail + detail = ErrorUtils.create_exception_detail( + ExceptionMessageType.INVALID_PHONE, + lang="en" + ) + + # Handle exceptions + try: + # Some code that might fail + raise ValueError("Something went wrong") + except Exception as e: + ErrorUtils.capture_exception(e) + +app_utils +-------- + +FastAPI application utilities: + +.. code-block:: python + + from archipy.helpers.utils.app_utils import AppUtils, FastAPIUtils + from archipy.configs.base_config import BaseConfig + + # Create a FastAPI app with standard config + app = AppUtils.create_fastapi_app(BaseConfig.global_config()) + + # Add custom exception handlers + FastAPIUtils.add_exception_handlers(app) + + # Generate unique route IDs + route_id = FastAPIUtils.generate_unique_route_id("users", "get_user") + + # Set up CORS + FastAPIUtils.setup_cors( + app, + allowed_origins=["https://example.com"] + ) + +transaction_utils +---------------- + +Database transaction management: + +.. code-block:: python + + from archipy.helpers.utils.transaction_utils import TransactionUtils + from archipy.adapters.orm.sqlalchemy.session_manager_adapters import SessionManagerAdapter + + # Synchronous transaction + session_manager = SessionManagerAdapter() + + with TransactionUtils.atomic_transaction(session_manager): + # Database operations here + # Will be committed if successful, rolled back if exception occurs + pass + + # Asynchronous transaction + async with TransactionUtils.async_atomic_transaction(async_session_manager): + # Async database operations here + pass + +string_utils +---------- + +String manipulation utilities: + +.. code-block:: python + + from archipy.helpers.utils.string_utils import StringUtils + + # Convert camel case to snake case + snake = StringUtils.camel_to_snake("thisIsACamelCaseString") + print(snake) # this_is_a_camel_case_string + + # Convert snake case to camel case + camel = StringUtils.snake_to_camel("this_is_a_snake_case_string") + print(camel) # thisIsASnakeCaseString + + # Generate a random string + random_str = StringUtils.generate_random_string(length=10) + print(random_str) + + # Mask sensitive data + masked = StringUtils.mask_sensitive_data("1234567890123456", show_last=4) + print(masked) # ************3456 + +validator_utils +------------- + +Validate input data: + +.. code-block:: python + + from archipy.helpers.utils.validator_utils import ValidatorUtils + + # Validate email + is_valid_email = ValidatorUtils.is_valid_email("user@example.com") + print(f"Valid email: {is_valid_email}") + + # Validate phone number + is_valid_phone = ValidatorUtils.is_valid_phone_number("+15551234567") + print(f"Valid phone: {is_valid_phone}") + + # Validate URL + is_valid_url = ValidatorUtils.is_valid_url("https://example.com") + print(f"Valid URL: {is_valid_url}") diff --git a/docs/source/index.rst b/docs/source/index.rst index 5cee216c..5676cd33 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,30 +1,56 @@ .. ArchiPy documentation master file -Welcome to ArchiPy's Documentation -================================== - -**Architecture + Python – Structured Development Simplified** - -ArchiPy is a Python framework designed to provide a standardized, scalable, and maintainable architecture for modern applications. Built with Python 3.13+, it offers tools and best practices for configuration management, testing, and development workflows, adhering to clean architecture principles. +Welcome to ArchiPy +================= .. image:: https://img.shields.io/badge/python-3.13+-blue.svg :target: https://www.python.org/downloads/ :alt: Python 3.13+ -Goals ------ +.. image:: ../assets/logo.jpg + :alt: ArchiPy Logo + :width: 150 + :align: right + +**Architecture + Python – Structured Development Simplified** + +ArchiPy provides a clean architecture framework for Python applications that: + +* Standardizes configuration management +* Offers pluggable adapters with testing mocks +* Enforces consistent data models +* Promotes maintainable code organization +* Simplifies testing with BDD support + +.. grid:: 2 + + .. grid-item-card:: πŸš€ Quick Start + :link: installation + :link-type: ref -ArchiPy aims to: + Get started with ArchiPy in minutes -1. **Standardize Configuration**: Simplify config management and injection. -2. **Provide Adapters & Mocks**: Enable delegation with testable mocks. -3. **Unify Data Models**: Offer base entities and DTOs for consistency. -4. **Enhance Productivity**: Include helpers, utils, and decorators. -5. **Support BDD**: Facilitate behavior-driven testing for sync/async scenarios. -6. **Enforce Best Practices**: Use modern tooling for optimal Python development. + .. grid-item-card:: πŸ“š Features + :link: features + :link-type: ref + + Explore what ArchiPy offers + + .. grid-item-card:: πŸ—οΈ Architecture + :link: architecture + :link-type: ref + + Learn about the design principles + + .. grid-item-card:: πŸ” API Reference + :link: api_reference/index + :link-type: ref + + Detailed API documentation .. toctree:: :maxdepth: 2 + :hidden: :caption: Contents: installation @@ -37,6 +63,16 @@ ArchiPy aims to: changelog license +.. toctree:: + :maxdepth: 1 + :hidden: + :caption: Examples: + + examples/config_management + examples/adapters/index + examples/helpers/index + examples/bdd_testing + Quick Start ---------- diff --git a/docs/source/usage.rst b/docs/source/usage.rst index b2d429cc..15a3de30 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -1,16 +1,155 @@ .. _usage: -Usage -===== - Getting Started --------------- +============== + +This guide will help you start building applications with ArchiPy. + +Basic Setup +---------- + +1. First, initialize your application with a configuration: + + .. code-block:: python + + from archipy.configs.base_config import BaseConfig + + class AppConfig(BaseConfig): + # Custom configuration + pass + + # Set as global config + config = AppConfig() + BaseConfig.set_global(config) + +2. Define your domain models: + + .. code-block:: python + + from uuid import uuid4 + from sqlalchemy import Column, String, ForeignKey + from sqlalchemy.orm import relationship + from archipy.models.entities import BaseEntity + + class User(BaseEntity): + __tablename__ = "users" + + username = Column(String(100), unique=True) + email = Column(String(255), unique=True) + + # Relationships + posts = relationship("Post", back_populates="author") + + class Post(BaseEntity): + __tablename__ = "posts" + + title = Column(String(255)) + content = Column(String(1000)) + + # Foreign keys + author_id = Column(UUID, ForeignKey("users.test_uuid")) + + # Relationships + author = relationship("User", back_populates="posts") + +3. Set up your database adapter: + + .. code-block:: python + + from archipy.adapters.orm.sqlalchemy.session_manager_adapters import SessionManagerAdapter + from archipy.adapters.orm.sqlalchemy.sqlalchemy_adapters import SqlAlchemyAdapter + + # Create session manager + session_manager = SessionManagerAdapter() + + # Create adapter + db_adapter = SqlAlchemyAdapter(session_manager) + + # Create tables (development only) + BaseEntity.metadata.create_all(session_manager.engine) + +4. Implement your repositories: + + .. code-block:: python + + from sqlalchemy import select + + class UserRepository: + def __init__(self, db_adapter): + self.db_adapter = db_adapter + + def create(self, username, email): + user = User(test_uuid=uuid4(), username=username, email=email) + return self.db_adapter.create(user) + + def get_by_username(self, username): + query = select(User).where(User.username == username) + users, _ = self.db_adapter.execute_search_query(User, query) + return users[0] if users else None + +5. Implement your business logic: + + .. code-block:: python + + class UserService: + def __init__(self, user_repository): + self.user_repository = user_repository + + def register_user(self, username, email): + # Business logic here (validation, etc.) + return self.user_repository.create(username, email) + +Working with Redis +---------------- + +For caching or other Redis operations: + +.. code-block:: python + + from archipy.adapters.redis.redis_adapters import RedisAdapter + + # Create Redis adapter + redis_adapter = RedisAdapter() + + # Cache user data + def cache_user(user): + user_data = { + "username": user.username, + "email": user.email + } + redis_adapter.set(f"user:{user.test_uuid}", json.dumps(user_data), ex=3600) + + # Get cached user + def get_cached_user(user_id): + data = redis_adapter.get(f"user:{user_id}") + return json.loads(data) if data else None + +Working with FastAPI +------------------ -After installing ArchiPy, import its components to leverage its structured architecture: +Integrate with FastAPI: .. code-block:: python - import archipy + from fastapi import FastAPI, Depends, HTTPException + from archipy.helpers.utils.app_utils import AppUtils + + # Create FastAPI app + app = AppUtils.create_fastapi_app(BaseConfig.global_config()) + + # Create dependencies + def get_user_service(): + user_repo = UserRepository(db_adapter) + return UserService(user_repo) + + # Define routes + @app.post("/users/") + def create_user(username: str, email: str, service: UserService = Depends(get_user_service)): + try: + user = service.register_user(username, email) + return {"id": str(user.test_uuid), "username": user.username} + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) Examples --------