# ©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 "". 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 # ":" — 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//. [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"