diff --git a/.github/workflows/build-deploy-non-framework-docs.yml b/.github/workflows/build-deploy-non-framework-docs.yml
index 16c645df36b5..5351d4fb7c8e 100644
--- a/.github/workflows/build-deploy-non-framework-docs.yml
+++ b/.github/workflows/build-deploy-non-framework-docs.yml
@@ -35,11 +35,11 @@ jobs:
python -m poetry install
python -m pip install -e ../datasets
- name: Update HTML theme options
- run: python dev/update-html-themes.py
+ run: python -m devtool.update_html_themes
- name: Build baselines docs
run: ./dev/build-baseline-docs.sh
- name: Build examples docs
- run: python dev/build-example-docs.py
+ run: python -m devtool.build_example_docs
- name: Build datasets docs
run: ./datasets/dev/build-flwr-datasets-docs.sh
- name: Deploy docs
diff --git a/.github/workflows/datasets-test.yml b/.github/workflows/datasets-test.yml
index cbdb5cae2e04..c433e6d76fce 100644
--- a/.github/workflows/datasets-test.yml
+++ b/.github/workflows/datasets-test.yml
@@ -6,12 +6,14 @@ on:
- main
paths:
- "datasets/**"
+ - "dev/devtool/**"
- ".github/workflows/datasets-*.yml"
pull_request:
branches:
- main
paths:
- "datasets/**"
+ - "dev/devtool/**"
- ".github/workflows/datasets-*.yml"
concurrency:
diff --git a/.github/workflows/framework-test.yml b/.github/workflows/framework-test.yml
index f23377d3ec4a..5d3691051788 100644
--- a/.github/workflows/framework-test.yml
+++ b/.github/workflows/framework-test.yml
@@ -33,6 +33,7 @@ jobs:
filters: |
framework:
- 'framework/**/*'
+ - 'dev/devtool/**'
- '.github/workflows/framework-test.yml'
ex_bench:
- 'examples/**/*'
diff --git a/.github/workflows/repo-check-pr-title.yml b/.github/workflows/repo-check-pr-title.yml
index 67a0878318b1..4d357bd977c9 100644
--- a/.github/workflows/repo-check-pr-title.yml
+++ b/.github/workflows/repo-check-pr-title.yml
@@ -24,4 +24,4 @@ jobs:
- name: Check PR title format
env:
PR_TITLE: ${{ github.event.pull_request.title }}
- run: python ./dev/check_pr_title.py "$PR_TITLE"
+ run: PYTHONPATH=dev python -m devtool.check_pr_title "$PR_TITLE"
diff --git a/dev/changelog_config.toml b/dev/changelog_config.toml
index 7a4401f3ecef..74756b3ff873 100644
--- a/dev/changelog_config.toml
+++ b/dev/changelog_config.toml
@@ -1,4 +1,4 @@
-# Defines the PR title format to be used in the `check_pr_title.py` script
+# Defines the PR title format to be used in the `devtool.check_pr_title` script
# and in the `update_changelog.py` script
type = ["ci", "docs", "feat", "fix", "refactor", "break"]
diff --git a/dev/build-example-docs.py b/dev/devtool/build_example_docs.py
similarity index 95%
rename from dev/build-example-docs.py
rename to dev/devtool/build_example_docs.py
index ad40db7d1465..dcfb9660bc35 100644
--- a/dev/build-example-docs.py
+++ b/dev/devtool/build_example_docs.py
@@ -20,8 +20,8 @@
import subprocess
from pathlib import Path
-ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
-INDEX = os.path.join(ROOT, "examples", "docs", "source", "index.rst")
+ROOT = Path(__file__).resolve().parents[2]
+INDEX = ROOT / "examples" / "docs" / "source" / "index.rst"
initial_text = """
Flower Examples Documentation
@@ -179,14 +179,14 @@ def _copy_markdown_files(example):
if file.endswith(".md"):
src = os.path.join(example, file)
dest = os.path.join(
- ROOT, "examples", "docs", "source", os.path.basename(example) + ".md"
+ str(ROOT), "examples", "docs", "source", os.path.basename(example) + ".md"
)
shutil.copyfile(src, dest)
def _add_gh_button(example):
gh_text = f'[
](https://github.com/adap/flower/blob/main/examples/{example})'
- readme_file = os.path.join(ROOT, "examples", "docs", "source", example + ".md")
+ readme_file = os.path.join(str(ROOT), "examples", "docs", "source", example + ".md")
with open(readme_file, "r+") as f:
content = f.read()
if gh_text not in content:
@@ -200,7 +200,7 @@ def _add_gh_button(example):
def _copy_images(example):
static_dir = os.path.join(example, "_static")
- dest_dir = os.path.join(ROOT, "examples", "docs", "source", "_static")
+ dest_dir = os.path.join(str(ROOT), "examples", "docs", "source", "_static")
if os.path.isdir(static_dir):
for file in os.listdir(static_dir):
if file.endswith((".jpg", ".png", ".jpeg")):
@@ -220,10 +220,10 @@ def _add_all_entries():
def _main():
- if os.path.exists(INDEX):
- os.remove(INDEX)
+ if INDEX.exists():
+ INDEX.unlink()
- with open(INDEX, "w") as index_file:
+ with INDEX.open("w", encoding="utf-8") as index_file:
index_file.write(initial_text)
examples_dir = os.path.join(ROOT, "examples")
@@ -238,7 +238,7 @@ def _main():
if not _add_table_entry(example_path, "advanced", "advanced"):
_add_table_entry(example_path, "", "other")
- with open(INDEX, "a") as index_file:
+ with INDEX.open("a", encoding="utf-8") as index_file:
index_file.write(categories["quickstart"]["table"])
index_file.write("\nAdvanced Examples\n-----------------\n")
diff --git a/dev/check_pr_title.py b/dev/devtool/check_pr_title.py
similarity index 80%
rename from dev/check_pr_title.py
rename to dev/devtool/check_pr_title.py
index 7a9aad83379b..f4db98a72a6f 100644
--- a/dev/check_pr_title.py
+++ b/dev/devtool/check_pr_title.py
@@ -14,21 +14,20 @@
# ==============================================================================
"""Used to check a given PR title format."""
-import pathlib
import re
import sys
import tomllib
+from pathlib import Path
+from typing import Any
-if __name__ == "__main__":
- pr_title = sys.argv[1]
+def _load_config() -> dict[str, Any]:
+ config_path = Path(__file__).resolve().parent.parent / "changelog_config.toml"
+ with config_path.open("rb") as file:
+ return tomllib.load(file)
- # Load the YAML configuration
- with (pathlib.Path(__file__).parent.resolve() / "changelog_config.toml").open(
- "rb"
- ) as file:
- config = tomllib.load(file)
+def _validate_title(pr_title: str, config: dict[str, Any]) -> tuple[bool, str]:
# Extract types, project, and scope from the config
types = "|".join(config["type"])
projects = "|".join(config["project"]) + "|\\*"
@@ -39,7 +38,7 @@
pattern_template = config["pattern_template"]
pattern = pattern_template.format(types=types, projects=projects, scope=scope)
- # Check for the pattern in the first argument given to the script
+ # Check title against pattern.
match = re.search(pattern, pr_title)
valid = True
@@ -47,7 +46,7 @@
# This check is there to ignore dependabot PRs from title checks
if pr_title.startswith("build"):
- sys.exit(0)
+ return True, ""
elif not match:
valid = False
else:
@@ -57,8 +56,19 @@
elif match.group(2) == "*" and match.group(3) is None:
valid = False
error = "the cannot be '*' without using the ':skip' flag"
+ return valid, error
+
+
+def main() -> None:
+ if len(sys.argv) < 2:
+ raise ValueError("Usage: python -m devtool.check_pr_title ''")
+
+ pr_title = sys.argv[1]
+ config = _load_config()
+ valid, error = _validate_title(pr_title, config)
if not valid:
+ types = "|".join(config["type"])
print(
f"PR title `{pr_title}` is invalid, {error}.\n\nA PR title should "
"be of the form:\n\n\t(): \n\n"
@@ -73,3 +83,7 @@
"the changelog:\n\n\t`feat(framework:skip): Add new option to build CLI`\n"
)
sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/dev/update-html-themes.py b/dev/devtool/update_html_themes.py
similarity index 96%
rename from dev/update-html-themes.py
rename to dev/devtool/update_html_themes.py
index 48dbed5c8a5f..d6e2658eefe4 100644
--- a/dev/update-html-themes.py
+++ b/dev/devtool/update_html_themes.py
@@ -19,10 +19,11 @@
import re
from pathlib import Path
from typing import Optional, Union
+
import yaml
-# Change this if you want to search a different directory.
-ROOT_DIR = Path(".")
+REPO_ROOT = Path(__file__).resolve().parents[2]
+ROOT_DIR = REPO_ROOT
# Define new fields to be added to the `html_theme_options` dictionary in `conf.py`.
# If no fields are needed, set to an empty dictionary.
@@ -37,7 +38,7 @@
},
}
-with (ROOT_DIR / "dev" / "docs-ui-config.yml").open() as f:
+with (REPO_ROOT / "dev" / "docs-ui-config.yml").open(encoding="utf-8") as f:
announcement = yaml.safe_load(f)["announcement"]
if announcement["enabled"]:
NEW_FIELDS["announcement"] = announcement["html"]
diff --git a/dev/update_python.py b/dev/devtool/update_python.py
similarity index 99%
rename from dev/update_python.py
rename to dev/devtool/update_python.py
index f2acd8102b07..2cbadb5bac73 100644
--- a/dev/update_python.py
+++ b/dev/devtool/update_python.py
@@ -125,7 +125,7 @@ def _update_python_versions(
r"\g<1>" + new_major_minor + r"\g<2>",
),
],
- "dev/*.py": [
+ "dev/devtool/*.py": [
# Update version assignments
(
r'(["\'])' + re.escape(old_version) + r'(\.\d+)?(["\'],?)\s*\n?',