From 922aa44155684b6e36516e10c23be86696b06131 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sat, 28 Feb 2026 23:36:41 +0100 Subject: [PATCH 01/18] ci(framework): Migrate pyproject.toml to use [project] --- .github/workflows/framework-devtools.yml | 2 +- .../framework-docs-update-translations.yml | 3 +- .github/workflows/framework-docs.yml | 2 +- .github/workflows/framework-e2e.yml | 6 +- .../workflows/framework-release-nightly.yml | 4 +- .github/workflows/framework-test.yml | 4 +- dev/pyproject.toml | 70 +++---- framework/dev/publish-nightly.sh | 3 +- framework/dev/pyproject_meta.py | 127 ++++++++++++ ...or-how-to-install-development-versions.rst | 3 +- ...ontributor-how-to-set-up-a-virtual-env.rst | 8 +- ...contributor-how-to-write-documentation.rst | 7 +- ...-tutorial-get-started-as-a-contributor.rst | 2 +- .../source/how-to-upgrade-to-flower-1.0.rst | 5 +- framework/e2e/strategies/pyproject.toml | 10 +- framework/pyproject.toml | 190 +++++++++--------- 16 files changed, 289 insertions(+), 157 deletions(-) create mode 100644 framework/dev/pyproject_meta.py diff --git a/.github/workflows/framework-devtools.yml b/.github/workflows/framework-devtools.yml index 37a9ac41377e..067a2fe40f32 100644 --- a/.github/workflows/framework-devtools.yml +++ b/.github/workflows/framework-devtools.yml @@ -30,6 +30,6 @@ jobs: - name: Install dependencies (mandatory + optional) run: | cd framework - python -m poetry install + python -m poetry install --with dev - name: Lint + Test (isort/black/mypy/pylint/pytest) run: ./framework/dev/test-tool.sh diff --git a/.github/workflows/framework-docs-update-translations.yml b/.github/workflows/framework-docs-update-translations.yml index c8ec8dfd9bd1..664ff50fe9bb 100644 --- a/.github/workflows/framework-docs-update-translations.yml +++ b/.github/workflows/framework-docs-update-translations.yml @@ -31,8 +31,7 @@ jobs: - name: Install dependencies run: | cd framework - python -m poetry install - pip install sphinx==7.4.7 + python -m poetry install --with dev - name: Install pandoc uses: nikeee/setup-pandoc@v1 diff --git a/.github/workflows/framework-docs.yml b/.github/workflows/framework-docs.yml index 4c2ba6d5f60a..02548072d4cf 100644 --- a/.github/workflows/framework-docs.yml +++ b/.github/workflows/framework-docs.yml @@ -43,7 +43,7 @@ jobs: - name: Install Flower Framework run: | cd framework - python -m poetry install + python -m poetry install --with dev - name: Sync required docs build assets from main for release branches if: ${{ startsWith(github.ref, 'refs/heads/release/framework-') || (github.event_name == 'pull_request' && startsWith(github.base_ref, 'release/framework-')) || (github.event_name == 'release' && startsWith(github.ref, 'refs/tags/framework-')) }} run: | diff --git a/.github/workflows/framework-e2e.yml b/.github/workflows/framework-e2e.yml index 6a4df392133e..dfa65e02db0f 100644 --- a/.github/workflows/framework-e2e.yml +++ b/.github/workflows/framework-e2e.yml @@ -44,10 +44,10 @@ jobs: - uses: actions/checkout@v5 - name: Bootstrap uses: ./.github/actions/bootstrap - - name: Install dependencies (mandatory only) + - name: Install dependencies run: | cd framework - python -m poetry install --all-extras + python -m poetry install --with dev --all-extras - name: Build wheel run: ./framework/dev/build.sh - name: Test wheel @@ -284,7 +284,7 @@ jobs: - name: Install dependencies if: ${{ needs.changes.outputs.framework == 'true' }} run: | - python -m poetry install + python -m poetry install --only main - name: Install Flower wheel from artifact store if: ${{ needs.changes.outputs.framework == 'true' && github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | diff --git a/.github/workflows/framework-release-nightly.yml b/.github/workflows/framework-release-nightly.yml index df21ce0f62b2..0dbf5ddd8f48 100644 --- a/.github/workflows/framework-release-nightly.yml +++ b/.github/workflows/framework-release-nightly.yml @@ -36,8 +36,8 @@ jobs: echo "pip-version=${{ steps.bootstrap.outputs.pip-version }}" >> "$GITHUB_OUTPUT" echo "setuptools-version=${{ steps.bootstrap.outputs.setuptools-version }}" >> "$GITHUB_OUTPUT" - NAME=$(poetry version | awk {'print $1'}) - VERSION=$(poetry version -s) + NAME=$(python dev/pyproject_meta.py get-name pyproject.toml) + VERSION=$(python dev/pyproject_meta.py get-version pyproject.toml) python dev/build-docker-image-matrix.py --flwr-version "${VERSION}" --matrix nightly --flwr-package "${NAME}" > matrix.json echo "matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/framework-test.yml b/.github/workflows/framework-test.yml index 949dcdf9b3ba..c877cf8f8df3 100644 --- a/.github/workflows/framework-test.yml +++ b/.github/workflows/framework-test.yml @@ -61,11 +61,11 @@ jobs: uses: ./.github/actions/bootstrap with: python-version: ${{ matrix.python }} - - name: Install dependencies (mandatory only) + - name: Install dependencies if: ${{ needs.changes.outputs.framework == 'true' || needs.changes.outputs.ex_bench == 'true' }} run: | cd framework - python -m poetry install --all-extras + python -m poetry install --with dev --all-extras - name: Check if protos need recompilation if: ${{ needs.changes.outputs.framework == 'true' }} run: ./framework/dev/check-protos.sh diff --git a/dev/pyproject.toml b/dev/pyproject.toml index 4afcc8df7a45..26aff596ea33 100644 --- a/dev/pyproject.toml +++ b/dev/pyproject.toml @@ -2,45 +2,47 @@ requires = ["poetry-core>=2.1.3"] build-backend = "poetry.core.masonry.api" -[tool.poetry] +[project] name = "devtool" version = "0.0.0" description = "Tools for developers." license = "Apache-2.0" -authors = ["The Flower Authors "] -packages = [{ include = "devtool", from = "./" }] +authors = [{ name = "The Flower Authors", email = "hello@flower.ai" }] +requires-python = ">=3.10,<4.0" +dependencies = [ + "clang-format==17.0.6", + "isort==5.13.2", + "black==25.11.0", + "taplo==0.9.3", + "docformatter==1.7.5", + "rope==1.13.0", + "semver==3.0.2", + "sphinx==7.4.7", + "sphinx-intl==2.2.0", + "sphinx-click==6.0.0", + "myst-parser==3.0.1", + "sphinx-design==0.6.1", + "sphinx-copybutton==0.5.2", + "sphinxcontrib-mermaid==0.9.2", + "sphinxcontrib-youtube==1.4.1", + "furo==2024.8.6", + "sphinx-reredirects==0.1.5", + "nbsphinx==0.9.5", + "nbstripout==0.6.1", + "sphinx-argparse==0.4.0", + "mdformat==1.0.0", + "mdformat-gfm==1.0.0", + "mdformat-frontmatter==2.0.10", + "mdformat-beautysh==1.0.0", + "beautysh==6.4.2", + "GitPython==3.1.32", + "sphinx-substitution-extensions==2022.2.16", + "sphinxext-opengraph==0.9.1", + "docstrfmt @ git+https://github.com/charlesbvll/docstrfmt.git@patch-2 ; python_version >= '3.10' and python_version < '4.0'", +] -[tool.poetry.dependencies] -python = "^3.10" -clang-format = "==17.0.6" -isort = "==5.13.2" -black = { version = "==25.11.0" } -taplo = "==0.9.3" -docformatter = "==1.7.5" -rope = "==1.13.0" -semver = "==3.0.2" -sphinx = "==7.4.7" -sphinx-intl = "==2.2.0" -sphinx-click = "==6.0.0" -myst-parser = "==3.0.1" -sphinx-design = "==0.6.1" -sphinx-copybutton = "==0.5.2" -sphinxcontrib-mermaid = "==0.9.2" -sphinxcontrib-youtube = "==1.4.1" -furo = "==2024.8.6" -sphinx-reredirects = "==0.1.5" -nbsphinx = "==0.9.5" -nbstripout = "==0.6.1" -sphinx-argparse = "==0.4.0" -mdformat = "==1.0.0" -mdformat-gfm = "==1.0.0" -mdformat-frontmatter = "==2.0.10" -mdformat-beautysh = "==1.0.0" -beautysh = "==6.4.2" -GitPython = "==3.1.32" -sphinx-substitution-extensions = "2022.02.16" -sphinxext-opengraph = "==0.9.1" -docstrfmt = { git = "https://github.com/charlesbvll/docstrfmt.git", branch = "patch-2", python = ">=3.10,<4.0.0" } +[tool.poetry] +packages = [{ include = "devtool", from = "./" }] [tool.docstrfmt] extend_exclude = [ diff --git a/framework/dev/publish-nightly.sh b/framework/dev/publish-nightly.sh index 0c03cdda9f49..6f65afcda49d 100755 --- a/framework/dev/publish-nightly.sh +++ b/framework/dev/publish-nightly.sh @@ -26,8 +26,7 @@ cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/../ # "0.1.1.dev20200716" as seen at https://pypi.org/project/flwr-nightly/ if [[ $(git log --since="24 hours ago" --pretty=oneline) ]]; then - sed -i -E "s/^name = \"(.+)\"/name = \"\1-nightly\"/" pyproject.toml - sed -i -E "s/^version = \"(.+)\"/version = \"\1.dev$(date '+%Y%m%d')\"/" pyproject.toml + python dev/pyproject_meta.py set-nightly-name-version pyproject.toml python -m poetry build python -m poetry publish -u __token__ -p $PYPI_TOKEN else diff --git a/framework/dev/pyproject_meta.py b/framework/dev/pyproject_meta.py new file mode 100644 index 000000000000..88f79299af62 --- /dev/null +++ b/framework/dev/pyproject_meta.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""Read and update [project] metadata in pyproject.toml.""" + +from __future__ import annotations + +import argparse +import re +import sys +from datetime import datetime +from pathlib import Path + + +class ProjectSectionError(RuntimeError): + """Raised when [project] metadata is missing or malformed.""" + + +def _load_lines(path: Path) -> list[str]: + return path.read_text(encoding="utf-8").splitlines(keepends=True) + + +def _find_project_bounds(lines: list[str]) -> tuple[int, int]: + start = -1 + for idx, line in enumerate(lines): + if line.strip() == "[project]": + start = idx + break + + if start == -1: + raise ProjectSectionError("Missing [project] section in pyproject.toml") + + end = len(lines) + for idx in range(start + 1, len(lines)): + stripped = lines[idx].strip() + if stripped.startswith("[") and stripped.endswith("]"): + end = idx + break + + return start, end + + +def _get_project_value(lines: list[str], key: str) -> str: + start, end = _find_project_bounds(lines) + pattern = re.compile(rf'^\s*{re.escape(key)}\s*=\s*"([^"]+)"') + + for idx in range(start + 1, end): + match = pattern.match(lines[idx]) + if match: + return match.group(1) + + raise ProjectSectionError(f'Missing [project].{key} in pyproject.toml') + + +def _set_project_value(lines: list[str], key: str, value: str) -> None: + start, end = _find_project_bounds(lines) + pattern = re.compile(rf'^(\s*{re.escape(key)}\s*=\s*")([^"]*)(".*)$') + + for idx in range(start + 1, end): + match = pattern.match(lines[idx]) + if match: + lines[idx] = f"{match.group(1)}{value}{match.group(3)}\n" + return + + raise ProjectSectionError(f'Missing [project].{key} in pyproject.toml') + + +def _cmd_get_name(path: Path) -> int: + print(_get_project_value(_load_lines(path), "name")) + return 0 + + +def _cmd_get_version(path: Path) -> int: + print(_get_project_value(_load_lines(path), "version")) + return 0 + + +def _cmd_set_nightly(path: Path) -> int: + lines = _load_lines(path) + + name = _get_project_value(lines, "name") + version = _get_project_value(lines, "version") + + nightly_name = name if name.endswith("-nightly") else f"{name}-nightly" + base_version = version.split(".dev", maxsplit=1)[0] + nightly_version = f"{base_version}.dev{datetime.utcnow().strftime('%Y%m%d')}" + + _set_project_value(lines, "name", nightly_name) + _set_project_value(lines, "version", nightly_version) + + path.write_text("".join(lines), encoding="utf-8") + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest="command", required=True) + + p_get_name = sub.add_parser("get-name", help="Print [project].name") + p_get_name.add_argument("pyproject", type=Path) + + p_get_version = sub.add_parser("get-version", help="Print [project].version") + p_get_version.add_argument("pyproject", type=Path) + + p_set_nightly = sub.add_parser( + "set-nightly-name-version", + help="Set [project].name and [project].version for nightly publishing", + ) + p_set_nightly.add_argument("pyproject", type=Path) + + args = parser.parse_args() + + try: + if args.command == "get-name": + return _cmd_get_name(args.pyproject) + if args.command == "get-version": + return _cmd_get_version(args.pyproject) + if args.command == "set-nightly-name-version": + return _cmd_set_nightly(args.pyproject) + except ProjectSectionError as err: + print(f"Error: {err}", file=sys.stderr) + return 1 + + print(f"Unsupported command: {args.command}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/framework/docs/source/contributor-how-to-install-development-versions.rst b/framework/docs/source/contributor-how-to-install-development-versions.rst index e2d0fea18fa7..3776a527fa49 100644 --- a/framework/docs/source/contributor-how-to-install-development-versions.rst +++ b/framework/docs/source/contributor-how-to-install-development-versions.rst @@ -10,8 +10,7 @@ Using Poetry (recommended) ========================== Install a ``flwr`` pre-release from PyPI: update the ``flwr`` dependency in -``pyproject.toml`` and then reinstall (don't forget to delete ``poetry.lock`` (``rm -poetry.lock``) before running ``poetry install``). +``pyproject.toml`` and then reinstall with ``python -m poetry install``. - ``flwr = { version = "1.0.0a0", allow-prereleases = true }`` (without extras) - ``flwr = { version = "1.0.0a0", allow-prereleases = true, extras = ["simulation"] }`` diff --git a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst index e0dba5187bab..a56b708abd0f 100644 --- a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst +++ b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst @@ -52,18 +52,20 @@ Activate the virtualenv by running the following command: ************************ The Flower examples are based on `Poetry `_ to manage -dependencies. After installing Poetry you simply create a virtual environment with: +dependencies. After installing Poetry you can create and activate a virtual environment +with: .. code-block:: shell - poetry shell + python -m poetry install --with dev --all-extras + eval "$(python -m poetry env activate)" If you open a new terminal you can activate the previously created virtual environment with the following command: .. code-block:: shell - source $(poetry env info --path)/bin/activate + eval "$(python -m poetry env activate)" ************************** Virtualenv with Anaconda diff --git a/framework/docs/source/contributor-how-to-write-documentation.rst b/framework/docs/source/contributor-how-to-write-documentation.rst index 49eb3a647862..d214ea69678f 100644 --- a/framework/docs/source/contributor-how-to-write-documentation.rst +++ b/framework/docs/source/contributor-how-to-write-documentation.rst @@ -9,7 +9,8 @@ The Flower documentation lives in the ``doc`` directory. The Sphinx-based documentation system supports both reStructuredText (``.rst`` files) and Markdown (``.md`` files). -Note that, in order to build the documentation locally (with ``poetry run make html``, +Note that, in order to build the documentation locally (with ``python -m poetry run +make html``, like described below), `Pandoc `_ needs to be installed on the system. @@ -18,7 +19,7 @@ installed on the system. *********************** 1. Edit an existing ``.rst`` (or ``.md``) file under ``framework/docs/source/`` -2. Compile the docs: ``cd framework/docs``, then ``poetry run make html`` +2. Compile the docs: ``cd framework/docs``, then ``python -m poetry run make html`` 3. Open ``framework/docs/build/html/index.html`` in the browser to check the result ******************* @@ -28,5 +29,5 @@ installed on the system. 1. Add new ``.rst`` file under ``framework/docs/source/`` 2. Add content to the new ``.rst`` file 3. Link to the new rst from ``index.rst`` -4. Compile the docs: ``cd framework/docs``, then ``poetry run make html`` +4. Compile the docs: ``cd framework/docs``, then ``python -m poetry run make html`` 5. Open ``framework/docs/build/html/index.html`` in the browser to check the result diff --git a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst index 334ac54449eb..cf549138075e 100644 --- a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst +++ b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst @@ -83,7 +83,7 @@ Create Flower Dev Environment :: (your-env-name) $ cd framework - (your-env-name) $ python -m poetry install --all-extras + (your-env-name) $ python -m poetry install --with dev --all-extras ********************* Convenience Scripts diff --git a/framework/docs/source/how-to-upgrade-to-flower-1.0.rst b/framework/docs/source/how-to-upgrade-to-flower-1.0.rst index 4f5afe0ff68a..5f0750ebc60e 100644 --- a/framework/docs/source/how-to-upgrade-to-flower-1.0.rst +++ b/framework/docs/source/how-to-upgrade-to-flower-1.0.rst @@ -31,9 +31,8 @@ Here's how to update an existing installation to Flower 1.0 using either pip or - ``python -m pip install -U flwr`` (when using ``start_server`` and ``start_client``) - ``python -m pip install -U 'flwr[simulation]'`` (when using ``start_simulation``) -- Poetry: update the ``flwr`` dependency in ``pyproject.toml`` and then reinstall (don't - forget to delete ``poetry.lock`` via ``rm poetry.lock`` before running ``poetry - install``). +- Poetry: update the ``flwr`` dependency in ``pyproject.toml`` and then reinstall with + ``python -m poetry install``. - ``flwr = "^1.0.0"`` (when using ``start_server`` and ``start_client``) - ``flwr = { version = "^1.0.0", extras = ["simulation"] }`` (when using diff --git a/framework/e2e/strategies/pyproject.toml b/framework/e2e/strategies/pyproject.toml index 7ae0abdd3a0a..904278d37539 100644 --- a/framework/e2e/strategies/pyproject.toml +++ b/framework/e2e/strategies/pyproject.toml @@ -2,14 +2,16 @@ requires = ["poetry-core>=2.1.3"] build-backend = "poetry.core.masonry.api" -[tool.poetry] +[project] name = "quickstart_tensorflow" version = "0.1.0" description = "Keras Federated Learning Quickstart with Flower" -authors = ["The Flower Authors "] +authors = [{ name = "The Flower Authors", email = "hello@flower.ai" }] +requires-python = ">=3.10,<3.11" +dependencies = ["flwr[simulation]>=1.27.0", "tensorflow-cpu>=2.18.0"] + +[tool.poetry] package-mode = false [tool.poetry.dependencies] -python = ">=3.10,<3.11" flwr = { path = "../../", develop = true, extras = ["simulation"] } -tensorflow-cpu = ">=2.18.0" diff --git a/framework/pyproject.toml b/framework/pyproject.toml index 8eb1098057a4..972bd2baf55f 100644 --- a/framework/pyproject.toml +++ b/framework/pyproject.toml @@ -2,16 +2,14 @@ requires = ["poetry-core>=2.1.3"] build-backend = "poetry.core.masonry.api" -[tool.poetry] +[project] name = "flwr" version = "1.27.0" description = "Flower: A Friendly Federated AI Framework" license = "Apache-2.0" -authors = ["The Flower Authors "] +authors = [{ name = "The Flower Authors", email = "hello@flower.ai" }] readme = "README.md" -homepage = "https://flower.ai" -repository = "https://github.com/adap/flower" -documentation = "https://flower.ai" +requires-python = ">=3.10,<4.0" keywords = [ "Artificial Intelligence", "Federated AI", @@ -25,7 +23,6 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", - "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", "Programming Language :: Python", @@ -44,10 +41,34 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed", ] -packages = [{ include = "flwr", from = "py" }] -exclude = ["py/**/*_test.py"] +dependencies = [ + "numpy>=1.26.0,<3.0.0", + "grpcio>=1.70.0,<2.0.0", + "grpcio-health-checking>=1.70.0,<2.0.0", + "protobuf>=5.28.0,<7.0.0", + "cryptography>=46.0.5,<47.0.0", + "pycryptodome>=3.18.0,<4.0.0", + "iterators>=0.0.2,<0.0.3", + "typer>=0.12.5,<0.21.0", + "tomli>=2.0.1,<3.0.0", + "tomli-w>=1.0.0,<2.0.0", + "pathspec>=0.12.1,<0.13.0", + "rich>=13.5.0,<14.0.0", + "pyyaml>=6.0.2,<7.0.0", + "requests>=2.31.0,<3.0.0", + "click>=8.0.0,<9.0.0", + "SQLAlchemy>=2.0.45,<3.0.0", + "alembic>=1.18.1,<2.0.0", +] + +[project.optional-dependencies] +simulation = [ + "ray==2.51.1; python_version >= '3.10' and python_version < '3.13'", + "ray==2.51.1; python_version == '3.13' and sys_platform != 'win32'", +] +rest = ["starlette>=0.50.0,<0.51.0", "uvicorn[standard]>=0.40.0,<0.41.0"] -[tool.poetry.scripts] +[project.scripts] # `flwr` CLI flwr = "flwr.cli.app:app" # Simulation Engine @@ -59,95 +80,76 @@ flower-superexec = "flwr.supercore.cli:flower_superexec" flwr-serverapp = "flwr.server.serverapp:flwr_serverapp" flwr-clientapp = "flwr.supernode.cli:flwr_clientapp" -[tool.poetry.dependencies] -python = "^3.10" -# Mandatory dependencies -numpy = ">=1.26.0,<3.0.0" -grpcio = "^1.70.0" -grpcio-health-checking = "^1.70.0" -protobuf = ">=5.28.0,<7.0.0" -cryptography = "^46.0.5" -pycryptodome = "^3.18.0" -iterators = "^0.0.2" -typer = ">=0.12.5,<0.21.0" -tomli = "^2.0.1" -tomli-w = "^1.0.0" -pathspec = "^0.12.1" -rich = "^13.5.0" -pyyaml = "^6.0.2" -requests = "^2.31.0" -click = "^8.0.0" -SQLAlchemy = "^2.0.45" -alembic = "^1.18.1" -# Optional dependencies (Simulation Engine) -ray = [ - { version = "==2.51.1", optional = true, python = ">=3.10,<3.13" }, - { version = "==2.51.1", optional = true, python = "==3.13", markers = "sys_platform != 'win32'" }, +[project.urls] +homepage = "https://flower.ai" +repository = "https://github.com/adap/flower" +documentation = "https://flower.ai" + +[dependency-groups] +dev = [ + "types-dataclasses==0.6.6", + "types-protobuf==5.29.1.20250403", + "types-requests==2.31.0.20240125", + "types-setuptools==82.0.0.20260210", + "setuptools==82.0.0", + "clang-format==17.0.6", + "isort==5.13.2", + "black[jupyter]==25.11.0", + "taplo==0.9.3", + "docformatter==1.7.5", + "mypy==1.8.0", + "pylint==3.3.1", + "parameterized==0.9.0", + "pytest==7.4.4", + "pytest-cov==4.1.0", + "pytest-watcher==0.4.3", + "grpcio-tools==1.70.0", + "mypy-protobuf==3.6.0", + "jupyterlab==4.0.12", + "rope==1.13.0", + "semver==3.0.2", + "sphinx==7.4.7", + "sphinx-intl==2.2.0", + "sphinx-click==6.0.0", + "myst-parser==3.0.1", + "sphinx-design==0.6.1", + "sphinx-copybutton==0.5.2", + "sphinxcontrib-mermaid==0.9.2", + "sphinxcontrib-youtube==1.4.1", + "furo==2024.8.6", + "sphinx-reredirects==0.1.5", + "nbsphinx==0.9.5", + "nbstripout==0.6.1", + "ruff==0.14.5", + "sphinx-argparse==0.4.0", + "pipreqs==0.4.13", + "mdformat==1.0.0", + "mdformat-gfm==1.0.0", + "mdformat-frontmatter==2.0.10", + "mdformat-beautysh==1.0.0", + "beautysh==6.4.2", + "twine==6.2.0", + "types-PyYAML>=6.0.2,<7.0.0", + "pyroma==4.2", + "check-wheel-contents==0.6.3", + "GitPython==3.1.32", + "PyGithub==2.1.1", + "licensecheck==2025.1.0", + "pre-commit==3.5.0", + "sphinx-substitution-extensions==2022.2.16", + "sphinxext-opengraph==0.9.1", + "docstrfmt @ git+https://github.com/charlesbvll/docstrfmt.git@patch-2 ; python_version >= '3.10' and python_version < '4.0'", + "docsig==0.64.0", + "paracelsus==0.15.0", + "devtool", ] -# Optional dependencies (REST transport layer) -starlette = { version = "^0.50.0", optional = true } -uvicorn = { version = "^0.40.0", extras = ["standard"], optional = true } -[tool.poetry.extras] -simulation = ["ray"] -rest = ["starlette", "uvicorn"] +[tool.poetry] +packages = [{ include = "flwr", from = "py" }] +exclude = ["py/**/*_test.py"] [tool.poetry.group.dev.dependencies] -types-dataclasses = "==0.6.6" -types-protobuf = "==5.29.1.20250403" -types-requests = "==2.31.0.20240125" -types-setuptools = "==82.0.0.20260210" -setuptools = "==82.0.0" -clang-format = "==17.0.6" -isort = "==5.13.2" -black = { version = "==25.11.0", extras = ["jupyter"] } -taplo = "==0.9.3" -docformatter = "==1.7.5" -mypy = "==1.8.0" -pylint = "==3.3.1" -parameterized = "==0.9.0" -pytest = "==7.4.4" -pytest-cov = "==4.1.0" -pytest-watcher = "==0.4.3" -grpcio-tools = "==1.70.0" -mypy-protobuf = "==3.6.0" -jupyterlab = "==4.0.12" -rope = "==1.13.0" -semver = "==3.0.2" -sphinx = "==7.4.7" -sphinx-intl = "==2.2.0" -sphinx-click = "==6.0.0" -myst-parser = "==3.0.1" -sphinx-design = "==0.6.1" -sphinx-copybutton = "==0.5.2" -sphinxcontrib-mermaid = "==0.9.2" -sphinxcontrib-youtube = "==1.4.1" -furo = "==2024.8.6" -sphinx-reredirects = "==0.1.5" -nbsphinx = "==0.9.5" -nbstripout = "==0.6.1" -ruff = "==0.14.5" -sphinx-argparse = "==0.4.0" -pipreqs = "==0.4.13" -mdformat = "==1.0.0" -mdformat-gfm = "==1.0.0" -mdformat-frontmatter = "==2.0.10" -mdformat-beautysh = "==1.0.0" -beautysh = "==6.4.2" -twine = "==6.2.0" -types-PyYAML = "^6.0.2" -pyroma = "==4.2" -check-wheel-contents = "==0.6.3" -GitPython = "==3.1.32" -PyGithub = "==2.1.1" -licensecheck = "==2025.1.0" -pre-commit = "==3.5.0" -sphinx-substitution-extensions = "2022.02.16" -sphinxext-opengraph = "==0.9.1" -docstrfmt = { git = "https://github.com/charlesbvll/docstrfmt.git", branch = "patch-2", python = ">=3.10,<4.0.0" } -docsig = "==0.64.0" -paracelsus = "==0.15.0" -devtool = [{ path = "./devtool", develop = true }] +devtool = { path = "./devtool", develop = true } [tool.docstrfmt] extend_exclude = [ From f68d3c8bdc8ef4d01ec92edc76baa081cc26df4a Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 08:59:24 +0100 Subject: [PATCH 02/18] Fix formatting --- .../docs/source/contributor-how-to-write-documentation.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/framework/docs/source/contributor-how-to-write-documentation.rst b/framework/docs/source/contributor-how-to-write-documentation.rst index d214ea69678f..755c7ec7401b 100644 --- a/framework/docs/source/contributor-how-to-write-documentation.rst +++ b/framework/docs/source/contributor-how-to-write-documentation.rst @@ -9,10 +9,9 @@ The Flower documentation lives in the ``doc`` directory. The Sphinx-based documentation system supports both reStructuredText (``.rst`` files) and Markdown (``.md`` files). -Note that, in order to build the documentation locally (with ``python -m poetry run -make html``, -like described below), `Pandoc `_ needs to be -installed on the system. +Note that, in order to build the documentation locally (with ``python -m poetry run make +html``, like described below), `Pandoc `_ needs to +be installed on the system. *********************** Edit an existing page From 6157afd5ca5f613842d7505502d4accdfdeef95e Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 10:11:14 +0100 Subject: [PATCH 03/18] Make E2E pyproject less ambiguous --- framework/e2e/strategies/pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/e2e/strategies/pyproject.toml b/framework/e2e/strategies/pyproject.toml index 904278d37539..043a05312ddf 100644 --- a/framework/e2e/strategies/pyproject.toml +++ b/framework/e2e/strategies/pyproject.toml @@ -8,10 +8,11 @@ version = "0.1.0" description = "Keras Federated Learning Quickstart with Flower" authors = [{ name = "The Flower Authors", email = "hello@flower.ai" }] requires-python = ">=3.10,<3.11" -dependencies = ["flwr[simulation]>=1.27.0", "tensorflow-cpu>=2.18.0"] +dependencies = ["tensorflow-cpu>=2.18.0"] [tool.poetry] package-mode = false +# Local editable Flower is used for PR/fork runs; artifact wheel step may replace it in main-repo CI. [tool.poetry.dependencies] flwr = { path = "../../", develop = true, extras = ["simulation"] } From 9a02d2bb1239e57e6ffd12b660eb77f3a92c36bf Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 10:17:12 +0100 Subject: [PATCH 04/18] Add license classifier --- framework/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/pyproject.toml b/framework/pyproject.toml index 972bd2baf55f..14c1e4ab16ca 100644 --- a/framework/pyproject.toml +++ b/framework/pyproject.toml @@ -23,6 +23,7 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", "Programming Language :: Python", From 0e83d22fb2c1686793ea80018dc47888d5eab964 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 10:41:05 +0100 Subject: [PATCH 05/18] Add simulation extra when installing flwr from artifact store --- .github/workflows/framework-e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/framework-e2e.yml b/.github/workflows/framework-e2e.yml index dfa65e02db0f..48468d8b008d 100644 --- a/.github/workflows/framework-e2e.yml +++ b/.github/workflows/framework-e2e.yml @@ -288,7 +288,7 @@ jobs: - name: Install Flower wheel from artifact store if: ${{ needs.changes.outputs.framework == 'true' && github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | - python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + python -m pip install "flwr[simulation] @ https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" - name: Cache Datasets if: ${{ needs.changes.outputs.framework == 'true' }} uses: actions/cache@v4 From 0ffabc47090cd3a573fcb4002078331c4679f95a Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 13:17:24 +0100 Subject: [PATCH 06/18] Remove --with dev --- .devcontainer/post-create.sh | 2 +- .github/workflows/framework-devtools.yml | 2 +- .github/workflows/framework-docs-update-translations.yml | 2 +- .github/workflows/framework-docs.yml | 2 +- .github/workflows/framework-e2e.yml | 2 +- .github/workflows/framework-test.yml | 2 +- .../docs/source/contributor-how-to-set-up-a-virtual-env.rst | 2 +- .../contributor-tutorial-get-started-as-a-contributor.rst | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 18f03ccdef22..f05b4beb3785 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -24,7 +24,7 @@ for f in "${files[@]}"; do comment_taplo "$f" done -sudo poetry install --with dev --extras "simulation" +sudo poetry install --all-extras # Restore taplo lines in "files" for f in "${files[@]}"; do diff --git a/.github/workflows/framework-devtools.yml b/.github/workflows/framework-devtools.yml index 067a2fe40f32..37a9ac41377e 100644 --- a/.github/workflows/framework-devtools.yml +++ b/.github/workflows/framework-devtools.yml @@ -30,6 +30,6 @@ jobs: - name: Install dependencies (mandatory + optional) run: | cd framework - python -m poetry install --with dev + python -m poetry install - name: Lint + Test (isort/black/mypy/pylint/pytest) run: ./framework/dev/test-tool.sh diff --git a/.github/workflows/framework-docs-update-translations.yml b/.github/workflows/framework-docs-update-translations.yml index 664ff50fe9bb..47ee52ae965d 100644 --- a/.github/workflows/framework-docs-update-translations.yml +++ b/.github/workflows/framework-docs-update-translations.yml @@ -31,7 +31,7 @@ jobs: - name: Install dependencies run: | cd framework - python -m poetry install --with dev + python -m poetry install - name: Install pandoc uses: nikeee/setup-pandoc@v1 diff --git a/.github/workflows/framework-docs.yml b/.github/workflows/framework-docs.yml index 02548072d4cf..4c2ba6d5f60a 100644 --- a/.github/workflows/framework-docs.yml +++ b/.github/workflows/framework-docs.yml @@ -43,7 +43,7 @@ jobs: - name: Install Flower Framework run: | cd framework - python -m poetry install --with dev + python -m poetry install - name: Sync required docs build assets from main for release branches if: ${{ startsWith(github.ref, 'refs/heads/release/framework-') || (github.event_name == 'pull_request' && startsWith(github.base_ref, 'release/framework-')) || (github.event_name == 'release' && startsWith(github.ref, 'refs/tags/framework-')) }} run: | diff --git a/.github/workflows/framework-e2e.yml b/.github/workflows/framework-e2e.yml index 48468d8b008d..7d1a30ace924 100644 --- a/.github/workflows/framework-e2e.yml +++ b/.github/workflows/framework-e2e.yml @@ -47,7 +47,7 @@ jobs: - name: Install dependencies run: | cd framework - python -m poetry install --with dev --all-extras + python -m poetry install --all-extras - name: Build wheel run: ./framework/dev/build.sh - name: Test wheel diff --git a/.github/workflows/framework-test.yml b/.github/workflows/framework-test.yml index c877cf8f8df3..f23377d3ec4a 100644 --- a/.github/workflows/framework-test.yml +++ b/.github/workflows/framework-test.yml @@ -65,7 +65,7 @@ jobs: if: ${{ needs.changes.outputs.framework == 'true' || needs.changes.outputs.ex_bench == 'true' }} run: | cd framework - python -m poetry install --with dev --all-extras + python -m poetry install --all-extras - name: Check if protos need recompilation if: ${{ needs.changes.outputs.framework == 'true' }} run: ./framework/dev/check-protos.sh diff --git a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst index a56b708abd0f..ea1acea8940a 100644 --- a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst +++ b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst @@ -57,7 +57,7 @@ with: .. code-block:: shell - python -m poetry install --with dev --all-extras + python -m poetry install --all-extras eval "$(python -m poetry env activate)" If you open a new terminal you can activate the previously created virtual environment diff --git a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst index cf549138075e..334ac54449eb 100644 --- a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst +++ b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst @@ -83,7 +83,7 @@ Create Flower Dev Environment :: (your-env-name) $ cd framework - (your-env-name) $ python -m poetry install --with dev --all-extras + (your-env-name) $ python -m poetry install --all-extras ********************* Convenience Scripts From 264029038e7287b22ad655492b43e57344a2bd26 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 13:39:40 +0100 Subject: [PATCH 07/18] ci(*:skip): Improve CI pipelines --- .devcontainer/post-create.sh | 2 +- .../framework-docs-update-translations.yml | 1 - .github/workflows/framework-e2e.yml | 19 ++++++++++++------- .github/workflows/framework-test.yml | 2 +- .github/workflows/intelligence-docs.yml | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 18f03ccdef22..f05b4beb3785 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -24,7 +24,7 @@ for f in "${files[@]}"; do comment_taplo "$f" done -sudo poetry install --with dev --extras "simulation" +sudo poetry install --all-extras # Restore taplo lines in "files" for f in "${files[@]}"; do diff --git a/.github/workflows/framework-docs-update-translations.yml b/.github/workflows/framework-docs-update-translations.yml index c8ec8dfd9bd1..47ee52ae965d 100644 --- a/.github/workflows/framework-docs-update-translations.yml +++ b/.github/workflows/framework-docs-update-translations.yml @@ -32,7 +32,6 @@ jobs: run: | cd framework python -m poetry install - pip install sphinx==7.4.7 - name: Install pandoc uses: nikeee/setup-pandoc@v1 diff --git a/.github/workflows/framework-e2e.yml b/.github/workflows/framework-e2e.yml index 6a4df392133e..59ab74c7a1d7 100644 --- a/.github/workflows/framework-e2e.yml +++ b/.github/workflows/framework-e2e.yml @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v5 - name: Bootstrap uses: ./.github/actions/bootstrap - - name: Install dependencies (mandatory only) + - name: Install dependencies run: | cd framework python -m poetry install --all-extras @@ -225,7 +225,8 @@ jobs: - name: Install Flower wheel from artifact store if: ${{ needs.changes.outputs.framework == 'true' && github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | - python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + WHEEL_URL="https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" + python -m pip install "flwr[simulation] @ ${WHEEL_URL}" - name: Download dataset if: ${{ needs.changes.outputs.framework == 'true' && matrix.dataset }} run: python -c "${{ matrix.dataset }}" @@ -284,11 +285,12 @@ jobs: - name: Install dependencies if: ${{ needs.changes.outputs.framework == 'true' }} run: | - python -m poetry install + python -m poetry install --only main - name: Install Flower wheel from artifact store if: ${{ needs.changes.outputs.framework == 'true' && github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | - python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + WHEEL_URL="https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" + python -m pip install "flwr[simulation] @ ${WHEEL_URL}" - name: Cache Datasets if: ${{ needs.changes.outputs.framework == 'true' }} uses: actions/cache@v4 @@ -335,7 +337,8 @@ jobs: - name: Install Flower wheel from artifact store if: ${{ needs.changes.outputs.framework == 'true' && github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | - python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + WHEEL_URL="https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" + python -m pip install "flwr[simulation] @ ${WHEEL_URL}" - name: Install project dependencies if: ${{ needs.changes.outputs.framework == 'true' }} working-directory: examples/${{ matrix.example }} @@ -378,7 +381,8 @@ jobs: - name: Install Flower wheel from artifact store if: ${{ needs.changes.outputs.framework == 'true' && github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | - python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + WHEEL_URL="https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" + python -m pip install "flwr[simulation] @ ${WHEEL_URL}" - name: Create project and install it if: ${{ needs.changes.outputs.framework == 'true' }} run: | @@ -424,7 +428,8 @@ jobs: - name: Install Flower wheel from artifact store if: ${{ needs.changes.outputs.framework == 'true' && github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} run: | - python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + WHEEL_URL="https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }}" + python -m pip install "flwr[simulation] @ ${WHEEL_URL}" - name: Create project, build, and install it if: ${{ needs.changes.outputs.framework == 'true' }} run: | diff --git a/.github/workflows/framework-test.yml b/.github/workflows/framework-test.yml index 949dcdf9b3ba..f23377d3ec4a 100644 --- a/.github/workflows/framework-test.yml +++ b/.github/workflows/framework-test.yml @@ -61,7 +61,7 @@ jobs: uses: ./.github/actions/bootstrap with: python-version: ${{ matrix.python }} - - name: Install dependencies (mandatory only) + - name: Install dependencies if: ${{ needs.changes.outputs.framework == 'true' || needs.changes.outputs.ex_bench == 'true' }} run: | cd framework diff --git a/.github/workflows/intelligence-docs.yml b/.github/workflows/intelligence-docs.yml index 292012a44656..579924fddef7 100644 --- a/.github/workflows/intelligence-docs.yml +++ b/.github/workflows/intelligence-docs.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@v5 - name: Bootstrap uses: ./.github/actions/bootstrap - - name: Install dependencies (mandatory only) + - name: Install dependencies run: | cd dev python -m poetry install --all-extras From 91ff810a7aacfb2569fe8591e5957780139bcea0 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 13:49:36 +0100 Subject: [PATCH 08/18] Update docs --- ...contributor-how-to-install-development-versions.rst | 4 ++-- .../source/contributor-how-to-set-up-a-virtual-env.rst | 3 ++- .../source/contributor-how-to-write-documentation.rst | 10 +++++----- framework/docs/source/how-to-upgrade-to-flower-1.0.rst | 6 +++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/framework/docs/source/contributor-how-to-install-development-versions.rst b/framework/docs/source/contributor-how-to-install-development-versions.rst index e2d0fea18fa7..158507eaff6e 100644 --- a/framework/docs/source/contributor-how-to-install-development-versions.rst +++ b/framework/docs/source/contributor-how-to-install-development-versions.rst @@ -10,8 +10,8 @@ Using Poetry (recommended) ========================== Install a ``flwr`` pre-release from PyPI: update the ``flwr`` dependency in -``pyproject.toml`` and then reinstall (don't forget to delete ``poetry.lock`` (``rm -poetry.lock``) before running ``poetry install``). +``pyproject.toml`` and then reinstall (ensure to delete ``poetry.lock`` via ``rm +poetry.lock`` before running ``python -m poetry install``). - ``flwr = { version = "1.0.0a0", allow-prereleases = true }`` (without extras) - ``flwr = { version = "1.0.0a0", allow-prereleases = true, extras = ["simulation"] }`` diff --git a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst index e0dba5187bab..c30baa0f4e17 100644 --- a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst +++ b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst @@ -52,7 +52,8 @@ Activate the virtualenv by running the following command: ************************ The Flower examples are based on `Poetry `_ to manage -dependencies. After installing Poetry you simply create a virtual environment with: +dependencies. After installing Poetry you can create and activate a virtual environment +with: .. code-block:: shell diff --git a/framework/docs/source/contributor-how-to-write-documentation.rst b/framework/docs/source/contributor-how-to-write-documentation.rst index 49eb3a647862..755c7ec7401b 100644 --- a/framework/docs/source/contributor-how-to-write-documentation.rst +++ b/framework/docs/source/contributor-how-to-write-documentation.rst @@ -9,16 +9,16 @@ The Flower documentation lives in the ``doc`` directory. The Sphinx-based documentation system supports both reStructuredText (``.rst`` files) and Markdown (``.md`` files). -Note that, in order to build the documentation locally (with ``poetry run make html``, -like described below), `Pandoc `_ needs to be -installed on the system. +Note that, in order to build the documentation locally (with ``python -m poetry run make +html``, like described below), `Pandoc `_ needs to +be installed on the system. *********************** Edit an existing page *********************** 1. Edit an existing ``.rst`` (or ``.md``) file under ``framework/docs/source/`` -2. Compile the docs: ``cd framework/docs``, then ``poetry run make html`` +2. Compile the docs: ``cd framework/docs``, then ``python -m poetry run make html`` 3. Open ``framework/docs/build/html/index.html`` in the browser to check the result ******************* @@ -28,5 +28,5 @@ installed on the system. 1. Add new ``.rst`` file under ``framework/docs/source/`` 2. Add content to the new ``.rst`` file 3. Link to the new rst from ``index.rst`` -4. Compile the docs: ``cd framework/docs``, then ``poetry run make html`` +4. Compile the docs: ``cd framework/docs``, then ``python -m poetry run make html`` 5. Open ``framework/docs/build/html/index.html`` in the browser to check the result diff --git a/framework/docs/source/how-to-upgrade-to-flower-1.0.rst b/framework/docs/source/how-to-upgrade-to-flower-1.0.rst index 4f5afe0ff68a..92720d728dc0 100644 --- a/framework/docs/source/how-to-upgrade-to-flower-1.0.rst +++ b/framework/docs/source/how-to-upgrade-to-flower-1.0.rst @@ -31,9 +31,9 @@ Here's how to update an existing installation to Flower 1.0 using either pip or - ``python -m pip install -U flwr`` (when using ``start_server`` and ``start_client``) - ``python -m pip install -U 'flwr[simulation]'`` (when using ``start_simulation``) -- Poetry: update the ``flwr`` dependency in ``pyproject.toml`` and then reinstall (don't - forget to delete ``poetry.lock`` via ``rm poetry.lock`` before running ``poetry - install``). +- Poetry: update the ``flwr`` dependency in ``pyproject.toml`` and then reinstall + (ensure to delete ``poetry.lock`` via ``rm poetry.lock`` before running ``python -m + poetry install``). - ``flwr = "^1.0.0"`` (when using ``start_server`` and ``start_client``) - ``flwr = { version = "^1.0.0", extras = ["simulation"] }`` (when using From eb2734adedf199008dab573f545ec1f06a7b2333 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 14:11:27 +0100 Subject: [PATCH 09/18] Revert changes in intelligence-docs.yml --- .github/workflows/intelligence-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/intelligence-docs.yml b/.github/workflows/intelligence-docs.yml index 579924fddef7..292012a44656 100644 --- a/.github/workflows/intelligence-docs.yml +++ b/.github/workflows/intelligence-docs.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@v5 - name: Bootstrap uses: ./.github/actions/bootstrap - - name: Install dependencies + - name: Install dependencies (mandatory only) run: | cd dev python -m poetry install --all-extras From 2f738bbddc70b337f6fe5eb6d0fc389e1f7be448 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sat, 28 Feb 2026 23:36:41 +0100 Subject: [PATCH 10/18] ci(framework): Migrate pyproject.toml to use [project] --- .github/workflows/framework-devtools.yml | 2 +- .github/workflows/framework-docs.yml | 2 +- .github/workflows/framework-e2e.yml | 2 +- .../workflows/framework-release-nightly.yml | 4 +- .github/workflows/framework-test.yml | 2 +- dev/pyproject.toml | 70 +++---- framework/dev/publish-nightly.sh | 3 +- framework/dev/pyproject_meta.py | 127 ++++++++++++ ...ontributor-how-to-set-up-a-virtual-env.rst | 5 +- ...-tutorial-get-started-as-a-contributor.rst | 2 +- framework/e2e/strategies/pyproject.toml | 10 +- framework/pyproject.toml | 190 +++++++++--------- 12 files changed, 276 insertions(+), 143 deletions(-) create mode 100644 framework/dev/pyproject_meta.py diff --git a/.github/workflows/framework-devtools.yml b/.github/workflows/framework-devtools.yml index 37a9ac41377e..067a2fe40f32 100644 --- a/.github/workflows/framework-devtools.yml +++ b/.github/workflows/framework-devtools.yml @@ -30,6 +30,6 @@ jobs: - name: Install dependencies (mandatory + optional) run: | cd framework - python -m poetry install + python -m poetry install --with dev - name: Lint + Test (isort/black/mypy/pylint/pytest) run: ./framework/dev/test-tool.sh diff --git a/.github/workflows/framework-docs.yml b/.github/workflows/framework-docs.yml index 4c2ba6d5f60a..02548072d4cf 100644 --- a/.github/workflows/framework-docs.yml +++ b/.github/workflows/framework-docs.yml @@ -43,7 +43,7 @@ jobs: - name: Install Flower Framework run: | cd framework - python -m poetry install + python -m poetry install --with dev - name: Sync required docs build assets from main for release branches if: ${{ startsWith(github.ref, 'refs/heads/release/framework-') || (github.event_name == 'pull_request' && startsWith(github.base_ref, 'release/framework-')) || (github.event_name == 'release' && startsWith(github.ref, 'refs/tags/framework-')) }} run: | diff --git a/.github/workflows/framework-e2e.yml b/.github/workflows/framework-e2e.yml index 59ab74c7a1d7..678b964b53d8 100644 --- a/.github/workflows/framework-e2e.yml +++ b/.github/workflows/framework-e2e.yml @@ -47,7 +47,7 @@ jobs: - name: Install dependencies run: | cd framework - python -m poetry install --all-extras + python -m poetry install --with dev --all-extras - name: Build wheel run: ./framework/dev/build.sh - name: Test wheel diff --git a/.github/workflows/framework-release-nightly.yml b/.github/workflows/framework-release-nightly.yml index df21ce0f62b2..0dbf5ddd8f48 100644 --- a/.github/workflows/framework-release-nightly.yml +++ b/.github/workflows/framework-release-nightly.yml @@ -36,8 +36,8 @@ jobs: echo "pip-version=${{ steps.bootstrap.outputs.pip-version }}" >> "$GITHUB_OUTPUT" echo "setuptools-version=${{ steps.bootstrap.outputs.setuptools-version }}" >> "$GITHUB_OUTPUT" - NAME=$(poetry version | awk {'print $1'}) - VERSION=$(poetry version -s) + NAME=$(python dev/pyproject_meta.py get-name pyproject.toml) + VERSION=$(python dev/pyproject_meta.py get-version pyproject.toml) python dev/build-docker-image-matrix.py --flwr-version "${VERSION}" --matrix nightly --flwr-package "${NAME}" > matrix.json echo "matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/framework-test.yml b/.github/workflows/framework-test.yml index f23377d3ec4a..c877cf8f8df3 100644 --- a/.github/workflows/framework-test.yml +++ b/.github/workflows/framework-test.yml @@ -65,7 +65,7 @@ jobs: if: ${{ needs.changes.outputs.framework == 'true' || needs.changes.outputs.ex_bench == 'true' }} run: | cd framework - python -m poetry install --all-extras + python -m poetry install --with dev --all-extras - name: Check if protos need recompilation if: ${{ needs.changes.outputs.framework == 'true' }} run: ./framework/dev/check-protos.sh diff --git a/dev/pyproject.toml b/dev/pyproject.toml index 4afcc8df7a45..26aff596ea33 100644 --- a/dev/pyproject.toml +++ b/dev/pyproject.toml @@ -2,45 +2,47 @@ requires = ["poetry-core>=2.1.3"] build-backend = "poetry.core.masonry.api" -[tool.poetry] +[project] name = "devtool" version = "0.0.0" description = "Tools for developers." license = "Apache-2.0" -authors = ["The Flower Authors "] -packages = [{ include = "devtool", from = "./" }] +authors = [{ name = "The Flower Authors", email = "hello@flower.ai" }] +requires-python = ">=3.10,<4.0" +dependencies = [ + "clang-format==17.0.6", + "isort==5.13.2", + "black==25.11.0", + "taplo==0.9.3", + "docformatter==1.7.5", + "rope==1.13.0", + "semver==3.0.2", + "sphinx==7.4.7", + "sphinx-intl==2.2.0", + "sphinx-click==6.0.0", + "myst-parser==3.0.1", + "sphinx-design==0.6.1", + "sphinx-copybutton==0.5.2", + "sphinxcontrib-mermaid==0.9.2", + "sphinxcontrib-youtube==1.4.1", + "furo==2024.8.6", + "sphinx-reredirects==0.1.5", + "nbsphinx==0.9.5", + "nbstripout==0.6.1", + "sphinx-argparse==0.4.0", + "mdformat==1.0.0", + "mdformat-gfm==1.0.0", + "mdformat-frontmatter==2.0.10", + "mdformat-beautysh==1.0.0", + "beautysh==6.4.2", + "GitPython==3.1.32", + "sphinx-substitution-extensions==2022.2.16", + "sphinxext-opengraph==0.9.1", + "docstrfmt @ git+https://github.com/charlesbvll/docstrfmt.git@patch-2 ; python_version >= '3.10' and python_version < '4.0'", +] -[tool.poetry.dependencies] -python = "^3.10" -clang-format = "==17.0.6" -isort = "==5.13.2" -black = { version = "==25.11.0" } -taplo = "==0.9.3" -docformatter = "==1.7.5" -rope = "==1.13.0" -semver = "==3.0.2" -sphinx = "==7.4.7" -sphinx-intl = "==2.2.0" -sphinx-click = "==6.0.0" -myst-parser = "==3.0.1" -sphinx-design = "==0.6.1" -sphinx-copybutton = "==0.5.2" -sphinxcontrib-mermaid = "==0.9.2" -sphinxcontrib-youtube = "==1.4.1" -furo = "==2024.8.6" -sphinx-reredirects = "==0.1.5" -nbsphinx = "==0.9.5" -nbstripout = "==0.6.1" -sphinx-argparse = "==0.4.0" -mdformat = "==1.0.0" -mdformat-gfm = "==1.0.0" -mdformat-frontmatter = "==2.0.10" -mdformat-beautysh = "==1.0.0" -beautysh = "==6.4.2" -GitPython = "==3.1.32" -sphinx-substitution-extensions = "2022.02.16" -sphinxext-opengraph = "==0.9.1" -docstrfmt = { git = "https://github.com/charlesbvll/docstrfmt.git", branch = "patch-2", python = ">=3.10,<4.0.0" } +[tool.poetry] +packages = [{ include = "devtool", from = "./" }] [tool.docstrfmt] extend_exclude = [ diff --git a/framework/dev/publish-nightly.sh b/framework/dev/publish-nightly.sh index 0c03cdda9f49..6f65afcda49d 100755 --- a/framework/dev/publish-nightly.sh +++ b/framework/dev/publish-nightly.sh @@ -26,8 +26,7 @@ cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/../ # "0.1.1.dev20200716" as seen at https://pypi.org/project/flwr-nightly/ if [[ $(git log --since="24 hours ago" --pretty=oneline) ]]; then - sed -i -E "s/^name = \"(.+)\"/name = \"\1-nightly\"/" pyproject.toml - sed -i -E "s/^version = \"(.+)\"/version = \"\1.dev$(date '+%Y%m%d')\"/" pyproject.toml + python dev/pyproject_meta.py set-nightly-name-version pyproject.toml python -m poetry build python -m poetry publish -u __token__ -p $PYPI_TOKEN else diff --git a/framework/dev/pyproject_meta.py b/framework/dev/pyproject_meta.py new file mode 100644 index 000000000000..88f79299af62 --- /dev/null +++ b/framework/dev/pyproject_meta.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +"""Read and update [project] metadata in pyproject.toml.""" + +from __future__ import annotations + +import argparse +import re +import sys +from datetime import datetime +from pathlib import Path + + +class ProjectSectionError(RuntimeError): + """Raised when [project] metadata is missing or malformed.""" + + +def _load_lines(path: Path) -> list[str]: + return path.read_text(encoding="utf-8").splitlines(keepends=True) + + +def _find_project_bounds(lines: list[str]) -> tuple[int, int]: + start = -1 + for idx, line in enumerate(lines): + if line.strip() == "[project]": + start = idx + break + + if start == -1: + raise ProjectSectionError("Missing [project] section in pyproject.toml") + + end = len(lines) + for idx in range(start + 1, len(lines)): + stripped = lines[idx].strip() + if stripped.startswith("[") and stripped.endswith("]"): + end = idx + break + + return start, end + + +def _get_project_value(lines: list[str], key: str) -> str: + start, end = _find_project_bounds(lines) + pattern = re.compile(rf'^\s*{re.escape(key)}\s*=\s*"([^"]+)"') + + for idx in range(start + 1, end): + match = pattern.match(lines[idx]) + if match: + return match.group(1) + + raise ProjectSectionError(f'Missing [project].{key} in pyproject.toml') + + +def _set_project_value(lines: list[str], key: str, value: str) -> None: + start, end = _find_project_bounds(lines) + pattern = re.compile(rf'^(\s*{re.escape(key)}\s*=\s*")([^"]*)(".*)$') + + for idx in range(start + 1, end): + match = pattern.match(lines[idx]) + if match: + lines[idx] = f"{match.group(1)}{value}{match.group(3)}\n" + return + + raise ProjectSectionError(f'Missing [project].{key} in pyproject.toml') + + +def _cmd_get_name(path: Path) -> int: + print(_get_project_value(_load_lines(path), "name")) + return 0 + + +def _cmd_get_version(path: Path) -> int: + print(_get_project_value(_load_lines(path), "version")) + return 0 + + +def _cmd_set_nightly(path: Path) -> int: + lines = _load_lines(path) + + name = _get_project_value(lines, "name") + version = _get_project_value(lines, "version") + + nightly_name = name if name.endswith("-nightly") else f"{name}-nightly" + base_version = version.split(".dev", maxsplit=1)[0] + nightly_version = f"{base_version}.dev{datetime.utcnow().strftime('%Y%m%d')}" + + _set_project_value(lines, "name", nightly_name) + _set_project_value(lines, "version", nightly_version) + + path.write_text("".join(lines), encoding="utf-8") + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest="command", required=True) + + p_get_name = sub.add_parser("get-name", help="Print [project].name") + p_get_name.add_argument("pyproject", type=Path) + + p_get_version = sub.add_parser("get-version", help="Print [project].version") + p_get_version.add_argument("pyproject", type=Path) + + p_set_nightly = sub.add_parser( + "set-nightly-name-version", + help="Set [project].name and [project].version for nightly publishing", + ) + p_set_nightly.add_argument("pyproject", type=Path) + + args = parser.parse_args() + + try: + if args.command == "get-name": + return _cmd_get_name(args.pyproject) + if args.command == "get-version": + return _cmd_get_version(args.pyproject) + if args.command == "set-nightly-name-version": + return _cmd_set_nightly(args.pyproject) + except ProjectSectionError as err: + print(f"Error: {err}", file=sys.stderr) + return 1 + + print(f"Unsupported command: {args.command}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst index c30baa0f4e17..a56b708abd0f 100644 --- a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst +++ b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst @@ -57,14 +57,15 @@ with: .. code-block:: shell - poetry shell + python -m poetry install --with dev --all-extras + eval "$(python -m poetry env activate)" If you open a new terminal you can activate the previously created virtual environment with the following command: .. code-block:: shell - source $(poetry env info --path)/bin/activate + eval "$(python -m poetry env activate)" ************************** Virtualenv with Anaconda diff --git a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst index 334ac54449eb..cf549138075e 100644 --- a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst +++ b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst @@ -83,7 +83,7 @@ Create Flower Dev Environment :: (your-env-name) $ cd framework - (your-env-name) $ python -m poetry install --all-extras + (your-env-name) $ python -m poetry install --with dev --all-extras ********************* Convenience Scripts diff --git a/framework/e2e/strategies/pyproject.toml b/framework/e2e/strategies/pyproject.toml index 7ae0abdd3a0a..904278d37539 100644 --- a/framework/e2e/strategies/pyproject.toml +++ b/framework/e2e/strategies/pyproject.toml @@ -2,14 +2,16 @@ requires = ["poetry-core>=2.1.3"] build-backend = "poetry.core.masonry.api" -[tool.poetry] +[project] name = "quickstart_tensorflow" version = "0.1.0" description = "Keras Federated Learning Quickstart with Flower" -authors = ["The Flower Authors "] +authors = [{ name = "The Flower Authors", email = "hello@flower.ai" }] +requires-python = ">=3.10,<3.11" +dependencies = ["flwr[simulation]>=1.27.0", "tensorflow-cpu>=2.18.0"] + +[tool.poetry] package-mode = false [tool.poetry.dependencies] -python = ">=3.10,<3.11" flwr = { path = "../../", develop = true, extras = ["simulation"] } -tensorflow-cpu = ">=2.18.0" diff --git a/framework/pyproject.toml b/framework/pyproject.toml index 8eb1098057a4..972bd2baf55f 100644 --- a/framework/pyproject.toml +++ b/framework/pyproject.toml @@ -2,16 +2,14 @@ requires = ["poetry-core>=2.1.3"] build-backend = "poetry.core.masonry.api" -[tool.poetry] +[project] name = "flwr" version = "1.27.0" description = "Flower: A Friendly Federated AI Framework" license = "Apache-2.0" -authors = ["The Flower Authors "] +authors = [{ name = "The Flower Authors", email = "hello@flower.ai" }] readme = "README.md" -homepage = "https://flower.ai" -repository = "https://github.com/adap/flower" -documentation = "https://flower.ai" +requires-python = ">=3.10,<4.0" keywords = [ "Artificial Intelligence", "Federated AI", @@ -25,7 +23,6 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", - "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", "Programming Language :: Python", @@ -44,10 +41,34 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed", ] -packages = [{ include = "flwr", from = "py" }] -exclude = ["py/**/*_test.py"] +dependencies = [ + "numpy>=1.26.0,<3.0.0", + "grpcio>=1.70.0,<2.0.0", + "grpcio-health-checking>=1.70.0,<2.0.0", + "protobuf>=5.28.0,<7.0.0", + "cryptography>=46.0.5,<47.0.0", + "pycryptodome>=3.18.0,<4.0.0", + "iterators>=0.0.2,<0.0.3", + "typer>=0.12.5,<0.21.0", + "tomli>=2.0.1,<3.0.0", + "tomli-w>=1.0.0,<2.0.0", + "pathspec>=0.12.1,<0.13.0", + "rich>=13.5.0,<14.0.0", + "pyyaml>=6.0.2,<7.0.0", + "requests>=2.31.0,<3.0.0", + "click>=8.0.0,<9.0.0", + "SQLAlchemy>=2.0.45,<3.0.0", + "alembic>=1.18.1,<2.0.0", +] + +[project.optional-dependencies] +simulation = [ + "ray==2.51.1; python_version >= '3.10' and python_version < '3.13'", + "ray==2.51.1; python_version == '3.13' and sys_platform != 'win32'", +] +rest = ["starlette>=0.50.0,<0.51.0", "uvicorn[standard]>=0.40.0,<0.41.0"] -[tool.poetry.scripts] +[project.scripts] # `flwr` CLI flwr = "flwr.cli.app:app" # Simulation Engine @@ -59,95 +80,76 @@ flower-superexec = "flwr.supercore.cli:flower_superexec" flwr-serverapp = "flwr.server.serverapp:flwr_serverapp" flwr-clientapp = "flwr.supernode.cli:flwr_clientapp" -[tool.poetry.dependencies] -python = "^3.10" -# Mandatory dependencies -numpy = ">=1.26.0,<3.0.0" -grpcio = "^1.70.0" -grpcio-health-checking = "^1.70.0" -protobuf = ">=5.28.0,<7.0.0" -cryptography = "^46.0.5" -pycryptodome = "^3.18.0" -iterators = "^0.0.2" -typer = ">=0.12.5,<0.21.0" -tomli = "^2.0.1" -tomli-w = "^1.0.0" -pathspec = "^0.12.1" -rich = "^13.5.0" -pyyaml = "^6.0.2" -requests = "^2.31.0" -click = "^8.0.0" -SQLAlchemy = "^2.0.45" -alembic = "^1.18.1" -# Optional dependencies (Simulation Engine) -ray = [ - { version = "==2.51.1", optional = true, python = ">=3.10,<3.13" }, - { version = "==2.51.1", optional = true, python = "==3.13", markers = "sys_platform != 'win32'" }, +[project.urls] +homepage = "https://flower.ai" +repository = "https://github.com/adap/flower" +documentation = "https://flower.ai" + +[dependency-groups] +dev = [ + "types-dataclasses==0.6.6", + "types-protobuf==5.29.1.20250403", + "types-requests==2.31.0.20240125", + "types-setuptools==82.0.0.20260210", + "setuptools==82.0.0", + "clang-format==17.0.6", + "isort==5.13.2", + "black[jupyter]==25.11.0", + "taplo==0.9.3", + "docformatter==1.7.5", + "mypy==1.8.0", + "pylint==3.3.1", + "parameterized==0.9.0", + "pytest==7.4.4", + "pytest-cov==4.1.0", + "pytest-watcher==0.4.3", + "grpcio-tools==1.70.0", + "mypy-protobuf==3.6.0", + "jupyterlab==4.0.12", + "rope==1.13.0", + "semver==3.0.2", + "sphinx==7.4.7", + "sphinx-intl==2.2.0", + "sphinx-click==6.0.0", + "myst-parser==3.0.1", + "sphinx-design==0.6.1", + "sphinx-copybutton==0.5.2", + "sphinxcontrib-mermaid==0.9.2", + "sphinxcontrib-youtube==1.4.1", + "furo==2024.8.6", + "sphinx-reredirects==0.1.5", + "nbsphinx==0.9.5", + "nbstripout==0.6.1", + "ruff==0.14.5", + "sphinx-argparse==0.4.0", + "pipreqs==0.4.13", + "mdformat==1.0.0", + "mdformat-gfm==1.0.0", + "mdformat-frontmatter==2.0.10", + "mdformat-beautysh==1.0.0", + "beautysh==6.4.2", + "twine==6.2.0", + "types-PyYAML>=6.0.2,<7.0.0", + "pyroma==4.2", + "check-wheel-contents==0.6.3", + "GitPython==3.1.32", + "PyGithub==2.1.1", + "licensecheck==2025.1.0", + "pre-commit==3.5.0", + "sphinx-substitution-extensions==2022.2.16", + "sphinxext-opengraph==0.9.1", + "docstrfmt @ git+https://github.com/charlesbvll/docstrfmt.git@patch-2 ; python_version >= '3.10' and python_version < '4.0'", + "docsig==0.64.0", + "paracelsus==0.15.0", + "devtool", ] -# Optional dependencies (REST transport layer) -starlette = { version = "^0.50.0", optional = true } -uvicorn = { version = "^0.40.0", extras = ["standard"], optional = true } -[tool.poetry.extras] -simulation = ["ray"] -rest = ["starlette", "uvicorn"] +[tool.poetry] +packages = [{ include = "flwr", from = "py" }] +exclude = ["py/**/*_test.py"] [tool.poetry.group.dev.dependencies] -types-dataclasses = "==0.6.6" -types-protobuf = "==5.29.1.20250403" -types-requests = "==2.31.0.20240125" -types-setuptools = "==82.0.0.20260210" -setuptools = "==82.0.0" -clang-format = "==17.0.6" -isort = "==5.13.2" -black = { version = "==25.11.0", extras = ["jupyter"] } -taplo = "==0.9.3" -docformatter = "==1.7.5" -mypy = "==1.8.0" -pylint = "==3.3.1" -parameterized = "==0.9.0" -pytest = "==7.4.4" -pytest-cov = "==4.1.0" -pytest-watcher = "==0.4.3" -grpcio-tools = "==1.70.0" -mypy-protobuf = "==3.6.0" -jupyterlab = "==4.0.12" -rope = "==1.13.0" -semver = "==3.0.2" -sphinx = "==7.4.7" -sphinx-intl = "==2.2.0" -sphinx-click = "==6.0.0" -myst-parser = "==3.0.1" -sphinx-design = "==0.6.1" -sphinx-copybutton = "==0.5.2" -sphinxcontrib-mermaid = "==0.9.2" -sphinxcontrib-youtube = "==1.4.1" -furo = "==2024.8.6" -sphinx-reredirects = "==0.1.5" -nbsphinx = "==0.9.5" -nbstripout = "==0.6.1" -ruff = "==0.14.5" -sphinx-argparse = "==0.4.0" -pipreqs = "==0.4.13" -mdformat = "==1.0.0" -mdformat-gfm = "==1.0.0" -mdformat-frontmatter = "==2.0.10" -mdformat-beautysh = "==1.0.0" -beautysh = "==6.4.2" -twine = "==6.2.0" -types-PyYAML = "^6.0.2" -pyroma = "==4.2" -check-wheel-contents = "==0.6.3" -GitPython = "==3.1.32" -PyGithub = "==2.1.1" -licensecheck = "==2025.1.0" -pre-commit = "==3.5.0" -sphinx-substitution-extensions = "2022.02.16" -sphinxext-opengraph = "==0.9.1" -docstrfmt = { git = "https://github.com/charlesbvll/docstrfmt.git", branch = "patch-2", python = ">=3.10,<4.0.0" } -docsig = "==0.64.0" -paracelsus = "==0.15.0" -devtool = [{ path = "./devtool", develop = true }] +devtool = { path = "./devtool", develop = true } [tool.docstrfmt] extend_exclude = [ From e013b9ab7c8485bfe9dfd78a398694637589e197 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 10:11:14 +0100 Subject: [PATCH 11/18] Make E2E pyproject less ambiguous --- framework/e2e/strategies/pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/e2e/strategies/pyproject.toml b/framework/e2e/strategies/pyproject.toml index 904278d37539..043a05312ddf 100644 --- a/framework/e2e/strategies/pyproject.toml +++ b/framework/e2e/strategies/pyproject.toml @@ -8,10 +8,11 @@ version = "0.1.0" description = "Keras Federated Learning Quickstart with Flower" authors = [{ name = "The Flower Authors", email = "hello@flower.ai" }] requires-python = ">=3.10,<3.11" -dependencies = ["flwr[simulation]>=1.27.0", "tensorflow-cpu>=2.18.0"] +dependencies = ["tensorflow-cpu>=2.18.0"] [tool.poetry] package-mode = false +# Local editable Flower is used for PR/fork runs; artifact wheel step may replace it in main-repo CI. [tool.poetry.dependencies] flwr = { path = "../../", develop = true, extras = ["simulation"] } From 8815e69decf3bfd70760061e80bac7d63c420b17 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 10:17:12 +0100 Subject: [PATCH 12/18] Add license classifier --- framework/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/pyproject.toml b/framework/pyproject.toml index 972bd2baf55f..14c1e4ab16ca 100644 --- a/framework/pyproject.toml +++ b/framework/pyproject.toml @@ -23,6 +23,7 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", "Programming Language :: Python", From 0130843024af570ad1d1d4504e134cf33dd965f0 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 13:17:24 +0100 Subject: [PATCH 13/18] Remove --with dev --- .github/workflows/framework-devtools.yml | 2 +- .github/workflows/framework-docs.yml | 2 +- .github/workflows/framework-e2e.yml | 2 +- .github/workflows/framework-test.yml | 2 +- .../docs/source/contributor-how-to-set-up-a-virtual-env.rst | 2 +- .../contributor-tutorial-get-started-as-a-contributor.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/framework-devtools.yml b/.github/workflows/framework-devtools.yml index 067a2fe40f32..37a9ac41377e 100644 --- a/.github/workflows/framework-devtools.yml +++ b/.github/workflows/framework-devtools.yml @@ -30,6 +30,6 @@ jobs: - name: Install dependencies (mandatory + optional) run: | cd framework - python -m poetry install --with dev + python -m poetry install - name: Lint + Test (isort/black/mypy/pylint/pytest) run: ./framework/dev/test-tool.sh diff --git a/.github/workflows/framework-docs.yml b/.github/workflows/framework-docs.yml index 02548072d4cf..4c2ba6d5f60a 100644 --- a/.github/workflows/framework-docs.yml +++ b/.github/workflows/framework-docs.yml @@ -43,7 +43,7 @@ jobs: - name: Install Flower Framework run: | cd framework - python -m poetry install --with dev + python -m poetry install - name: Sync required docs build assets from main for release branches if: ${{ startsWith(github.ref, 'refs/heads/release/framework-') || (github.event_name == 'pull_request' && startsWith(github.base_ref, 'release/framework-')) || (github.event_name == 'release' && startsWith(github.ref, 'refs/tags/framework-')) }} run: | diff --git a/.github/workflows/framework-e2e.yml b/.github/workflows/framework-e2e.yml index 678b964b53d8..59ab74c7a1d7 100644 --- a/.github/workflows/framework-e2e.yml +++ b/.github/workflows/framework-e2e.yml @@ -47,7 +47,7 @@ jobs: - name: Install dependencies run: | cd framework - python -m poetry install --with dev --all-extras + python -m poetry install --all-extras - name: Build wheel run: ./framework/dev/build.sh - name: Test wheel diff --git a/.github/workflows/framework-test.yml b/.github/workflows/framework-test.yml index c877cf8f8df3..f23377d3ec4a 100644 --- a/.github/workflows/framework-test.yml +++ b/.github/workflows/framework-test.yml @@ -65,7 +65,7 @@ jobs: if: ${{ needs.changes.outputs.framework == 'true' || needs.changes.outputs.ex_bench == 'true' }} run: | cd framework - python -m poetry install --with dev --all-extras + python -m poetry install --all-extras - name: Check if protos need recompilation if: ${{ needs.changes.outputs.framework == 'true' }} run: ./framework/dev/check-protos.sh diff --git a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst index a56b708abd0f..ea1acea8940a 100644 --- a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst +++ b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst @@ -57,7 +57,7 @@ with: .. code-block:: shell - python -m poetry install --with dev --all-extras + python -m poetry install --all-extras eval "$(python -m poetry env activate)" If you open a new terminal you can activate the previously created virtual environment diff --git a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst index cf549138075e..334ac54449eb 100644 --- a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst +++ b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst @@ -83,7 +83,7 @@ Create Flower Dev Environment :: (your-env-name) $ cd framework - (your-env-name) $ python -m poetry install --with dev --all-extras + (your-env-name) $ python -m poetry install --all-extras ********************* Convenience Scripts From ff24e2d68ee699dab28383f6b92151e1ce34e9de Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sat, 28 Feb 2026 23:36:41 +0100 Subject: [PATCH 14/18] ci(framework): Migrate pyproject.toml to use [project] --- .github/workflows/framework-devtools.yml | 2 +- .github/workflows/framework-docs.yml | 2 +- .github/workflows/framework-e2e.yml | 2 +- .github/workflows/framework-test.yml | 2 +- .../contributor-tutorial-get-started-as-a-contributor.rst | 2 +- framework/pyproject.toml | 1 - 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/framework-devtools.yml b/.github/workflows/framework-devtools.yml index 37a9ac41377e..067a2fe40f32 100644 --- a/.github/workflows/framework-devtools.yml +++ b/.github/workflows/framework-devtools.yml @@ -30,6 +30,6 @@ jobs: - name: Install dependencies (mandatory + optional) run: | cd framework - python -m poetry install + python -m poetry install --with dev - name: Lint + Test (isort/black/mypy/pylint/pytest) run: ./framework/dev/test-tool.sh diff --git a/.github/workflows/framework-docs.yml b/.github/workflows/framework-docs.yml index 4c2ba6d5f60a..02548072d4cf 100644 --- a/.github/workflows/framework-docs.yml +++ b/.github/workflows/framework-docs.yml @@ -43,7 +43,7 @@ jobs: - name: Install Flower Framework run: | cd framework - python -m poetry install + python -m poetry install --with dev - name: Sync required docs build assets from main for release branches if: ${{ startsWith(github.ref, 'refs/heads/release/framework-') || (github.event_name == 'pull_request' && startsWith(github.base_ref, 'release/framework-')) || (github.event_name == 'release' && startsWith(github.ref, 'refs/tags/framework-')) }} run: | diff --git a/.github/workflows/framework-e2e.yml b/.github/workflows/framework-e2e.yml index 59ab74c7a1d7..678b964b53d8 100644 --- a/.github/workflows/framework-e2e.yml +++ b/.github/workflows/framework-e2e.yml @@ -47,7 +47,7 @@ jobs: - name: Install dependencies run: | cd framework - python -m poetry install --all-extras + python -m poetry install --with dev --all-extras - name: Build wheel run: ./framework/dev/build.sh - name: Test wheel diff --git a/.github/workflows/framework-test.yml b/.github/workflows/framework-test.yml index f23377d3ec4a..c877cf8f8df3 100644 --- a/.github/workflows/framework-test.yml +++ b/.github/workflows/framework-test.yml @@ -65,7 +65,7 @@ jobs: if: ${{ needs.changes.outputs.framework == 'true' || needs.changes.outputs.ex_bench == 'true' }} run: | cd framework - python -m poetry install --all-extras + python -m poetry install --with dev --all-extras - name: Check if protos need recompilation if: ${{ needs.changes.outputs.framework == 'true' }} run: ./framework/dev/check-protos.sh diff --git a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst index 334ac54449eb..cf549138075e 100644 --- a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst +++ b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst @@ -83,7 +83,7 @@ Create Flower Dev Environment :: (your-env-name) $ cd framework - (your-env-name) $ python -m poetry install --all-extras + (your-env-name) $ python -m poetry install --with dev --all-extras ********************* Convenience Scripts diff --git a/framework/pyproject.toml b/framework/pyproject.toml index 14c1e4ab16ca..972bd2baf55f 100644 --- a/framework/pyproject.toml +++ b/framework/pyproject.toml @@ -23,7 +23,6 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", - "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", "Programming Language :: Python", From 8d067fe7a1badd2286cff7ddeb7ccec6894c8136 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 10:17:12 +0100 Subject: [PATCH 15/18] Add license classifier --- framework/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/pyproject.toml b/framework/pyproject.toml index 972bd2baf55f..14c1e4ab16ca 100644 --- a/framework/pyproject.toml +++ b/framework/pyproject.toml @@ -23,6 +23,7 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", "Programming Language :: Python", From e24eb825f1b628ffd35d6783d2c0cfb41581d8f5 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 1 Mar 2026 13:17:24 +0100 Subject: [PATCH 16/18] Remove --with dev --- .github/workflows/framework-devtools.yml | 2 +- .github/workflows/framework-docs.yml | 2 +- .github/workflows/framework-e2e.yml | 2 +- .github/workflows/framework-test.yml | 2 +- .../contributor-tutorial-get-started-as-a-contributor.rst | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/framework-devtools.yml b/.github/workflows/framework-devtools.yml index 067a2fe40f32..37a9ac41377e 100644 --- a/.github/workflows/framework-devtools.yml +++ b/.github/workflows/framework-devtools.yml @@ -30,6 +30,6 @@ jobs: - name: Install dependencies (mandatory + optional) run: | cd framework - python -m poetry install --with dev + python -m poetry install - name: Lint + Test (isort/black/mypy/pylint/pytest) run: ./framework/dev/test-tool.sh diff --git a/.github/workflows/framework-docs.yml b/.github/workflows/framework-docs.yml index 02548072d4cf..4c2ba6d5f60a 100644 --- a/.github/workflows/framework-docs.yml +++ b/.github/workflows/framework-docs.yml @@ -43,7 +43,7 @@ jobs: - name: Install Flower Framework run: | cd framework - python -m poetry install --with dev + python -m poetry install - name: Sync required docs build assets from main for release branches if: ${{ startsWith(github.ref, 'refs/heads/release/framework-') || (github.event_name == 'pull_request' && startsWith(github.base_ref, 'release/framework-')) || (github.event_name == 'release' && startsWith(github.ref, 'refs/tags/framework-')) }} run: | diff --git a/.github/workflows/framework-e2e.yml b/.github/workflows/framework-e2e.yml index 678b964b53d8..59ab74c7a1d7 100644 --- a/.github/workflows/framework-e2e.yml +++ b/.github/workflows/framework-e2e.yml @@ -47,7 +47,7 @@ jobs: - name: Install dependencies run: | cd framework - python -m poetry install --with dev --all-extras + python -m poetry install --all-extras - name: Build wheel run: ./framework/dev/build.sh - name: Test wheel diff --git a/.github/workflows/framework-test.yml b/.github/workflows/framework-test.yml index c877cf8f8df3..f23377d3ec4a 100644 --- a/.github/workflows/framework-test.yml +++ b/.github/workflows/framework-test.yml @@ -65,7 +65,7 @@ jobs: if: ${{ needs.changes.outputs.framework == 'true' || needs.changes.outputs.ex_bench == 'true' }} run: | cd framework - python -m poetry install --with dev --all-extras + python -m poetry install --all-extras - name: Check if protos need recompilation if: ${{ needs.changes.outputs.framework == 'true' }} run: ./framework/dev/check-protos.sh diff --git a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst index cf549138075e..334ac54449eb 100644 --- a/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst +++ b/framework/docs/source/contributor-tutorial-get-started-as-a-contributor.rst @@ -83,7 +83,7 @@ Create Flower Dev Environment :: (your-env-name) $ cd framework - (your-env-name) $ python -m poetry install --with dev --all-extras + (your-env-name) $ python -m poetry install --all-extras ********************* Convenience Scripts From 1cbe331087a6dd799fc424ea91fc4f27496cc94c Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Mon, 2 Mar 2026 22:25:24 +0100 Subject: [PATCH 17/18] Move pyproject_meta into devtool --- .github/workflows/framework-release-nightly.yml | 4 ++-- {framework/dev => dev/devtool}/pyproject_meta.py | 15 ++++++++++++++- framework/dev/publish-nightly.sh | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) rename {framework/dev => dev/devtool}/pyproject_meta.py (84%) diff --git a/.github/workflows/framework-release-nightly.yml b/.github/workflows/framework-release-nightly.yml index 0dbf5ddd8f48..63e2199fb980 100644 --- a/.github/workflows/framework-release-nightly.yml +++ b/.github/workflows/framework-release-nightly.yml @@ -36,8 +36,8 @@ jobs: echo "pip-version=${{ steps.bootstrap.outputs.pip-version }}" >> "$GITHUB_OUTPUT" echo "setuptools-version=${{ steps.bootstrap.outputs.setuptools-version }}" >> "$GITHUB_OUTPUT" - NAME=$(python dev/pyproject_meta.py get-name pyproject.toml) - VERSION=$(python dev/pyproject_meta.py get-version pyproject.toml) + NAME=$(PYTHONPATH=../dev python -m devtool.pyproject_meta get-name pyproject.toml) + VERSION=$(PYTHONPATH=../dev python -m devtool.pyproject_meta get-version pyproject.toml) python dev/build-docker-image-matrix.py --flwr-version "${VERSION}" --matrix nightly --flwr-package "${NAME}" > matrix.json echo "matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT diff --git a/framework/dev/pyproject_meta.py b/dev/devtool/pyproject_meta.py similarity index 84% rename from framework/dev/pyproject_meta.py rename to dev/devtool/pyproject_meta.py index 88f79299af62..91ec20a92606 100644 --- a/framework/dev/pyproject_meta.py +++ b/dev/devtool/pyproject_meta.py @@ -1,4 +1,17 @@ -#!/usr/bin/env python3 +# Copyright 2026 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== """Read and update [project] metadata in pyproject.toml.""" from __future__ import annotations diff --git a/framework/dev/publish-nightly.sh b/framework/dev/publish-nightly.sh index 6f65afcda49d..8196bffc5238 100755 --- a/framework/dev/publish-nightly.sh +++ b/framework/dev/publish-nightly.sh @@ -26,7 +26,7 @@ cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/../ # "0.1.1.dev20200716" as seen at https://pypi.org/project/flwr-nightly/ if [[ $(git log --since="24 hours ago" --pretty=oneline) ]]; then - python dev/pyproject_meta.py set-nightly-name-version pyproject.toml + PYTHONPATH=../dev python -m devtool.pyproject_meta set-nightly-name-version pyproject.toml python -m poetry build python -m poetry publish -u __token__ -p $PYPI_TOKEN else From 5e86859d82f8b17bfdecf71b612e42a89982c5f8 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Tue, 3 Mar 2026 14:23:09 +0100 Subject: [PATCH 18/18] Delete project_meta Python helper --- .../workflows/framework-release-nightly.yml | 4 +- dev/devtool/pyproject_meta.py | 140 ------------------ framework/dev/publish-nightly.sh | 3 +- ...ontributor-how-to-set-up-a-virtual-env.rst | 5 +- 4 files changed, 6 insertions(+), 146 deletions(-) delete mode 100644 dev/devtool/pyproject_meta.py diff --git a/.github/workflows/framework-release-nightly.yml b/.github/workflows/framework-release-nightly.yml index 63e2199fb980..df21ce0f62b2 100644 --- a/.github/workflows/framework-release-nightly.yml +++ b/.github/workflows/framework-release-nightly.yml @@ -36,8 +36,8 @@ jobs: echo "pip-version=${{ steps.bootstrap.outputs.pip-version }}" >> "$GITHUB_OUTPUT" echo "setuptools-version=${{ steps.bootstrap.outputs.setuptools-version }}" >> "$GITHUB_OUTPUT" - NAME=$(PYTHONPATH=../dev python -m devtool.pyproject_meta get-name pyproject.toml) - VERSION=$(PYTHONPATH=../dev python -m devtool.pyproject_meta get-version pyproject.toml) + NAME=$(poetry version | awk {'print $1'}) + VERSION=$(poetry version -s) python dev/build-docker-image-matrix.py --flwr-version "${VERSION}" --matrix nightly --flwr-package "${NAME}" > matrix.json echo "matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT diff --git a/dev/devtool/pyproject_meta.py b/dev/devtool/pyproject_meta.py deleted file mode 100644 index 91ec20a92606..000000000000 --- a/dev/devtool/pyproject_meta.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright 2026 Flower Labs GmbH. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== -"""Read and update [project] metadata in pyproject.toml.""" - -from __future__ import annotations - -import argparse -import re -import sys -from datetime import datetime -from pathlib import Path - - -class ProjectSectionError(RuntimeError): - """Raised when [project] metadata is missing or malformed.""" - - -def _load_lines(path: Path) -> list[str]: - return path.read_text(encoding="utf-8").splitlines(keepends=True) - - -def _find_project_bounds(lines: list[str]) -> tuple[int, int]: - start = -1 - for idx, line in enumerate(lines): - if line.strip() == "[project]": - start = idx - break - - if start == -1: - raise ProjectSectionError("Missing [project] section in pyproject.toml") - - end = len(lines) - for idx in range(start + 1, len(lines)): - stripped = lines[idx].strip() - if stripped.startswith("[") and stripped.endswith("]"): - end = idx - break - - return start, end - - -def _get_project_value(lines: list[str], key: str) -> str: - start, end = _find_project_bounds(lines) - pattern = re.compile(rf'^\s*{re.escape(key)}\s*=\s*"([^"]+)"') - - for idx in range(start + 1, end): - match = pattern.match(lines[idx]) - if match: - return match.group(1) - - raise ProjectSectionError(f'Missing [project].{key} in pyproject.toml') - - -def _set_project_value(lines: list[str], key: str, value: str) -> None: - start, end = _find_project_bounds(lines) - pattern = re.compile(rf'^(\s*{re.escape(key)}\s*=\s*")([^"]*)(".*)$') - - for idx in range(start + 1, end): - match = pattern.match(lines[idx]) - if match: - lines[idx] = f"{match.group(1)}{value}{match.group(3)}\n" - return - - raise ProjectSectionError(f'Missing [project].{key} in pyproject.toml') - - -def _cmd_get_name(path: Path) -> int: - print(_get_project_value(_load_lines(path), "name")) - return 0 - - -def _cmd_get_version(path: Path) -> int: - print(_get_project_value(_load_lines(path), "version")) - return 0 - - -def _cmd_set_nightly(path: Path) -> int: - lines = _load_lines(path) - - name = _get_project_value(lines, "name") - version = _get_project_value(lines, "version") - - nightly_name = name if name.endswith("-nightly") else f"{name}-nightly" - base_version = version.split(".dev", maxsplit=1)[0] - nightly_version = f"{base_version}.dev{datetime.utcnow().strftime('%Y%m%d')}" - - _set_project_value(lines, "name", nightly_name) - _set_project_value(lines, "version", nightly_version) - - path.write_text("".join(lines), encoding="utf-8") - return 0 - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - sub = parser.add_subparsers(dest="command", required=True) - - p_get_name = sub.add_parser("get-name", help="Print [project].name") - p_get_name.add_argument("pyproject", type=Path) - - p_get_version = sub.add_parser("get-version", help="Print [project].version") - p_get_version.add_argument("pyproject", type=Path) - - p_set_nightly = sub.add_parser( - "set-nightly-name-version", - help="Set [project].name and [project].version for nightly publishing", - ) - p_set_nightly.add_argument("pyproject", type=Path) - - args = parser.parse_args() - - try: - if args.command == "get-name": - return _cmd_get_name(args.pyproject) - if args.command == "get-version": - return _cmd_get_version(args.pyproject) - if args.command == "set-nightly-name-version": - return _cmd_set_nightly(args.pyproject) - except ProjectSectionError as err: - print(f"Error: {err}", file=sys.stderr) - return 1 - - print(f"Unsupported command: {args.command}", file=sys.stderr) - return 1 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/framework/dev/publish-nightly.sh b/framework/dev/publish-nightly.sh index 8196bffc5238..0c03cdda9f49 100755 --- a/framework/dev/publish-nightly.sh +++ b/framework/dev/publish-nightly.sh @@ -26,7 +26,8 @@ cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/../ # "0.1.1.dev20200716" as seen at https://pypi.org/project/flwr-nightly/ if [[ $(git log --since="24 hours ago" --pretty=oneline) ]]; then - PYTHONPATH=../dev python -m devtool.pyproject_meta set-nightly-name-version pyproject.toml + sed -i -E "s/^name = \"(.+)\"/name = \"\1-nightly\"/" pyproject.toml + sed -i -E "s/^version = \"(.+)\"/version = \"\1.dev$(date '+%Y%m%d')\"/" pyproject.toml python -m poetry build python -m poetry publish -u __token__ -p $PYPI_TOKEN else diff --git a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst index ea1acea8940a..c30baa0f4e17 100644 --- a/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst +++ b/framework/docs/source/contributor-how-to-set-up-a-virtual-env.rst @@ -57,15 +57,14 @@ with: .. code-block:: shell - python -m poetry install --all-extras - eval "$(python -m poetry env activate)" + poetry shell If you open a new terminal you can activate the previously created virtual environment with the following command: .. code-block:: shell - eval "$(python -m poetry env activate)" + source $(poetry env info --path)/bin/activate ************************** Virtualenv with Anaconda