Skip to content

Dependencies & Configs: Proposals for improvement and optimisation #599

@alexted

Description

@alexted

It is possible to move most settings from separate files, such as setup.py, setup.cfg, MANIFEST.in, and other configuration files, inside pyproject.toml. Below is an example that combines these files and configures dependencies, package instructions, and other settings to reduce the number of separate files to a minimum:

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "flask-jsonrpc"
version = "4.0.0a7"  # Укажите нужную версию
description = "Adds JSONRPC support to Flask."
readme = {file = "README.md", content-type = "text/markdown"}
license = {text = "MIT"}
authors = [
    {name = "Nycholas Oliveira", email = "[email protected]"},
    {name = "Your Name", email = "[email protected]"}
]
maintainers = [{name = "Cenobit Technologies Inc.", email = "[email protected]"}]
keywords = ["flask", "flask-extensions", "jsonrpc", "json-rpc", "rpc"]
classifiers = [
    "Development Status :: 5 - Production/Stable",
    "Environment :: Web Environment",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
    "Programming Language :: Python",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.7",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
    "Topic :: Software Development :: Libraries :: Application Frameworks",
]

requires-python = ">=3.7"
dependencies = [
    "Flask>=3.0.0,<4.0",
    "werkzeug>=1.0",
    "typeguard==4.4.0",
    "typing_extensions>=4.3.0",
    "typing_inspect==0.9.0",
    "pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4",
    "eval_type_backport==0.2.0"
]

[project.optional-dependencies]
async = ["Flask[async]>=3.0.0,<4.0"]
dotenv = ["Flask[dotenv]>=3.0.0,<4.0"]
dev = [
    "pytest",
    "black",
    "isort",
]
test = [
    "pytest",
    "pytest-cov",
]
lint = [
    "flake8",
    "black",
    "isort",
]
docs = [
    "sphinx",
    "sphinx-rtd-theme",
]

[project.urls]
Donate = "https://github.com/sponsors/nycholas"
Homepage = "https://github.com/cenobites/flask-jsonrpc"
Documentation = "https://flask-jsonrpc.readthedocs.io/"
"Source Code" = "https://github.com/cenobites/flask-jsonrpc"
"Issue Tracker" = "https://github.com/cenobites/flask-jsonrpc/issues/"
Website = "https://flask-jsonrpc.readthedocs.io/"

[tool.setuptools.packages.find]
include = ["flask_jsonrpc"]

# Конфигурация для инструментов
[tool.black]
line-length = 88
target-version = ["py37"]

[tool.isort]
profile = "black"

[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-v --tb=short"
testpaths = ["tests"]

[tool.flake8]
max-line-length = 88
extend-ignore = ["E203", "W503"]

# Конфигурация для tox
[tool.tox]
envlist = ["py37", "py38", "py39", "lint", "test"]

[tool.tox.env.py37]
description = "Run tests on Python 3.7"
package = "wheel"
requires = ["pytest", "pytest-cov"]
deps = ["pytest", "pytest-cov"]
commands = ["pytest"]

[tool.tox.env.py38]
description = "Run tests on Python 3.8"
package = "wheel"
requires = ["pytest", "pytest-cov"]
deps = ["pytest", "pytest-cov"]
commands = ["pytest"]

[tool.tox.env.py39]
description = "Run tests on Python 3.9"
package = "wheel"
requires = ["pytest", "pytest-cov"]
deps = ["pytest", "pytest-cov"]
commands = ["pytest"]

[tool.tox.env.lint]
description = "Run linters (flake8, black, isort)"
deps = ["flake8", "black", "isort"]
commands = [
    "flake8 src tests",
    "black --check src tests",
    "isort --check-only src tests"
]

[tool.tox.env.test]
description = "Run tests with coverage"
deps = ["pytest", "pytest-cov"]
commands = [
    "pytest --cov=flask_jsonrpc --cov-report=term-missing"
]

[tool.cibuildwheel]
before-all = "uname -a"
build-verbosity = 1
skip = [
    "cp3{8,9,10}-*",
    "*-manylinux_i686",
    "*-musllinux_i686",
    "*-win32",
    "pp*",
]
test-requires = [
    "pytest",
    "pytest-cov",
    "pytest-xdist",
    "pytest-sugar",
    "pytest-env",
    "requests"
]
test-command = "pytest -n auto -vv --tb=short --rootdir={project} {project}/tests --cov-fail-under=0"

[tool.cibuildwheel.environment]
MYPYC_ENABLE = "1"

[tool.ruff]
src = ["src"]
fix = true
show-fixes = true
output-format = "concise"
line-length = 120
target-version = "py39"

[tool.ruff.lint]
select = [
    "E",    # pycodestyle error
    "W",    # pycodestyle warning
    "F",    # pyflakes
    "UP",   # pyupgrade
    "ANN",  # flake8-annotations
    "B",    # flake8-bugbear
    "Q",    # flake8-quotes
    "SIM",  # flake8-simplify
    "T",    # flake8-type-checking
    "B",    # flake8-bandit
    "C",    # flake8-copyright
    "I",    # isort
]

[tool.ruff.lint.mccabe]
max-complexity = 10

[tool.ruff.lint.flake8-quotes]
inline-quotes = "single"
docstring-quotes = "double"

[tool.ruff.lint.flake8-type-checking]
exempt-modules = ["typing", "typing_extensions", "annotated_types"]

[tool.ruff.lint.flake8-bandit]
check-typed-exception = true

[tool.ruff.lint.flake8-copyright]
author = "Cenobit Technologies, Inc. http://cenobit.es/"

[tool.ruff.lint.isort]
length-sort = true
combine-as-imports = true
order-by-type = true
force-sort-within-sections = true
split-on-trailing-comma = false
section-order = [
    "future",
    "standard-library",
    "typing-extensions",
    "flask",
    "pydantic",
    "third-party",
    "first-party",
    "local-folder"
]

[tool.ruff.lint.isort.sections]
"flask" = ["flask"]
"pydantic" = ["pydantic"]
"typing-extensions" = ["typing_inspect", "typing_extensions"]

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.format]
quote-style = "single"
indent-style = "space"
skip-magic-trailing-comma = true
docstring-code-format = true
docstring-code-line-length = 79

