[project] name = "imagecraft" description = "Create Ubuntu bootable images." dynamic = ["version", "readme"] authors = [{ name = "Canonical Ltd.", email = "snapcraft@lists.snapcraft.io" }] license = { file = "LICENSE" } dependencies = [ "craft-parts~=2.6", "craft-cli~=2.15", "craft-platforms~=0.4", "craft-application~=4.6", "craft-providers~=2.0", "pydantic~=2.8", ] classifiers = [ "Development Status :: 1 - Planning", "License :: OSI Approved :: GNU General Public License (GPL)", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.12", ] requires-python = ">=3.11" [project.urls] Homepage = "https://github.com/canonical/imagecraft" Issues = "https://github.com/canonical/imagecraft/issues" Source = "https://github.com/canonical/imagecraft.git" [project.scripts] imagecraft = "imagecraft.cli:run" [project.optional-dependencies] types = [ "mypy[reports]~=1.15.0", "types-Pygments", "types-colorama", "types-setuptools", ] apt-noble = [ # 2.7 for Noble "python-apt~=2.7.0;sys_platform=='linux'", ] apt-oracular = [ # 2.9 for Oracular+ "python-apt>=2.9.0;sys_platform=='linux'", ] apt-plucky = [ # 2.9 for Oracular+ "python-apt>=2.9.0;sys_platform=='linux'", ] docs = [ "canonical-sphinx[full]~=0.3.0", "sphinx-autobuild~=2024.2", "sphinx-pydantic==0.1.1", "sphinx-toolbox~=3.5", "sphinx-lint==1.0.0", "sphinxcontrib-details-directive", "matplotlib", ] [[tool.uv.index]] name = "python-apt-wheels" url = "https://people.canonical.com/~lengau/python-apt-ubuntu-wheels/" [tool.uv] constraint-dependencies = [ # Basic constraints to allow --resolution=lowest "build>=0.7.0", "cffi>=1.15", "iniconfig>=1.1.0", "httplib2>=0.20.0", "libnacl>=2.0", "lxml>=5.0", "markdown>=3.0", "markupsafe>=2.0", "oauthlib>=3.0.0", "protobuf>=5.0", "pynacl>=1.5", "pyparsing>=3.0.0", "pyproject-hooks>=1.0.0", "pytz>=2020", "pyyaml>=5.0", "regex>=2021.11.10", "setuptools>=50", "sphinx-basic-ng>=1.0.0b1", "tornado>=4.0", "urllib3>=2.0", "webencodings>=0.4.0", "wheel>=0.38", ] dev-dependencies = [ "build", "coverage[toml]~=7.4", "pytest~=8.0", "pytest-cov~=6.0", "pytest-check>=2.4", "pytest-mock~=3.12", "mypy[reports]~=1.15.0", "pyright==1.1.393", "types-Pygments", "types-colorama", "types-setuptools", ] conflicts = [ [ { extra = "apt-noble" }, { extra = "apt-oracular" }, { extra = "apt-plucky" }, ], ] [build-system] requires = ["setuptools>=69.0", "setuptools_scm[toml]>=7.1"] build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] readme = { file = "README.rst" } [tool.setuptools_scm] write_to = "imagecraft/_version.py" # the version comes from the latest annotated git tag formatted as 'X.Y.Z' # version scheme: # - X.Y.Z.post<commits since tag>+g<hash>.d<%Y%m%d> # parts of scheme: # - X.Y.Z - most recent git tag # - post<commits since tag>+g<hash> - present when current commit is not tagged # - .d<%Y%m%d> - present when working dir is dirty # version scheme when no tags exist: # - 0.0.post<total commits>+g<hash> version_scheme = "post-release" # deviations from the default 'git describe' command: # - only match annotated tags # - only match tags formatted as 'X.Y.Z' git_describe_command = "git describe --dirty --long --match '[0-9]*.[0-9]*.[0-9]*' --exclude '*[^0-9.]*'" [tool.setuptools.packages.find] include = ["*craft*"] namespaces = false [tool.black] target-version = ["py311"] [tool.codespell] ignore-words-list = "buildd,crate,keyserver,comandos,ro,dedent,dedented" skip = ".git,build,.*_cache,__pycache__,*.tar,*.snap,*.png,./node_modules,./docs/_build,.direnv,.venv,venv,.vscode" quiet-level = 3 check-filenames = true [tool.isort] multi_line_output = 3 include_trailing_comma = true force_grid_wrap = 0 use_parentheses = true ensure_newline_before_comments = true line_length = 88 [tool.pytest.ini_options] minversion = "7.0" testpaths = "tests" xfail_strict = true markers = ["slow: slow tests"] [tool.coverage.run] branch = true omit = ["tests/**"] [tool.coverage.report] skip_empty = true exclude_also = ["if (typing\\.)?TYPE_CHECKING:"] [tool.pyright] strict = ["imagecraft"] pythonVersion = "3.12" pythonPlatform = "Linux" exclude = [ "**/.*", "**/__pycache__", # pyright might not like the annotations generated by setuptools_scm "**/_version.py", "**/tests/lib/external/**", ] [tool.mypy] python_version = "3.12" exclude = ["build", "results"] warn_unused_configs = true warn_redundant_casts = true strict_equality = true extra_checks = true warn_return_any = true disallow_subclassing_any = true disallow_untyped_decorators = true disallow_any_generics = true [[tool.mypy.overrides]] module = ["imagecraft.*"] disallow_untyped_defs = true no_implicit_optional = true [[tool.mypy.overrides]] module = ["tests.*"] strict = false [tool.ruff] line-length = 88 target-version = "py310" src = ["imagecraft", "tests"] extend-exclude = ["docs", "tests/lib/external", "imagecraft/_version.py"] [tool.ruff.format] docstring-code-format = true line-ending = "lf" quote-style = "double" [tool.ruff.lint] # Follow ST063 - Maintaining and updating linting specifications for updating these. # Handy link: https://docs.astral.sh/ruff/rules/ select = [ # Base linting rule selections. # See the internal document for discussion: # https://docs.google.com/document/d/1i1n8pDmFmWi4wTDpk-JfnWCVUThPJiggyPi2DYwBBu4/edit # All sections here are stable in ruff and shouldn't randomly introduce # failures with ruff updates. "F", # The rules built into Flake8 "E", "W", # pycodestyle errors and warnings "I", # isort checking "N", # PEP8 naming "D", # Implement pydocstyle checking as well. "UP", # Pyupgrade - note that some of are excluded below due to Python versions "YTT", # flake8-2020: Misuse of `sys.version` and `sys.version_info` "ANN", # Type annotations. "ASYNC", # Catching blocking calls in async functions # flake8-bandit: security testing. https://docs.astral.sh/ruff/rules/#flake8-bandit-s # https://bandit.readthedocs.io/en/latest/plugins/index.html#complete-test-plugin-listing "S101", "S102", # assert or exec "S103", "S108", # File permissions and tempfiles - use #noqa to silence when appropriate. "S104", # Network binds "S105", "S106", "S107", # Hardcoded passwords "S110", # try-except-pass (use contextlib.suppress instead) "S113", # Requests calls without timeouts "S3", # Serialising, deserialising, hashing, crypto, etc. "S5", # Unsafe cryptography or YAML loading. "S602", # Subprocess call with shell=true "S701", # jinja2 templates without autoescape "BLE", # Do not catch blind exceptions "FBT", # Disallow boolean positional arguments (make them keyword-only) "B0", # Common mistakes and typos. "A", # Shadowing built-ins. "COM", # Trailing commas "C4", # Encourage comprehensions, which tend to be faster than alternatives. "T10", # Don't call the debugger in production code "ISC", # Implicit string concatenation that can cause subtle issues "ICN", # Only use common conventions for import aliases. "INP", # Implicit namespace packages # flake8-pie: miscellaneous linters (enabled individually because they're not really related) "PIE790", # Unnecessary pass statement "PIE794", # Multiple definitions of class field "PIE796", # Duplicate value in an enum (reasonable to noqa for backwards compatibility) "PIE804", # Don't use a dict with unnecessary kwargs "PIE807", # prefer `list` over `lambda: []` "PIE810", # Use a tuple rather than multiple calls. E.g. `mystr.startswith(("Hi", "Hello"))` "PYI", # Linting for type stubs. "PT", # Pytest "Q", # Consistent quotations "RSE", # Errors on pytest raises. "RET", # Simpler logic after return, raise, continue or break "SLF", # Prevent accessing private class members. "SIM", # Code simplification "TID", # Tidy imports # The team have chosen to only use type-checking blocks when necessary to prevent circular imports. # As such, the only enabled type-checking checks are those that warn of an import that needs to be # removed from a type-checking block. "TC004", # Remove imports from type-checking guard blocks if used at runtime "TC005", # Delete empty type-checking blocks "ARG", # Unused arguments "PTH", # Migrate to pathlib "FIX", # All TODOs, FIXMEs, etc. should be turned into issues instead. "ERA", # Don't check in commented out code "PGH", # Pygrep hooks "PL", # Pylint "TRY", # Cleaner try/except, "FLY", # Detect things that would be better as f-strings. "PERF", # Catch things that can slow down the application like unnecessary casts to list. "RUF001", "RUF002", "RUF003", # Ambiguous unicode characters "RUF005", # Encourages unpacking rather than concatenation "RUF008", # Do not use mutable default values for dataclass attributes "B035", # Don't use static keys in dict comprehensions. "RUF013", # Prohibit implicit Optionals (PEP 484) "RUF100", # #noqa directive that doesn't flag anything "RUF200", # If ruff fails to parse pyproject.toml... ] ignore = [ "ANN10", # Type annotations for `self` and `cls` #"E203", # Whitespace before ":" -- Commented because ruff doesn't currently check E203 "E501", # Line too long (reason: black will automatically fix this for us) "D105", # Missing docstring in magic method (reason: magic methods already have definitions) "D107", # Missing docstring in __init__ (reason: documented in class docstring) "D203", # 1 blank line required before class docstring (reason: pep257 default) "D213", # Multi-line docstring summary should start at the second line (reason: pep257 default) "D215", # Section underline is over-indented (reason: pep257 default) "A003", # Class attribute shadowing built-in (reason: Class attributes don't often get bare references) "SIM117", # Use a single `with` statement with multiple contexts instead of nested `with` statements # (reason: this creates long lines that get wrapped and reduces readability) # Ignored due to conflicts with ruff's formatter: # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules "COM812", # Missing trailing comma - mostly the same, but marginal differences. "ISC001", # Single-line implicit string concatenation. # Ignored due to common usage in current code "TRY003", # Avoid specifying long messages outside the exception class ] [tool.ruff.lint.flake8-annotations] allow-star-arg-any = true [tool.ruff.lint.pydocstyle] ignore-decorators = [ # Functions with these decorators don't have to have docstrings. "typing.overload", # Default configuration # The next four are all variations on override, so child classes don't have to repeat parent classes' docstrings. "overrides.override", "overrides.overrides", "typing.override", "typing_extensions.override", ] [tool.ruff.lint.pylint] max-args = 8 [tool.ruff.lint.pep8-naming] # Allow Pydantic's `@validator` decorator to trigger class method treatment. classmethod-decorators = ["pydantic.validator", "pydantic.root_validator"] [tool.ruff.lint.per-file-ignores] "tests/**.py" = [ # Some things we want for the main project are unnecessary in tests. "D", # Ignore docstring rules in tests "ANN", # Ignore type annotations in tests "ARG", # Allow unused arguments in tests (e.g. for fake functions/methods/classes) "S101", # Allow assertions in tests "S103", # Allow `os.chmod` setting a permissive mask `0o555` on file or directory "S108", # Allow Probable insecure usage of temporary file or directory "PLR0913", # Allow many arguments for test functions (useful if we need many fixtures) "PLR2004", # Allow magic values in tests "SLF", # Allow accessing private members from tests. ] "__init__.py" = [ "I001", # isort leaves init files alone by default, this makes ruff ignore them too. "F401", # Allows unused imports in __init__ files. ]