Cybersecurity-Projects/PROJECTS/foundations/password-manager/pyproject.toml

295 lines
11 KiB
TOML

# ©AngelaMos | 2026
# pyproject.toml
#
# This file is the "instruction manual" for our Python project. Every
# modern Python project has one. It tells tools like uv, pip, pytest,
# ruff, mypy, and pylint:
#
# - What our project is called
# - Which third-party libraries we depend on
# - How to build and install it
# - How to lint, type-check, and test it
#
# The file format is called TOML — a simple, human-readable config
# language. Square brackets like [project] start a "table" (a section).
# Lines like name = "..." are key-value pairs inside that table.
#
# Read this top-to-bottom. Each section has a short comment explaining
# what it does and why it is here.
# =============================================================================
# [project] — the metadata that describes WHAT this project is
# =============================================================================
# This section is standardized across the whole Python ecosystem (PEP 621).
# Anyone who runs `pip install` or `uv sync` reads from here.
[project]
# The name people will install this package under.
# Must be unique on PyPI if we ever publish there.
name = "password-vault"
# Semantic version: MAJOR.MINOR.PATCH.
# 1.0.0 means "first stable release."
# When we add features → bump MINOR (1.1.0). When we break things → MAJOR.
version = "1.0.0"
# One-line description that shows up in `pip show` and on PyPI.
description = "Beginner-friendly encrypted password manager (Argon2id + AES-256-GCM)"
# The minimum Python version this project supports.
# >=3.13 means "Python 3.13 or anything newer."
# We need 3.13 because we use modern type-hint syntax (X | None).
requires-python = ">=3.13"
# Who wrote it. Shows up in package metadata.
authors = [
{name = "CarterPerez-dev", email = "support@certgames.com"}
]
# The README file is shown on PyPI and in `pip show`.
readme = "README.md"
# AGPL-3.0 means: anyone can use it, but if they modify and run it as a
# service, they must publish their changes. It protects the project
# from being privatized.
license = {text = "AGPL-3.0-or-later"}
# -----------------------------------------------------------------------------
# dependencies — third-party libraries we MUST have at runtime
# -----------------------------------------------------------------------------
# When a user runs `pip install password-vault`, these get installed too.
# The string format is "<name><operator><version>". We use ranges so we
# get bug fixes (>=) but never an incompatible major version (<).
dependencies = [
# argon2-cffi: implements Argon2id, the modern password-hashing
# algorithm we use to turn the master password into an encryption key.
"argon2-cffi>=25.1.0",
# cryptography: provides AES-256-GCM, the actual encryption that
# scrambles vault contents. Maintained by the Python Cryptographic
# Authority — the gold-standard library, audited and trusted.
"cryptography>=48.0.0",
# typer: builds nice CLIs from regular Python functions.
# Lets us write `add`, `get`, `list` commands with auto-generated help.
"typer>=0.25.1",
# rich: pretty terminal output — colors, tables, panels, prompts.
# Makes the CLI pleasant to use without writing a bunch of formatting.
"rich>=15.0.0",
]
# -----------------------------------------------------------------------------
# optional-dependencies — extra libraries that are NOT needed at runtime
# -----------------------------------------------------------------------------
# These only matter to developers (testing, linting, formatting). End users
# don't install them. Activated with: `uv sync --extra dev` or `--all-extras`.
[project.optional-dependencies]
dev = [
# pytest — runs our test suite (the test_*.py files in tests/).
"pytest>=9.0.3",
# pytest-cov — measures how much of our code the tests actually
# exercise. "Coverage" — high coverage means well-tested code.
"pytest-cov>=7.1.0",
# ruff — extremely fast linter and formatter. Catches bugs, style
# issues, dead imports. Modern replacement for flake8/black/isort.
"ruff>=0.15.12",
# mypy — static type checker. Reads our type hints and tells us if
# we passed a string where an int was expected, BEFORE running the code.
"mypy>=2.1.0",
# pylint — second linter that catches deeper logic issues ruff misses.
"pylint>=4.0.5",
# yapf — code formatter (Google's). Keeps every file looking identical.
"yapf>=0.43.0,<1.0.0",
]
# -----------------------------------------------------------------------------
# project.urls — links shown on PyPI / package pages
# -----------------------------------------------------------------------------
[project.urls]
Homepage = "https://github.com/CarterPerez-dev/Cybersecurity-Projects"
Repository = "https://github.com/CarterPerez-dev/Cybersecurity-Projects/tree/main/PROJECTS/foundations/password-manager"
# -----------------------------------------------------------------------------
# project.scripts — command-line entry points
# -----------------------------------------------------------------------------
# These create real shell commands when the package is installed. After
# `uv sync`, the user can type `pv add github` instead of
# `python -m password_manager add github`. The right-hand side is
# "<module>:<function>" — the function gets called when the command runs.
[project.scripts]
pv = "password_manager.main:app"
password-vault = "password_manager.main:app"
# =============================================================================
# [build-system] — how to BUILD this project into an installable package
# =============================================================================
# Required by PEP 517. Tools like uv read this to know which builder
# to use. We chose hatchling — modern, fast, zero-config for our case.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
# Tell hatchling where the actual Python code lives. Without this, it
# guesses — and guesses wrong when the layout is src/<package>/.
[tool.hatch.build.targets.wheel]
packages = ["src/password_manager"]
# =============================================================================
# [tool.ruff] — linter / formatter configuration
# =============================================================================
# Ruff is the FAST one. It runs in milliseconds and catches 90% of issues.
[tool.ruff]
# Target Python version — ruff adjusts which warnings apply.
target-version = "py313"
# Wrap lines at 88 characters (PEP 8 says 79, but 88 is the modern norm).
line-length = 88
# Tell ruff "the source code lives in src/" so imports resolve correctly.
src = ["src"]
# Don't lint these directories.
exclude = [
".git",
".venv",
"__pycache__",
"venv",
"build",
"dist",
]
# -----------------------------------------------------------------------------
# Which lint rules to enable. Each "code" is a category of check.
# -----------------------------------------------------------------------------
[tool.ruff.lint]
select = [
"E", # pycodestyle errors — basic PEP 8 spacing/indent rules
"F", # Pyflakes — unused imports, undefined names, real bugs
"W", # pycodestyle warnings — softer style issues
"B", # Bugbear — sneaky bugs (mutable default args, etc.)
"C4", # Comprehensions — encourages cleaner list/dict comprehensions
"UP", # Pyupgrade — flags old syntax we should modernize
"SIM", # Simplify — suggests cleaner equivalents
]
# Some rules we deliberately ignore.
ignore = [
# Line length is handled by yapf, not ruff. Avoids double-flagging.
"E501",
]
# =============================================================================
# [tool.mypy] — static type checker configuration
# =============================================================================
# Mypy reads our type hints (the `: int`, `-> str` parts) and verifies them
# without running the code. Catches whole categories of bugs at edit time.
[tool.mypy]
python_version = "3.13"
# Treat anything returning Any as a warning — Any is a type-system escape
# hatch and we want to know when we're using it.
warn_return_any = true
# Warn if our config has unused settings (typos, etc.).
warn_unused_configs = true
# Require type annotations on every function. STRICT mode — appropriate
# for a security-critical project where bugs = leaked passwords.
disallow_untyped_defs = true
disallow_incomplete_defs = true
# Don't auto-add Optional just because a default is None. Be explicit.
no_implicit_optional = true
# Warn if we cast a value to a type it already has (dead code).
warn_redundant_casts = true
# Warn if a function might fall off the end without returning.
warn_no_return = true
# Pretty error output — helps when reading mypy output.
show_error_codes = true
show_column_numbers = true
pretty = true
exclude = [
".venv",
"venv",
"tests", # tests use fixtures and don't need strict typing
]
# -----------------------------------------------------------------------------
# Per-module overrides — silence missing stubs for argon2.
# argon2-cffi doesn't ship type hints, so mypy can't check it. That's fine.
# -----------------------------------------------------------------------------
[[tool.mypy.overrides]]
module = ["argon2.*"]
ignore_missing_imports = true
# =============================================================================
# [tool.pylint] — second-opinion linter
# =============================================================================
# Slower than ruff but catches different things — class design issues,
# dead variables, complex code patterns.
[tool.pylint.main]
py-version = "3.13"
jobs = 4 # run in parallel across 4 cores
persistent = true # cache results between runs
ignore = [
"venv",
".venv",
"__pycache__",
"build",
"dist",
".git",
"tests",
]
# Specific pylint warnings we deliberately turn off.
[tool.pylint.messages_control]
disable = [
"R0903", # too-few-public-methods — small data classes are fine
"C0103", # invalid-name — we use short names like `kdf` on purpose
"C0325", # superfluous-parens — fights with yapf formatting
]
# Override pylint's default complexity limits where they're too strict.
[tool.pylint.design]
max-args = 8 # default is 5; we sometimes need a few more
max-attributes = 10 # default is 7
# Foundation-tier modules carry heavy teaching comments and multi-
# paragraph docstrings, which push a single self-contained module
# above the default 1000-line ceiling. The right answer is not to
# split the module artificially — keeping vault.py as one place to
# read end-to-end is the pedagogical point — so we lift the cap.
[tool.pylint.format]
max-module-lines = 1500
# =============================================================================
# [tool.pytest.ini_options] — test runner configuration
# =============================================================================
[tool.pytest.ini_options]
# Where the test files live.
testpaths = ["tests"]
# Files that match this pattern are treated as test modules.
python_files = ["test_*.py"]
# Default flags every `pytest` invocation gets:
# -v → verbose, show each test name
# --tb=short → short tracebacks on failure (less wall of text)
addopts = "-v --tb=short"