Last active
January 6, 2026 22:01
-
-
Save IceWreck/a44c3294edce034485a5e1454aad86f3 to your computer and use it in GitHub Desktop.
Python New App Boilerplat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Python Boilerplate | |
| ### Tree | |
| This is how the repo should look like. | |
| ``` | |
| . | |
| ├── AGENTS.md | |
| ├── config | |
| │ └── example.yml | |
| ├── Dockerfile | |
| ├── Makefile | |
| ├── pyproject.toml | |
| ├── README.md | |
| ├── requirements.txt | |
| ├── src | |
| │ └── appname | |
| │ ├── config.py | |
| │ ├── __init__.py | |
| │ ├── logger.py | |
| │ ├── main.py | |
| │ ├── py.typed | |
| └── uv.lock | |
| ``` | |
| ### Pyproject | |
| In the empty git repo, init with `uv init --app --package ./`. | |
| After that add in the following to pyproject. | |
| ```toml | |
| [tool.ruff] | |
| line-length = 120 | |
| indent-width = 4 | |
| [tool.ruff.lint] | |
| extend-select = [ | |
| "F", | |
| "W", | |
| "E", | |
| "I", | |
| "UP", | |
| "C4", | |
| "FA", | |
| "ISC", | |
| "ICN", | |
| "RET", | |
| "SIM", | |
| "TID", | |
| "TC", | |
| "PTH", | |
| "TD", | |
| "NPY", | |
| ] | |
| [tool.ruff.format] | |
| indent-style = "space" | |
| quote-style = "double" | |
| [tool.pyright] | |
| typeCheckingMode = "strict" | |
| [dependency-groups] | |
| dev = [ | |
| "basedpyright", | |
| "ruff", | |
| ] | |
| ``` | |
| ### Agents MD | |
| `AGENTS.md` file also symlink to `CLAUDE.md`. | |
| ```markdown | |
| # Agents Knowledge | |
| ## Overview | |
| ## Code Conventions | |
| - Write modular, clean and fully typed modern python code. | |
| - Use new style Python types (e.g., `list`, `dict`, `tuple`, `set`, `int | None` instead of `List`, `Dict`, `Tuple`, `Set`, `Optional`). | |
| - Use lower case for logs: logger.info("starting xyz"). | |
| - Prefer logger instead of print statements. | |
| - Log lines and exceptions should always start with a lowercase char. | |
| - Log lines should not end with a period. | |
| - Try not to use `Any` for typing. | |
| - All code should be typed using modern python. | |
| - Use early returns to reduce indentation. | |
| - Extract complex logic into functions. | |
| - Flatten loops with list comprehensions. | |
| - Leverage data structures instead of deeply nested conditions. | |
| - Write self-explanatory code – Prefer clear variable and function names over comments. | |
| - Explain "why," not "what" – Comments should clarify intent, not restate code. | |
| - Avoid redundant comments – Don't comment obvious things. | |
| - Use comments for complex logic – Explain non-trivial decisions or workarounds. | |
| - Write docstrings for functions/classes – Document purpose, inputs, and outputs. | |
| - Keep comments updated – Outdated comments are worse than none. | |
| - Use inline comments sparingly – Only when necessary for clarity. | |
| - Let Python handle exceptions by default - prefer crashing over complex error handling | |
| - Only add try-except blocks when: | |
| - Explicitly required to keep the application running. | |
| - Requested by the user/product requirements. | |
| - Handling a specific, recoverable error case. | |
| - Instead of defensive programming with try-except blocks: | |
| - Use assertions to validate critical assumptions. | |
| - Let functions fail fast with invalid inputs - Don't catch broad exceptions (except Exception). | |
| - Trust Python's built-in error handling. | |
| - Let stack traces expose issues during development. | |
| ## Development | |
| ### Typechecking | |
| ```bash | |
| make typecheck | |
| ``` | |
| ### Linting | |
| ```bash | |
| make lint | |
| ``` | |
| ### Formatting | |
| ```bash | |
| make format | |
| ``` | |
| ### Dependency Management | |
| We use uv to manage Python dependencies. | |
| ```bash | |
| uv add <your-package-name> | |
| ``` | |
| This installs the package and adds it to `pyproject.toml`. | |
| ``` | |
| ### Makefile | |
| ``` | |
| #!make | |
| -include .env | |
| export $(shell sed 's/=.*//' .env) | |
| SHELL := /bin/bash | |
| IMAGE_NAME = example.com/packages/appname:latest | |
| .PHONY: * | |
| develop: | |
| uv venv ./.venv | |
| uv sync --all-extras | |
| touch .env | |
| format: | |
| uv run ruff format ./src | |
| lint: | |
| uv run ruff check --fix src/ | |
| uv run ruff check | |
| typecheck: | |
| uv run basedpyright ./src | |
| build: | |
| uv export --format requirements.txt --no-emit-project --no-hashes -o requirements.txt | |
| podman build -t $(IMAGE_NAME) . | |
| ``` | |
| ### Logger | |
| ```python | |
| import logging | |
| import os | |
| _logging_configured = False | |
| def setup_logging(force: bool = False) -> None: | |
| global _logging_configured | |
| if not force and _logging_configured: | |
| return | |
| level_name = os.getenv("LOG_LEVEL", "INFO").upper() | |
| level = getattr(logging, level_name, logging.INFO) | |
| # force configure root logger - remove any existing handlers and add ours | |
| root_logger = logging.getLogger() | |
| root_logger.handlers.clear() # remove any existing handlers | |
| root_logger.setLevel(level) | |
| # add our handler with priority | |
| handler = logging.StreamHandler() | |
| handler.setLevel(level) | |
| formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") | |
| handler.setFormatter(formatter) | |
| root_logger.addHandler(handler) | |
| _logging_configured = True | |
| def get_logger(name: str) -> logging.Logger: | |
| setup_logging() | |
| return logging.getLogger(name) | |
| ``` | |
| ### Config | |
| Use pydantic settings for config management. | |
| ```python | |
| class Config(BaseSettings): | |
| model_config = SettingsConfigDict(env_prefix="APPNAME_") | |
| bar: BarConfig = Field(default=...) | |
| foo: FooServerConfig = Field(default_factory=FooServerConfig) | |
| @classmethod | |
| def from_yaml(cls, file_path: Path) -> "Config": | |
| logger.info(f"loading config file: {file_path}") | |
| if not file_path.exists(): | |
| logger.error(f"config file not found: {file_path}") | |
| raise FileNotFoundError(f"config file not found: {file_path}") | |
| yaml_source = YamlConfigSettingsSource(cls, yaml_file=str(file_path)) | |
| return cls(**yaml_source()) | |
| def to_json(self) -> str: | |
| return json.dumps(self.model_dump(mode="json"), indent=4) | |
| ``` | |
| ### Main | |
| Just a hello world using the logger and typer for CLI creation. | |
| It should load config file and then log it. | |
| ### Gitignore | |
| ``` | |
| # Python | |
| __pycache__/ | |
| *.py[cod] | |
| *.egg-info/ | |
| .venv/ | |
| .mypy_cache/ | |
| # Environment files | |
| .env | |
| .env.* | |
| # Build | |
| build/ | |
| dist/ | |
| # IDE | |
| .vscode/ | |
| .idea/ | |
| # OS | |
| .DS_Store | |
| # Other | |
| /drafts | |
| ``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment