Phạm Văn Nguyên commited on
Commit
1678967
·
unverified ·
0 Parent(s):

Initial commit

Browse files
.dockerignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .venv
.env.example ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # Application Settings
2
+ APP_NAME=template-python
3
+ DEBUG=true
4
+ ENV=development
.github/workflows/build.yaml ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Docker Build
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - develop
8
+ - "feature/**"
9
+ - "hotfix/**"
10
+ tags:
11
+ - "v*.*.*"
12
+ pull_request:
13
+ branches:
14
+ - main
15
+ - develop
16
+
17
+ jobs:
18
+ docker:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: pnstack/actions/docker-publish@main
22
+ with:
23
+ platforms: linux/amd64,linux/arm64
24
+ dockerfile: Dockerfile
25
+ context: .
26
+ push_enabled: true
27
+ registry: ghcr.io
.github/workflows/deploy-to-huggingface.yml ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Deploy to Hugging Face
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+
9
+ jobs:
10
+ deploy:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ with:
15
+ fetch-depth: 0
16
+ lfs: true
17
+
18
+ - name: Setup Python
19
+ uses: actions/setup-python@v4
20
+ with:
21
+ python-version: '3.11'
22
+ cache: 'pip'
23
+
24
+ - name: Config git
25
+ run: |
26
+ git config --global credential.helper store
27
+ git config --global user.email "actions@github.com"
28
+ git config --global user.name "github-actions"
29
+
30
+ - name: Configure Hugging Face
31
+ env:
32
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
33
+ HF_USERNAME: ${{ secrets.HF_USERNAME }}
34
+ run: |
35
+ # Install Hugging Face CLI
36
+ pip install huggingface-hub
37
+ # Login to Hugging Face
38
+ huggingface-cli login --token $HF_TOKEN --add-to-git-credential
39
+
40
+ - name: Push to Hugging Face
41
+ env:
42
+ HF_USERNAME: ${{ secrets.HF_USERNAME }}
43
+ SPACE_NAME: ${{ secrets.SPACE_NAME }}
44
+ run: |
45
+ # Create repository URL
46
+ REPO_URL="https://huggingface.co/spaces/$HF_USERNAME/template-python"
47
+
48
+ # Add Hugging Face as a remote and push
49
+ git remote add space $REPO_URL || git remote set-url space $REPO_URL
50
+ git push -f space main
.gitignore ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
110
+ .pdm.toml
111
+ .pdm-python
112
+ .pdm-build/
113
+
114
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115
+ __pypackages__/
116
+
117
+ # Celery stuff
118
+ celerybeat-schedule
119
+ celerybeat.pid
120
+
121
+ # SageMath parsed files
122
+ *.sage.py
123
+
124
+ # Environments
125
+ .env
126
+ .venv
127
+ env/
128
+ venv/
129
+ ENV/
130
+ env.bak/
131
+ venv.bak/
132
+
133
+ # Spyder project settings
134
+ .spyderproject
135
+ .spyproject
136
+
137
+ # Rope project settings
138
+ .ropeproject
139
+
140
+ # mkdocs documentation
141
+ /site
142
+
143
+ # mypy
144
+ .mypy_cache/
145
+ .dmypy.json
146
+ dmypy.json
147
+
148
+ # Pyre type checker
149
+ .pyre/
150
+
151
+ # pytype static type analyzer
152
+ .pytype/
153
+
154
+ # Cython debug symbols
155
+ cython_debug/
156
+
157
+ # PyCharm
158
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
161
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
162
+ #.idea/
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.11
.vscode/settings.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "python.testing.pytestArgs": ["tests"],
3
+ "python.testing.unittestEnabled": false,
4
+ "python.testing.pytestEnabled": true,
5
+ "[python]": {
6
+ "editor.formatOnSave": true,
7
+ "editor.defaultFormatter": "ms-python.black-formatter",
8
+ "editor.codeActionsOnSave": {
9
+ "source.organizeImports": "explicit",
10
+ "source.fixAll": "explicit"
11
+ }
12
+ },
13
+ "editor.formatOnSave": true,
14
+ "python.analysis.typeCheckingMode": "basic",
15
+ "ruff.organizeImports": true,
16
+ "ruff.fixAll": true
17
+ }
Dockerfile ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use a Python image with uv pre-installed
2
+ FROM ghcr.io/astral-sh/uv:python3.11-bookworm
3
+
4
+ # Install the project into `/app`
5
+ WORKDIR /app
6
+
7
+ # Enable bytecode compilation
8
+ ENV UV_COMPILE_BYTECODE=1
9
+
10
+ # Copy from the cache instead of linking since it's a mounted volume
11
+ ENV UV_LINK_MODE=copy
12
+
13
+ # Install the project's dependencies using the lockfile and settings
14
+ RUN --mount=type=cache,target=/root/.cache/uv \
15
+ --mount=type=bind,source=uv.lock,target=uv.lock \
16
+ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
17
+ uv sync --frozen --no-install-project --no-dev
18
+
19
+ # Then, add the rest of the project source code and install it
20
+ # Installing separately from its dependencies allows optimal layer caching
21
+ ADD . /app
22
+ RUN --mount=type=cache,target=/root/.cache/uv \
23
+ uv sync --frozen --no-dev
24
+
25
+ # Place executables in the environment at the front of the path
26
+ ENV PATH="/app/.venv/bin:$PATH"
27
+
28
+ # Reset the entrypoint, don't invoke `uv`
29
+ ENTRYPOINT []
30
+
31
+ # Run setup.py
32
+
33
+ RUN python setup.py
34
+
35
+ EXPOSE 8080
36
+ # Run the FastAPI application by default
37
+ # Uses `fastapi dev` to enable hot-reloading when the `watch` sync occurs
38
+ # Uses `--host 0.0.0.0` to allow access from outside the container
39
+ CMD ["python", "main.py"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024-present Your Name or Organization
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
Makefile ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .PHONY: help install dev-install test format lint type-check clean build run
2
+
3
+ help: ## Show this help menu
4
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
5
+
6
+ install: ## Install production dependencies
7
+ uv pip install .
8
+
9
+ dev-install: ## Install development dependencies
10
+ uv pip install -e ".[dev]"
11
+
12
+ test: ## Run tests with pytest
13
+ pytest -v --cov=src --cov-report=term-missing
14
+
15
+ format: ## Format code with black and isort
16
+ black .
17
+ isort .
18
+
19
+ lint: ## Lint code with ruff
20
+ ruff check .
21
+
22
+ type-check: ## Run type checking with mypy
23
+ mypy src tests
24
+
25
+ clean: ## Clean build artifacts
26
+ rm -rf build/ dist/ *.egg-info/ .coverage .pytest_cache/ .mypy_cache/ .ruff_cache/
27
+ find . -type d -name __pycache__ -exec rm -rf {} +
28
+
29
+ build: ## Build Docker image
30
+ docker build -t template-python .
31
+
32
+ run: ## Run Docker container
33
+ docker run -it --rm template-python
34
+
35
+ package: ## Create requirements.txt
36
+ uv pip freeze > requirements.txt
37
+
38
+ setup: ## Initial project setup
39
+ uv venv
40
+ $(MAKE) dev-install
41
+ cp .env.example .env
README.md ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Python Project Template
3
+ emoji: 🟧
4
+ colorFrom: yellow
5
+ colorTo: purple
6
+ sdk: docker
7
+ tags:
8
+ - Python
9
+ fullwidth: true
10
+ license: mit
11
+ app_port: 8080
12
+ ---
13
+
14
+ # Python Project Template
15
+
16
+ A modern Python project template with best practices for development, testing, and deployment.
17
+
18
+ ## Features
19
+
20
+ - Modern Python (3.11+) project structure
21
+ - Development tools configuration (pytest, black, isort, mypy, ruff)
22
+ - Docker support
23
+ - GitHub Actions ready
24
+ - Comprehensive documentation structure
25
+ - Jupyter notebook support
26
+
27
+ ## Project Structure
28
+
29
+ ```
30
+ .
31
+ ├── docs/ # Documentation files
32
+ ├── notebooks/ # Jupyter notebooks
33
+ ├── src/ # Source code
34
+ │ ├── common/ # Common utilities and shared code
35
+ │ ├── modules/ # Feature modules
36
+ │ │ └── api/ # API related code
37
+ │ ├── shared/ # Shared resources
38
+ │ └── utils/ # Utility functions
39
+ └── tests/ # Test files
40
+ ```
41
+
42
+ ## Getting Started
43
+
44
+ ### Prerequisites
45
+
46
+ - Python 3.11 or higher
47
+ - [uv](https://github.com/astral-sh/uv) for dependency management
48
+
49
+ ### Installation
50
+
51
+ 1. Clone the repository:
52
+ ```bash
53
+ git clone https://github.com/yourusername/template-python.git
54
+ cd template-python
55
+ ```
56
+
57
+ 2. Create a virtual environment and install dependencies:
58
+ ```bash
59
+ uv venv
60
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
61
+ uv pip install -e ".[dev]"
62
+ ```
63
+
64
+ 3. Copy the environment file and adjust as needed:
65
+ ```bash
66
+ cp .env.example .env
67
+ ```
68
+
69
+ ### Development
70
+
71
+ This project uses several development tools:
72
+
73
+ - **pytest**: Testing framework
74
+ - **black**: Code formatting
75
+ - **isort**: Import sorting
76
+ - **mypy**: Static type checking
77
+ - **ruff**: Fast Python linter
78
+
79
+ Run tests:
80
+ ```bash
81
+ pytest
82
+ ```
83
+
84
+ Format code:
85
+ ```bash
86
+ black .
87
+ isort .
88
+ ```
89
+
90
+ Run type checking:
91
+ ```bash
92
+ mypy src tests
93
+ ```
94
+
95
+ Run linting:
96
+ ```bash
97
+ ruff check .
98
+ ```
99
+
100
+ ### Docker
101
+
102
+ Build the Docker image:
103
+ ```bash
104
+ make build
105
+ ```
106
+
107
+ Run the container:
108
+ ```bash
109
+ make run
110
+ ```
111
+
112
+ ## Contributing
113
+
114
+ 1. Fork the repository
115
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
116
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
117
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
118
+ 5. Open a Pull Request
119
+
120
+ ## License
121
+
122
+ This project is licensed under the MIT License - see the LICENSE file for details.
docs/README.md ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Project Documentation
2
+
3
+ Welcome to the documentation for the Python Template Project.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Getting Started](getting-started.md)
8
+ 2. [Project Structure](project-structure.md)
9
+ 3. [Development Guide](development.md)
10
+ 4. [API Documentation](api.md)
11
+ 5. [Testing Guide](testing.md)
12
+ 6. [Deployment Guide](deployment.md)
13
+
14
+ ## Overview
15
+
16
+ This template provides a foundation for Python projects with:
17
+
18
+ - Modern Python project structure
19
+ - Development tooling configuration
20
+ - Black for code formatting
21
+ - Ruff for linting and import sorting
22
+ - Type checking support
23
+ - Testing framework setup
24
+ - Docker support
25
+ - Documentation templates
26
+ - CI/CD examples
27
+
28
+ ## Development Setup
29
+
30
+ ### Code Quality Tools
31
+
32
+ This project uses modern Python code quality tools:
33
+
34
+ - **Black**: Code formatter that enforces a consistent style
35
+ - **Ruff**: Fast Python linter and import sorter
36
+ - Enforces PEP 8 style guide
37
+ - Sorts imports automatically
38
+ - Checks for common errors and anti-patterns
39
+ - Type checking enforcement
40
+
41
+ VSCode is configured to automatically:
42
+ - Format code on save using Black
43
+ - Run Ruff for linting and import sorting
44
+ - Provide type checking feedback
45
+
46
+ ## Quick Links
47
+
48
+ - [Installation Guide](getting-started.md#installation)
49
+ - [Development Setup](development.md#setup)
50
+ - [Running Tests](testing.md#running-tests)
51
+ - [Docker Guide](deployment.md#docker)
52
+ - [API Reference](api.md#endpoints)
53
+
54
+ ## Contributing
55
+
56
+ See our [Contributing Guide](CONTRIBUTING.md) for details on how to contribute to this project.
57
+
58
+ ## License
59
+
60
+ This project is licensed under the MIT License - see the [LICENSE](../LICENSE) file for details.
main.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import logging
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Dict
7
+
8
+
9
+ def main() -> None:
10
+ """Main application entry point."""
11
+ try:
12
+ print("Start app...")
13
+ import src.modules.api
14
+
15
+ except Exception as e:
16
+ logging.error(f"Application failed to start: {e}", exc_info=True)
17
+ raise
18
+
19
+
20
+ if __name__ == "__main__":
21
+ main()
notebooks/notes.ipynb ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import os"
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "code",
14
+ "execution_count": 2,
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "# Import the parent directory into the path\n",
19
+ "import sys\n",
20
+ "sys.path.append(\"..\")"
21
+ ]
22
+ },
23
+ {
24
+ "cell_type": "code",
25
+ "execution_count": 3,
26
+ "metadata": {},
27
+ "outputs": [
28
+ {
29
+ "data": {
30
+ "text/plain": [
31
+ "56277"
32
+ ]
33
+ },
34
+ "execution_count": 3,
35
+ "metadata": {},
36
+ "output_type": "execute_result"
37
+ }
38
+ ],
39
+ "source": [
40
+ "os.getpid()"
41
+ ]
42
+ },
43
+ {
44
+ "cell_type": "code",
45
+ "execution_count": 3,
46
+ "metadata": {},
47
+ "outputs": [],
48
+ "source": [
49
+ "from src.utils.math import hello"
50
+ ]
51
+ },
52
+ {
53
+ "cell_type": "code",
54
+ "execution_count": 4,
55
+ "metadata": {},
56
+ "outputs": [
57
+ {
58
+ "name": "stdout",
59
+ "output_type": "stream",
60
+ "text": [
61
+ "Hello from math module\n"
62
+ ]
63
+ }
64
+ ],
65
+ "source": [
66
+ "hello()"
67
+ ]
68
+ }
69
+ ],
70
+ "metadata": {
71
+ "kernelspec": {
72
+ "display_name": ".venv",
73
+ "language": "python",
74
+ "name": "python3"
75
+ },
76
+ "language_info": {
77
+ "codemirror_mode": {
78
+ "name": "ipython",
79
+ "version": 3
80
+ },
81
+ "file_extension": ".py",
82
+ "mimetype": "text/x-python",
83
+ "name": "python",
84
+ "nbconvert_exporter": "python",
85
+ "pygments_lexer": "ipython3",
86
+ "version": "3.11.7"
87
+ }
88
+ },
89
+ "nbformat": 4,
90
+ "nbformat_minor": 2
91
+ }
pyproject.toml ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "template-python"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "fastapi[standard]>=0.115.8",
9
+ "ipykernel>=6.29.5",
10
+ "numpy>=2.2.3",
11
+ "pytest>=8.3.4",
12
+ ]
13
+
14
+ [tool.black]
15
+ line-length = 88
16
+ target-version = ["py311"]
17
+ include = '\.pyi?$'
18
+
19
+ [tool.ruff]
20
+ line-length = 88
21
+ target-version = "py311"
22
+ select = [
23
+ "E", # pycodestyle errors
24
+ "W", # pycodestyle warnings
25
+ "F", # pyflakes
26
+ "I", # isort
27
+ "C", # flake8-comprehensions
28
+ "B", # flake8-bugbear
29
+ ]
30
+ ignore = []
31
+
32
+ [tool.ruff.per-file-ignores]
33
+ "__init__.py" = ["F401"]
34
+
35
+ [tool.ruff.isort]
36
+ known-first-party = ["src"]
37
+
38
+ [tool.ruff.mccabe]
39
+ max-complexity = 10
setup.py ADDED
@@ -0,0 +1 @@
 
 
1
+ print("Setup.py")
src/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ """
2
+ Main application package.
3
+ This is the root package of the application.
4
+ """
src/common/__init__.py ADDED
File without changes
src/common/config.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Common configuration utilities."""
2
+
3
+ import json
4
+ import os
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any, Optional
8
+
9
+
10
+ @dataclass
11
+ class AppConfig:
12
+ """Application configuration."""
13
+
14
+ app_name: str
15
+ environment: str
16
+ debug: bool
17
+ log_level: str
18
+
19
+ @classmethod
20
+ def from_env(cls) -> "AppConfig":
21
+ """Create configuration from environment variables.
22
+
23
+ Returns:
24
+ AppConfig: Application configuration instance
25
+ """
26
+ return cls(
27
+ app_name=os.getenv("APP_NAME", "template-python"),
28
+ environment=os.getenv("ENV", "development"),
29
+ debug=os.getenv("DEBUG", "false").lower() == "true",
30
+ log_level=os.getenv("LOG_LEVEL", "INFO"),
31
+ )
32
+
33
+
34
+ def load_json_config(path: Path) -> dict[str, Any]:
35
+ """Load configuration from JSON file.
36
+
37
+ Args:
38
+ path: Path to JSON configuration file
39
+
40
+ Returns:
41
+ dict[str, Any]: Configuration dictionary
42
+
43
+ Raises:
44
+ FileNotFoundError: If configuration file doesn't exist
45
+ json.JSONDecodeError: If configuration file is invalid JSON
46
+ """
47
+ if not path.exists():
48
+ raise FileNotFoundError(f"Configuration file not found: {path}")
49
+
50
+ with path.open() as f:
51
+ return json.load(f)
52
+
53
+
54
+ class ConfigurationError(Exception):
55
+ """Base class for configuration errors."""
56
+
57
+ pass
58
+
59
+
60
+ def get_config_path(config_name: str) -> Path:
61
+ """Get configuration file path.
62
+
63
+ Args:
64
+ config_name: Name of the configuration file
65
+
66
+ Returns:
67
+ Path: Path to configuration file
68
+ """
69
+ # Check common configuration locations
70
+ locations = [
71
+ Path("config"), # Project config directory
72
+ Path("~/.config/template-python"), # User config directory
73
+ Path("/etc/template-python"), # System config directory
74
+ ]
75
+
76
+ for location in locations:
77
+ path = location.expanduser() / f"{config_name}.json"
78
+ if path.exists():
79
+ return path
80
+
81
+ return locations[0].expanduser() / f"{config_name}.json"
src/modules/__init__.py ADDED
File without changes
src/modules/api/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """API module initialization."""
2
+ print("API module loaded.")
3
+ import src.modules.api.index
src/modules/api/app.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """FastAPI application factory."""
2
+
3
+ from fastapi import FastAPI
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+
6
+ from .routes import v1_router
7
+
8
+
9
+ def create_app() -> FastAPI:
10
+ """Create and configure the FastAPI application."""
11
+ app = FastAPI()
12
+
13
+ # Add CORS middleware
14
+ app.add_middleware(
15
+ CORSMiddleware,
16
+ allow_origins=["*"],
17
+ allow_credentials=True,
18
+ allow_methods=["*"],
19
+ allow_headers=["*"],
20
+ )
21
+
22
+ # Include routers
23
+ app.include_router(v1_router)
24
+
25
+ @app.on_event("startup")
26
+ async def startup_event():
27
+ """Run startup events."""
28
+ pass
29
+
30
+ @app.on_event("shutdown")
31
+ async def shutdown_event():
32
+ """Run shutdown events."""
33
+ pass
34
+
35
+ return app
36
+
37
+
38
+ # Create default app instance
39
+ app = create_app()
src/modules/api/index.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uvicorn
2
+ from .app import app as api_app
3
+ import logging
4
+
5
+
6
+ def main():
7
+ """Main application entry point."""
8
+ try:
9
+ print("Start api...")
10
+ uvicorn.run(
11
+ api_app,
12
+ host="0.0.0.0",
13
+ port=8080,
14
+ reload=False,
15
+ )
16
+ except Exception as e:
17
+ logging.error(f"Application failed to start: {e}", exc_info=True)
18
+ raise
19
+
20
+
21
+ main()
src/modules/api/routes/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """API routes package."""
2
+
3
+ from .v1 import router as v1_router
4
+
5
+ __all__ = ["v1_router"]
src/modules/api/routes/v1.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """API v1 route handlers."""
2
+
3
+ from fastapi import APIRouter
4
+ from typing import Dict, List
5
+
6
+
7
+ # Create v1 router
8
+ router = APIRouter(prefix='/v1', tags=['v1'])
9
+
10
+ @router.get("/hello")
11
+ async def hello_world() -> Dict[str, str]:
12
+ """Hello world endpoint."""
13
+ return {"message": "Hello, World!"}
14
+
15
+ @router.get("/health")
16
+ async def health_check() -> Dict[str, str]:
17
+ """Health check endpoint."""
18
+ return {"status": "healthy"}
19
+
20
+ @router.get("/metrics")
21
+ async def metrics() -> Dict[str, int]:
22
+ """Application metrics endpoint."""
23
+ return {
24
+ "total_routes": len(router.routes),
25
+ "api_version": 1
26
+ }
src/shared/__init__.py ADDED
File without changes
src/shared/utils/__init__.py ADDED
File without changes
src/shared/utils/validation.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared validation utilities."""
2
+
3
+ import re
4
+ from typing import Any, Optional, Pattern
5
+
6
+ # Common validation patterns
7
+ EMAIL_PATTERN: Pattern = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
8
+ UUID_PATTERN: Pattern = re.compile(
9
+ r"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
10
+ )
11
+
12
+
13
+ def validate_email(email: str) -> bool:
14
+ """Validate email format.
15
+
16
+ Args:
17
+ email: Email address to validate
18
+
19
+ Returns:
20
+ bool: True if email format is valid, False otherwise
21
+ """
22
+ return bool(EMAIL_PATTERN.match(email))
23
+
24
+
25
+ def validate_uuid(uuid: str) -> bool:
26
+ """Validate UUID format.
27
+
28
+ Args:
29
+ uuid: UUID string to validate
30
+
31
+ Returns:
32
+ bool: True if UUID format is valid, False otherwise
33
+ """
34
+ return bool(UUID_PATTERN.match(uuid.lower()))
35
+
36
+
37
+ def validate_required_fields(
38
+ data: dict[str, Any], required_fields: list[str]
39
+ ) -> tuple[bool, Optional[str]]:
40
+ """Validate required fields in a dictionary.
41
+
42
+ Args:
43
+ data: Dictionary containing data to validate
44
+ required_fields: List of required field names
45
+
46
+ Returns:
47
+ tuple[bool, Optional[str]]: (is_valid, error_message)
48
+ """
49
+ missing_fields = [field for field in required_fields if field not in data]
50
+
51
+ if missing_fields:
52
+ return False, f"Missing required fields: {', '.join(missing_fields)}"
53
+
54
+ return True, None
src/utils/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ """
2
+ Main application package.
3
+ This is the root package of the application.
4
+ """
src/utils/math.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Mathematical utility functions."""
2
+
3
+ from typing import List, Union
4
+
5
+ Number = Union[int, float]
6
+
7
+
8
+ def calculate_mean(numbers: List[Number]) -> float:
9
+ """Calculate the arithmetic mean of a list of numbers.
10
+
11
+ Args:
12
+ numbers: A list of integers or floating point numbers
13
+
14
+ Returns:
15
+ float: The arithmetic mean of the input numbers
16
+
17
+ Raises:
18
+ ValueError: If the input list is empty
19
+ TypeError: If any element is not a number
20
+ """
21
+ if not numbers:
22
+ raise ValueError("Cannot calculate mean of empty list")
23
+
24
+ if not all(isinstance(x, (int, float)) for x in numbers):
25
+ raise TypeError("All elements must be numbers")
26
+
27
+ return sum(numbers) / len(numbers)
28
+
29
+
30
+ def calculate_median(numbers: List[Number]) -> float:
31
+ """Calculate the median value from a list of numbers.
32
+
33
+ Args:
34
+ numbers: A list of integers or floating point numbers
35
+
36
+ Returns:
37
+ float: The median value of the input numbers
38
+
39
+ Raises:
40
+ ValueError: If the input list is empty
41
+ TypeError: If any element is not a number
42
+ """
43
+ if not numbers:
44
+ raise ValueError("Cannot calculate median of empty list")
45
+
46
+ if not all(isinstance(x, (int, float)) for x in numbers):
47
+ raise TypeError("All elements must be numbers")
48
+
49
+ sorted_numbers = sorted(numbers)
50
+ length = len(sorted_numbers)
51
+
52
+ if length % 2 == 0:
53
+ mid = length // 2
54
+ return (sorted_numbers[mid - 1] + sorted_numbers[mid]) / 2
55
+ else:
56
+ return sorted_numbers[length // 2]
tests/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ """
2
+ Test package.
3
+ Contains all test cases for the application.
4
+ """
tests/test_math.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for mathematical utility functions."""
2
+
3
+ import pytest
4
+ from src.utils.math import calculate_mean, calculate_median
5
+
6
+ def test_calculate_mean():
7
+ """Test the calculate_mean function with various inputs."""
8
+ # Test with integers
9
+ assert calculate_mean([1, 2, 3, 4, 5]) == 3.0
10
+
11
+ # Test with floats
12
+ assert calculate_mean([1.5, 2.5, 3.5]) == 2.5
13
+
14
+ # Test with mixed numbers
15
+ assert calculate_mean([1, 2.5, 3]) == 2.1666666666666665
16
+
17
+ def test_calculate_mean_errors():
18
+ """Test error handling in calculate_mean function."""
19
+ # Test empty list
20
+ with pytest.raises(ValueError) as exc:
21
+ calculate_mean([])
22
+ assert str(exc.value) == "Cannot calculate mean of empty list"
23
+
24
+ # Test invalid input types
25
+ with pytest.raises(TypeError) as exc:
26
+ calculate_mean([1, "2", 3])
27
+ assert str(exc.value) == "All elements must be numbers"
28
+
29
+ def test_calculate_median():
30
+ """Test the calculate_median function with various inputs."""
31
+ # Test odd number of integers
32
+ assert calculate_median([1, 2, 3, 4, 5]) == 3.0
33
+
34
+ # Test even number of integers
35
+ assert calculate_median([1, 2, 3, 4]) == 2.5
36
+
37
+ # Test unsorted list
38
+ assert calculate_median([5, 2, 1, 4, 3]) == 3.0
39
+
40
+ # Test with floats
41
+ assert calculate_median([1.5, 2.5, 3.5]) == 2.5
42
+
43
+ # Test with mixed numbers
44
+ assert calculate_median([1, 2.5, 3]) == 2.5
45
+
46
+ def test_calculate_median_errors():
47
+ """Test error handling in calculate_median function."""
48
+ # Test empty list
49
+ with pytest.raises(ValueError) as exc:
50
+ calculate_median([])
51
+ assert str(exc.value) == "Cannot calculate median of empty list"
52
+
53
+ # Test invalid input types
54
+ with pytest.raises(TypeError) as exc:
55
+ calculate_median([1, "2", 3])
56
+ assert str(exc.value) == "All elements must be numbers"
uv.lock ADDED
The diff for this file is too large to render. See raw diff