[tool.coverage.run]
branch = true
source = [
    "src/flask_jsonrpc",
    "tests",
]
omit = [
    "*/settings.py",
    "*/fixtures.py",
    "*/tests.py",
    "*/test_*.py",
    "*/*_tests.py",
]

[tool.coverage.paths]
source = [
    "src",
    "*/site-packages",
]

[tool.coverage.report]
fail_under = 100
ignore_errors = true
skip_covered = true
exclude_lines = [
    "pragma: no cover",
    "pragma: no cover ${PRAGMA_VERSION}",
    "def __repr__",
    "if self\\.debug",
    "if settings\\.DEBUG",
    "if current_app\\.config\\['DEBUG'\\]",
    "if app\\.config\\['DEBUG'\\]",
    "raise AssertionError",
    "raise NotImplementedError",
    "if 0:",
    "if __name__ == .__main__.:",
    "if TYPE_CHECKING:",
    "if t\\.TYPE_CHECKING:",
]

[tool.mypy]
plugins = ["pydantic.mypy"]
files = ["src/flask_jsonrpc"]
python_version = "3.12"
pretty = true
strict = true
show_error_codes = true
ignore_errors = false
ignore_missing_imports = false
warn_redundant_casts = true
warn_unused_ignores = true
no_implicit_reexport = true
check_untyped_defs = true
disallow_any_generics = true
disallow_untyped_defs = true

[[tool.mypy.overrides]]
module = [
    "asgiref.*",
    "mypy-werkzeug.datastructures.*",
    "typeguard.*",
    "typing_inspect.*",
    "dotenv.*",
]
ignore_missing_imports = true

[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true

[tool.pytype]
inputs = ["src/flask_jsonrpc"]
python_version = "3.11"
disable = ["invalid-annotation"]

[tool.pyright]
pythonVersion = "3.12"
include = ["src/flask_jsonrpc"]
typeCheckingMode = "basic"

Explanation of New Sections

  1. [build-system] — Specifies the build system requirements, indicating the need for setuptools and wheel for building the package.

  2. [project] — Contains essential project metadata, such as name, version, description, authors, classifiers, and main dependencies. It also includes optional dependencies for different environments like development, testing, linting, and documentation.

  3. [project.optional-dependencies] — Defines optional dependencies that can be installed based on the specific needs of the project, such as development tools (dev), testing tools (test), linters (lint), and documentation tools (docs).

  4. [project.urls] — Provides URLs related to the project, including the homepage, documentation, and source repository.

  5. [tool.setuptools.packages.find] — Configures the packages to be included, similar to find_packages() in setup.py.

  6. [tool.black] and [tool.isort] — Configuration settings for code formatting tools like black and isort, defining rules for code style.

  7. [tool.pytest.ini_options] — Configuration for pytest, specifying minimum version requirements, command-line options, and test paths.

  8. [tool.flake8] — Configuration for the flake8 linter, defining rules such as maximum line length and ignored errors.

  9. [tool.tox] — Main section for configuring tox, listing the different environments for testing and their dependencies.

  10. [tool.tox.env.] — Configuration for individual tox environments (like py37, py38, lint, and test), specifying dependencies, commands to run, and descriptions for each environment.

This consolidated configuration allows for easier management and eliminates the need for multiple configuration files like setup.py, setup.cfg, MANIFEST.in, .flake8, .isort.cfg, .coveragerc, tox.ini, and requirements/*.txt, streamlining the project structure.

You can then use docker's two-phase image creation in elegant style, where in the first phase you simply convert pyproject.toml to requirements.txt like this:

FROM python:3.12-slim as requirements-stage

WORKDIR /tmp

RUN pip install poetry

COPY pyproject.toml ./poetry.lock /tmp/

RUN poetry export -f requirements.txt --output requirements.txt --without-hashes

# Pull official base image
FROM python:3.12-slim

# Set work directory
WORKDIR /usr/src/app

# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/go/dockerfile-user-best-practices/
ARG UID=10001
RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/nonexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    appuser

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Install dependencies
COPY --from=requirements-stage /tmp/requirements.txt ./requirements.txt
RUN apt update && apt install -y --no-install-recommends gcc python3-dev \
    && pip install --no-cache-dir -U pip setuptools wheel && pip install --no-cache-dir -r requirements.txt \
    && apt purge -y --auto-remove gcc python3-dev

# Copy project files
COPY . .

# Make entrypoint.sh executable
RUN chmod +x ./entrypoint.sh

# Switch to the non-privileged user to run the application.
USER appuser

# Publish network port
EXPOSE 5000

# Execute script to start the application web server
ENTRYPOINT ["./entrypoint.sh"]

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions