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 d88efdc6134..52b579eb565 100644 --- a/src/api/json/catalog.json +++ b/src/api/json/catalog.json @@ -1225,6 +1225,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 839bde39eeb..4ceeff17091 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.*] @@ -1277,6 +1278,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"