From 6debd6cb6bf5a6ed9508eb3b48522d929afbb5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Tue, 12 May 2026 21:03:21 +0100 Subject: [PATCH 1/2] Update tox JSON Schema to 4.54.0 (#5680) * Update tox JSON Schema to 4.54.0 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/schemas/json/tox.json | 563 +++++++++++++++++++++----------------- 1 file changed, 309 insertions(+), 254 deletions(-) diff --git a/src/schemas/json/tox.json b/src/schemas/json/tox.json index dc59b9f71cc..cefe1e3cd6a 100644 --- a/src/schemas/json/tox.json +++ b/src/schemas/json/tox.json @@ -35,70 +35,7 @@ "env_list": { "type": "array", "items": { - "oneOf": [ - { - "$ref": "#/definitions/subs" - }, - { - "type": "object", - "required": ["product"], - "properties": { - "product": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "object", - "required": ["prefix"], - "properties": { - "prefix": { - "type": "string" - }, - "start": { - "type": "integer" - }, - "stop": { - "type": "integer" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "minProperties": 1, - "maxProperties": 1, - "not": { - "required": ["prefix"] - }, - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - }, - "description": "labeled factor group for {factor:label} substitution" - } - ] - }, - "description": "factor groups for cartesian product expansion" - }, - "exclude": { - "type": "array", - "items": { - "type": "string" - }, - "description": "environment names to exclude from product" - } - }, - "additionalProperties": false - } - ] + "$ref": "#/definitions/env_list_item" }, "description": "define environments to automatically run" }, @@ -139,70 +76,7 @@ "additionalProperties": { "type": "array", "items": { - "oneOf": [ - { - "$ref": "#/definitions/subs" - }, - { - "type": "object", - "required": ["product"], - "properties": { - "product": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "object", - "required": ["prefix"], - "properties": { - "prefix": { - "type": "string" - }, - "start": { - "type": "integer" - }, - "stop": { - "type": "integer" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "minProperties": 1, - "maxProperties": 1, - "not": { - "required": ["prefix"] - }, - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - }, - "description": "labeled factor group for {factor:label} substitution" - } - ] - }, - "description": "factor groups for cartesian product expansion" - }, - "exclude": { - "type": "array", - "items": { - "type": "string" - }, - "description": "environment names to exclude from product" - } - }, - "additionalProperties": false - } - ] + "$ref": "#/definitions/env_list_item" } }, "description": "core labels" @@ -286,70 +160,7 @@ "depends": { "type": "array", "items": { - "oneOf": [ - { - "$ref": "#/definitions/subs" - }, - { - "type": "object", - "required": ["product"], - "properties": { - "product": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "object", - "required": ["prefix"], - "properties": { - "prefix": { - "type": "string" - }, - "start": { - "type": "integer" - }, - "stop": { - "type": "integer" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "minProperties": 1, - "maxProperties": 1, - "not": { - "required": ["prefix"] - }, - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - }, - "description": "labeled factor group for {factor:label} substitution" - } - ] - }, - "description": "factor groups for cartesian product expansion" - }, - "exclude": { - "type": "array", - "items": { - "type": "string" - }, - "description": "environment names to exclude from product" - } - }, - "additionalProperties": false - } - ] + "$ref": "#/definitions/env_list_item" }, "description": "tox environments that this environment depends on (must be run after those)" }, @@ -470,30 +281,51 @@ "commands_pre": { "type": "array", "items": { - "type": "array", - "items": { - "$ref": "#/definitions/subs" - } + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/subs" + } + }, + { + "$ref": "#/definitions/replace_object" + } + ] }, "description": "the commands to be called before testing" }, "commands": { "type": "array", "items": { - "type": "array", - "items": { - "$ref": "#/definitions/subs" - } + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/subs" + } + }, + { + "$ref": "#/definitions/replace_object" + } + ] }, "description": "the commands to be called for testing" }, "commands_post": { "type": "array", "items": { - "type": "array", - "items": { - "$ref": "#/definitions/subs" - } + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/subs" + } + }, + { + "$ref": "#/definitions/replace_object" + } + ] }, "description": "the commands to be called after testing" }, @@ -504,10 +336,17 @@ "recreate_commands": { "type": "array", "items": { - "type": "array", - "items": { - "$ref": "#/definitions/subs" - } + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/subs" + } + }, + { + "$ref": "#/definitions/replace_object" + } + ] }, "description": "commands to run before the environment is removed during recreation (e.g. cache cleanup)" }, @@ -623,10 +462,17 @@ "extra_setup_commands": { "type": "array", "items": { - "type": "array", - "items": { - "$ref": "#/definitions/subs" - } + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/subs" + } + }, + { + "$ref": "#/definitions/replace_object" + } + ] }, "description": "commands to execute after setup (deps and package install) but before test commands" }, @@ -727,65 +573,274 @@ "type": "string" }, { - "type": "object", - "properties": { - "replace": { + "$ref": "#/definitions/replace_env" + }, + { + "$ref": "#/definitions/replace_ref" + }, + { + "$ref": "#/definitions/replace_posargs" + }, + { + "$ref": "#/definitions/replace_glob" + }, + { + "$ref": "#/definitions/replace_if" + } + ] + }, + "replace_env": { + "type": "object", + "description": "substitute the value of an environment variable", + "properties": { + "replace": { + "const": "env" + }, + "name": { + "type": "string" + }, + "default": { + "oneOf": [ + { "type": "string" }, - "name": { + { + "type": "array", + "items": { + "$ref": "#/definitions/subs" + } + } + ] + }, + "extend": { + "type": "boolean" + }, + "marker": { + "type": "string" + } + }, + "required": ["replace", "name"], + "additionalProperties": false + }, + "replace_ref": { + "type": "object", + "description": "substitute the value of another configuration key", + "properties": { + "replace": { + "const": "ref" + }, + "of": { + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "type": "string" + }, + "key": { + "type": "string" + }, + "default": { + "oneOf": [ + { "type": "string" }, - "default": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/subs" - } - } - ] + { + "type": "array", + "items": { + "$ref": "#/definitions/subs" + } + } + ] + }, + "extend": { + "type": "boolean" + }, + "marker": { + "type": "string" + } + }, + "required": ["replace"], + "additionalProperties": false + }, + "replace_posargs": { + "type": "object", + "description": "substitute the positional arguments passed to tox", + "properties": { + "replace": { + "const": "posargs" + }, + "default": { + "type": "array", + "items": { + "$ref": "#/definitions/subs" + } + }, + "extend": { + "type": "boolean" + }, + "marker": { + "type": "string" + } + }, + "required": ["replace"], + "additionalProperties": false + }, + "replace_glob": { + "type": "object", + "description": "substitute matches of a filesystem glob pattern", + "properties": { + "replace": { + "const": "glob" + }, + "pattern": { + "type": "string" + }, + "default": { + "oneOf": [ + { + "type": "string" }, - "extend": { - "type": "boolean" + { + "type": "array", + "items": { + "$ref": "#/definitions/subs" + } } - }, - "required": ["replace"], - "additionalProperties": false + ] + }, + "extend": { + "type": "boolean" + }, + "marker": { + "type": "string" + } + }, + "required": ["replace", "pattern"], + "additionalProperties": false + }, + "replace_if": { + "type": "object", + "description": "conditional substitution based on env vars, factors, or env_name", + "properties": { + "replace": { + "const": "if" + }, + "condition": { + "type": "string" + }, + "then": true, + "else": true, + "extend": { + "type": "boolean" + }, + "marker": { + "type": "string" + } + }, + "required": ["replace", "condition", "then"], + "additionalProperties": false + }, + "replace_object": { + "description": "any of the table-form replacements; usable wherever a list item can be a replacement", + "anyOf": [ + { + "$ref": "#/definitions/replace_env" + }, + { + "$ref": "#/definitions/replace_ref" + }, + { + "$ref": "#/definitions/replace_posargs" + }, + { + "$ref": "#/definitions/replace_glob" + }, + { + "$ref": "#/definitions/replace_if" + } + ] + }, + "factor_range_dict": { + "type": "object", + "required": ["prefix"], + "properties": { + "prefix": { + "type": "string" + }, + "start": { + "type": "integer" + }, + "stop": { + "type": "integer" + } + }, + "additionalProperties": false, + "description": "range factor group: expands to prefix+N for N in [start, stop]" + }, + "factor_labeled_dict": { + "type": "object", + "minProperties": 1, + "maxProperties": 1, + "not": { + "required": ["prefix"] + }, + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": "labeled factor group for {factor:label} substitution" + }, + "product_factor_group": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "$ref": "#/definitions/factor_range_dict" + }, + { + "$ref": "#/definitions/factor_labeled_dict" + } + ] + }, + "env_list_item": { + "oneOf": [ + { + "$ref": "#/definitions/subs" }, { "type": "object", + "required": ["product"], "properties": { - "replace": { - "type": "string" + "product": { + "type": "array", + "items": { + "$ref": "#/definitions/product_factor_group" + }, + "description": "factor groups for cartesian product expansion" }, - "of": { + "exclude": { "type": "array", "items": { "type": "string" - } - }, - "default": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/subs" - } - } - ] - }, - "extend": { - "type": "boolean" + }, + "description": "environment names to exclude from product" } }, - "required": ["replace", "of"], "additionalProperties": false + }, + { + "$ref": "#/definitions/factor_range_dict" + }, + { + "$ref": "#/definitions/factor_labeled_dict" } ] } From d037e42762e37407f01647235002a8759f565cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 13 May 2026 00:09:24 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=A8=20Add=20JSON=20Schema=20for=20`sc?= =?UTF-8?q?heduled`=20in=20`pyproject.toml`=20(#5681)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Add support for scheduled in pyproject.toml * ♻️ Update descriptions for keys * ✅ Update test examples * ♻️ Sort alphabetically * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/CODEOWNERS | 1 + src/api/json/catalog.json | 6 +++++ .../pyproject/scheduled-invalid-cadence.toml | 5 ++++ .../scheduled-missing-entrypoint.toml | 4 +++ src/schema-validation.jsonc | 2 ++ src/schemas/json/partial-scheduled.json | 26 +++++++++++++++++++ src/schemas/json/pyproject.json | 5 ++++ src/test/pyproject/scheduled.toml | 9 +++++++ 8 files changed, 58 insertions(+) create mode 100644 src/negative_test/pyproject/scheduled-invalid-cadence.toml create mode 100644 src/negative_test/pyproject/scheduled-missing-entrypoint.toml create mode 100644 src/schemas/json/partial-scheduled.json create mode 100644 src/test/pyproject/scheduled.toml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b430a02d207..a9caf65beab 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -48,6 +48,7 @@ src/schemas/json/pyproject.json @ya7010 # Managed by FastAPI Team: src/schemas/json/partial-fastapi.json @tiangolo +src/schemas/json/partial-scheduled.json @tiangolo # Managed by Contextive Team: src/schemas/json/contextive-glossary.json @chrissimon-au diff --git a/src/api/json/catalog.json b/src/api/json/catalog.json index 951442d3329..9071bc0fbd1 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -1228,6 +1228,12 @@ "fileMatch": [], "url": "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/partial-fastapi.json" }, + { + "name": "partial-scheduled.json", + "description": "Scheduled job configuration for pyproject.toml", + "fileMatch": [], + "url": "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/partial-scheduled.json" + }, { "name": "bozr.suite.json", "description": "Bozr test suite file", diff --git a/src/negative_test/pyproject/scheduled-invalid-cadence.toml b/src/negative_test/pyproject/scheduled-invalid-cadence.toml new file mode 100644 index 00000000000..19131cb4ca2 --- /dev/null +++ b/src/negative_test/pyproject/scheduled-invalid-cadence.toml @@ -0,0 +1,5 @@ +#:schema ../../schemas/json/pyproject.json + +[tool.scheduled.cleanup] +every = "daily" +entrypoint = "app.jobs:clean_files" diff --git a/src/negative_test/pyproject/scheduled-missing-entrypoint.toml b/src/negative_test/pyproject/scheduled-missing-entrypoint.toml new file mode 100644 index 00000000000..1bfaab739be --- /dev/null +++ b/src/negative_test/pyproject/scheduled-missing-entrypoint.toml @@ -0,0 +1,4 @@ +#:schema ../../schemas/json/pyproject.json + +[tool.scheduled.clean-files] +every = "day" diff --git a/src/schema-validation.jsonc b/src/schema-validation.jsonc index beef8e6e8b7..5726fb8dcc9 100644 --- a/src/schema-validation.jsonc +++ b/src/schema-validation.jsonc @@ -339,6 +339,7 @@ "partial-poe.json", // pyproject.json[tool.poe] "partial-poetry.json", // pyproject.json[tool.poetry] "partial-repo-review.json", // pyproject.json[tool.repo-review] + "partial-scheduled.json", // pyproject.json[tool.scheduled] "partial-tox.json", // classic pyproject.json[tool.tox] "tox.json", "partial-eslint-plugins.json", // eslintrc.json[rules.*] @@ -1305,6 +1306,7 @@ "partial-pyright.json", "partial-pytest.json", "partial-repo-review.json", + "partial-scheduled.json", "partial-scikit-build.json", "partial-setuptools.json", "partial-setuptools-scm.json", diff --git a/src/schemas/json/partial-scheduled.json b/src/schemas/json/partial-scheduled.json new file mode 100644 index 00000000000..294bccc5e91 --- /dev/null +++ b/src/schemas/json/partial-scheduled.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/partial-scheduled.json", + "type": "object", + "additionalProperties": { + "type": "object", + "title": "Scheduled Job", + "description": "A recurring scheduled job. The table key under `[tool.scheduled]` is the unique job ID.\n\n```toml\n[tool.scheduled.]\nevery = \"\"\nentrypoint = \":\"\n```", + "additionalProperties": false, + "required": ["every", "entrypoint"], + "properties": { + "every": { + "type": "string", + "title": "Every", + "description": "How often to run the job. For example, `every = \"day\"` would run the job once per day.\n\n```toml\n[tool.scheduled.clean-files]\nevery = \"day\"\nentrypoint = \"app.jobs:clean_files\"\n```\n\nSupported values are:\n\n* `minute`\n* `hour`\n* `day`\n* `week`\n* `month`\n\nProviders should run each job at least once per configured cadence and may support only a subset of these values.", + "enum": ["minute", "hour", "day", "week", "month"] + }, + "entrypoint": { + "type": "string", + "title": "Entrypoint", + "description": "Python callable to run, in the format:\n```\nimportable.module:some_callable\n```\n\nFor example, for a scheduled job like:\n\n```python\ndef clean_files():\n ...\n```\n\nin a file at `app/jobs.py`, the config could look like:\n\n```toml\n[tool.scheduled.clean-files]\nevery = \"day\"\nentrypoint = \"app.jobs:clean_files\"\n```\n\nThis is equivalent to:\n\n```python\nfrom importable.module import some_callable\n\nsome_callable()\n```\n\nSo, `app.jobs:clean_files` is equivalent to:\n\n```python\nfrom app.jobs import clean_files\n\nclean_files()\n```", + "examples": ["app.jobs:clean_files", "backend.tasks:send_notifications"] + } + } + } +} diff --git a/src/schemas/json/pyproject.json b/src/schemas/json/pyproject.json index 32f161a403b..4ee935aaa19 100644 --- a/src/schemas/json/pyproject.json +++ b/src/schemas/json/pyproject.json @@ -977,6 +977,11 @@ "title": "Web Framework", "description": "FastAPI web framework configuration." }, + "scheduled": { + "$ref": "partial-scheduled.json", + "title": "Scheduled Jobs", + "description": "Scheduled jobs in Python's `pyproject.toml`.\n\nThis is a specification for declaring recurring scheduled jobs in Python projects, in `pyproject.toml`.\n\nIt defines how jobs are declared and how providers would run them.\n\nIt does not provide a specific implementation for running scheduled jobs, because that is provider specific.\n\nFor example, a file at `app/jobs.py` could define:\n\n```python\ndef clean_files():\n print(\"Running cleanup...\")\n```\n\nYou could define a scheduled job to run that function once per day with:\n\n```toml\n[tool.scheduled.clean-files]\nevery = \"day\"\nentrypoint = \"app.jobs:clean_files\"\n```" + }, "mypy": { "$ref": "partial-mypy.json", "title": "Static Type Checker", diff --git a/src/test/pyproject/scheduled.toml b/src/test/pyproject/scheduled.toml new file mode 100644 index 00000000000..2b091a2e945 --- /dev/null +++ b/src/test/pyproject/scheduled.toml @@ -0,0 +1,9 @@ +#:schema ../../schemas/json/pyproject.json + +[tool.scheduled.clean-files] +every = "day" +entrypoint = "app.jobs:clean_files" + +[tool.scheduled.send_notifications] +every = "week" +entrypoint = "backend.tasks:send_notifications"