From 86d0b76c11fefbb565d2af06b8f5e7139ab6b208 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 30 Jun 2025 21:00:42 -0700 Subject: [PATCH] chore: modernize Python tooling setup (Phase 1) - Migrate to comprehensive pyproject.toml configuration - Move all package metadata from setup.py - Configure build system with setuptools backend - Include private package in distribution - Use SPDX license identifier (Apache-2.0) - Add uv for fast dependency management with .python-version - Configure ruff for linting and formatting (replacing pylint/yapf) - Use modern Python formatting standards (Black-style) - Enable Python 3.10+ syntax improvements - Configure to match existing code style where possible - Update GitHub Actions workflows - Replace venv/pip with uv for faster installs - Replace pylint with ruff check - Replace yapf with ruff format - Use python -m build instead of setup.py - Add .git-blame-ignore-revs for future formatting changes - Update .gitignore to exclude uv.lock This is Phase 1 of the modernization plan. Next steps: - Remove setup.py after verifying builds work - Apply ruff formatting in separate commit - Add tox configuration for multi-version testing --- .git-blame-ignore-revs | 6 + .github/workflows/ci.yaml | 57 +++++----- .github/workflows/release.yaml | 13 ++- .gitignore | 5 +- .python-version | 1 + pyproject.toml | 194 ++++++++++++++++++++++++++++++--- 6 files changed, 227 insertions(+), 49 deletions(-) create mode 100644 .git-blame-ignore-revs create mode 100644 .python-version diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..f5937b46 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,6 @@ +# .git-blame-ignore-revs +# Use this file to ignore commits in git blame that are just formatting changes +# Configure with: git config blame.ignoreRevsFile .git-blame-ignore-revs + +# Switch from yapf to ruff formatting (one-time reformatting) +# TODO: Add commit hash here after formatting commit \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5e0cfacc..d8201237 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,20 +16,20 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Install uv + uses: astral-sh/setup-uv@v2 + with: + version: "latest" - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Install dependencies run: | - python${{ matrix.python }} -m venv venv - source venv/bin/activate - pip3 install --upgrade pip - python${{ matrix.python }} -m pip install -e ".[dev]" + uv sync --extra dev - name: Test with pytest & coverage run: | - source venv/bin/activate - python${{ matrix.python }} -m pytest --cov=src --cov-report term --cov-report html --cov-report xml -vv + uv run pytest --cov=src --cov-report term --cov-report html --cov-report xml -vv # TODO requires activation for this repository on codecov website first. # - name: Upload coverage to Codecov # uses: codecov/codecov-action@v3 @@ -38,44 +38,43 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Install uv + uses: astral-sh/setup-uv@v2 + with: + version: "latest" - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install dependencies run: | - python3.10 -m venv venv - source venv/bin/activate - pip3 install --upgrade pip - python3.10 -m pip install -e ".[dev]" - - name: Lint with pylint + uv sync --extra dev + - name: Lint with ruff run: | - source venv/bin/activate - python3.10 -m pylint $(git ls-files '*.py') - - name: Lint with mypy + uv run ruff check . + - name: Type check with mypy run: | - source venv/bin/activate - python3.10 -m mypy . + uv run mypy . docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Install uv + uses: astral-sh/setup-uv@v2 + with: + version: "latest" - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install dependencies run: | - python3.10 -m venv venv - source venv/bin/activate - pip3 install --upgrade pip - python3.10 -m pip install -e ".[dev]" + uv sync --extra dev - name: Generate Reference Docs run: | - source venv/bin/activate mkdir ./docs/build - ./docs/generate.sh --out=./docs/build/ --pypath=src/ + uv run ./docs/generate.sh --out=./docs/build/ --pypath=src/ - uses: actions/upload-artifact@v4 name: Upload Docs Preview with: @@ -86,17 +85,17 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Install uv + uses: astral-sh/setup-uv@v2 + with: + version: "latest" - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.10" - name: Install dependencies run: | - python3.10 -m venv venv - source venv/bin/activate - pip3 install --upgrade pip - python3.10 -m pip install -e ".[dev]" - - name: Check Formatting + uv sync --extra dev + - name: Check Formatting with ruff run: | - source venv/bin/activate - yapf -d -r -p . + uv run ruff format --check . \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 851f32fd..07b4fa2e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -36,6 +36,11 @@ jobs: - name: Checkout source for staging uses: actions/checkout@v3 + - name: Install uv + uses: astral-sh/setup-uv@v2 + with: + version: "latest" + - name: Set up Python uses: actions/setup-python@v4 with: @@ -43,18 +48,16 @@ jobs: - name: Install dependencies run: | - pip install --upgrade pip - python -m pip install -e ".[dev]" + uv sync --extra dev - name: Test with pytest & coverage run: | - python -m pytest --cov=src --cov-report term --cov-report html --cov-report xml -vv + uv run pytest --cov=src --cov-report term --cov-report html --cov-report xml -vv # Build the Python Wheel and the source distribution. - name: Package release artifacts run: | - python -m pip install setuptools wheel - python setup.py bdist_wheel sdist + uv run python -m build # Attach the packaged artifacts to the workflow output. These can be manually # downloaded for later inspection if necessary. diff --git a/.gitignore b/.gitignore index 6339aeb6..973c8755 100644 --- a/.gitignore +++ b/.gitignore @@ -82,7 +82,7 @@ profile_default/ ipython_config.py # pyenv -.python-version +# .python-version # We're now tracking this for uv # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. @@ -139,3 +139,6 @@ doc/dist .idea .vscode/* !.vscode/settings.json + +# uv +uv.lock diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..7c7a975f --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f2171b20..c0ac8b94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,23 +1,189 @@ -[tool.pytest.ini_options] -pythonpath = [ - ".", "src/", +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "firebase_functions" +description = "Firebase Functions Python SDK" +readme = "README.md" +license = "Apache-2.0" +authors = [{name = "Firebase Team"}] +keywords = ["firebase", "functions", "google", "cloud"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Software Development :: Build Tools", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +requires-python = ">=3.10" +dynamic = ["version"] +dependencies = [ + "flask>=2.1.2", + "functions-framework>=3.0.0", + "firebase-admin>=6.0.0", + "pyyaml>=6.0", + "typing-extensions>=4.4.0", + "cloudevents>=1.2.0,<2.0.0", + "flask-cors>=3.0.10", + "pyjwt[crypto]>=2.5.0", + "google-events==0.5.0", + "google-cloud-firestore>=2.11.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.1.2", + "pytest-cov>=3.0.0", + "mypy>=1.0.0", + "sphinx>=6.1.3", + "sphinxcontrib-napoleon>=0.7", + "toml>=0.10.2", + "google-cloud-tasks>=2.13.1", + "ruff>=0.1.0", + "pre-commit>=3.0.0", + "tox>=4.0.0", + "build>=1.0.0", +] +test = [ + "pytest>=7.1.2", + "pytest-cov>=3.0.0", + "google-cloud-tasks>=2.13.1", +] +docs = [ + "sphinx>=6.1.3", + "sphinxcontrib-napoleon>=0.7", ] + +[project.urls] +Homepage = "https://github.com/firebase/firebase-functions-python" +Repository = "https://github.com/firebase/firebase-functions-python" +Issues = "https://github.com/firebase/firebase-functions-python/issues" + +[tool.setuptools] +packages = ["firebase_functions", "firebase_functions.private"] +package-dir = {"" = "src"} +include-package-data = true + +[tool.setuptools.package-data] +firebase_functions = ["py.typed"] + +[tool.setuptools.dynamic] +version = {attr = "firebase_functions.__version__"} + +[tool.pytest.ini_options] +pythonpath = [".", "src/"] +testpaths = ["tests"] +addopts = "-v --cov=firebase_functions --cov-report=term-missing" + [tool.coverage] [tool.coverage.run] + source = ["src/firebase_functions"] omit = [ - '__init__.py', - 'tests/*', - '*/tests/*', + "__init__.py", + "tests/*", + "*/tests/*", ] [tool.coverage.report] skip_empty = true -[tool.yapf] -based_on_style = "google" -indent_width = 4 -[tool.yapfignore] -ignore_patterns = [ - "venv", - "build", - "dist", + show_missing = true + precision = 2 + +[tool.mypy] +python_version = "3.10" +exclude = ["build", "dist", "venv", "docs"] +ignore_missing_imports = true +enable_incomplete_feature = ["Unpack"] +strict = true +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +warn_unreachable = true +strict_equality = true + +[[tool.mypy.overrides]] +module = "yaml.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "cloudevents.*" +ignore_missing_imports = true + +[tool.ruff] +target-version = "py310" +line-length = 100 +indent-width = 4 + +[tool.ruff.lint] +# Enable Pyflakes `E` and `F` codes by default, plus additional rules +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "PL", # pylint + "PIE", # flake8-pie + "RET", # flake8-return + "SIM", # flake8-simplify + "TCH", # flake8-type-checking + "PTH", # flake8-use-pathlib + "ERA", # eradicate + "TRY", # tryceratops + "RUF", # Ruff-specific rules ] + +# Disable specific rules to match existing pylint configuration +ignore = [ + "PLR0913", # Too many arguments + "PLR0912", # Too many branches + "PLR0915", # Too many statements + "PLR2004", # Magic value used in comparison + "PLW0603", # Using the global statement + "PLC0415", # Import outside toplevel + "E501", # Line too long (handled by formatter) + "TRY003", # Avoid specifying long messages outside the exception class + "TRY400", # Use logging.exception instead of logging.error + "B008", # Do not perform function calls in argument defaults + "SIM108", # Use ternary operator + "SIM114", # Combine if branches using logical or + "RET504", # Unnecessary variable assignment before return + "RET505", # Unnecessary else after return + "RET506", # Unnecessary else after raise + # Removed UP007 to allow modern Union type syntax +] + +# Enable Python 3.10+ specific rules +pyupgrade.keep-runtime-typing = false + +[tool.ruff.lint.pylint] +max-args = 10 +max-branches = 20 +max-returns = 10 +max-statements = 100 + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["PLR2004", "PLR0913"] +"setup.py" = ["ALL"] + +[tool.ruff.lint.isort] +known-first-party = ["firebase_functions"] +combine-as-imports = true +split-on-trailing-comma = false + +[tool.ruff.format] +quote-style = "double" # Consistent double quotes (Black-style) +indent-style = "space" +skip-magic-trailing-comma = false # Always add trailing commas for cleaner diffs +line-ending = "auto" +docstring-code-format = true # Format code examples in docstrings \ No newline at end of file