Skip to content

Commit f0338f0

Browse files
authored
Merge pull request #38 from data-science-extensions/updates
Introduce automated JSON Schema generation for `pyproject.toml` configuration validation
2 parents 1eb6912 + fb82545 commit f0338f0

6 files changed

Lines changed: 566 additions & 13 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,7 @@ jobs:
5050
run: uv sync --no-cache --all-groups --upgrade --reinstall-package=${{ env.PACKAGE_NAME }}
5151
- name: Run checks
5252
run: uv run ./src/utils/scripts.py check
53+
- name: Generate Config Schemas
54+
run: uv run ./src/utils/generate_config_schema.py
55+
- name: Check for Schema Changes
56+
run: git diff --exit-code src/schemas/json/ || (echo "Schemas have changed. Please commit the updated schema files." && exit 1)

src/docstring_format_checker/config.py

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444

4545
# ## Python StdLib Imports ----
4646
import sys
47-
from dataclasses import dataclass
47+
from dataclasses import dataclass, field
4848
from pathlib import Path
4949
from typing import Any, Literal, Optional, Union
5050

@@ -111,11 +111,41 @@ class GlobalConfig:
111111
Global configuration for docstring checking behavior.
112112
"""
113113

114-
allow_undefined_sections: bool = False
115-
require_docstrings: bool = True
116-
check_private: bool = False
117-
validate_param_types: bool = True
118-
optional_style: Literal["silent", "validate", "strict"] = "validate"
114+
allow_undefined_sections: bool = field(
115+
default=False,
116+
metadata={
117+
"title": "Allow Undefined Sections",
118+
"description": "Allow sections not defined in the configuration.",
119+
},
120+
)
121+
require_docstrings: bool = field(
122+
default=True,
123+
metadata={
124+
"title": "Require Docstrings",
125+
"description": "Require docstrings for all functions/methods.",
126+
},
127+
)
128+
check_private: bool = field(
129+
default=False,
130+
metadata={
131+
"title": "Check Private Members",
132+
"description": "Check docstrings for private members (starting with an underscore).",
133+
},
134+
)
135+
validate_param_types: bool = field(
136+
default=True,
137+
metadata={
138+
"title": "Validate Parameter Types",
139+
"description": "Validate that parameter types are provided in the docstring.",
140+
},
141+
)
142+
optional_style: Literal["silent", "validate", "strict"] = field(
143+
default="validate",
144+
metadata={
145+
"title": "Optional Style",
146+
"description": "The style for reporting issues in optional sections.",
147+
},
148+
)
119149

120150

121151
## --------------------------------------------------------------------------- #
@@ -130,13 +160,53 @@ class SectionConfig:
130160
Configuration for a docstring section.
131161
"""
132162

133-
name: str
134-
type: Literal["free_text", "list_name", "list_type", "list_name_and_type"]
135-
order: Optional[int] = None
136-
admonition: Union[bool, str] = False
137-
prefix: str = "" # Support any prefix string
138-
required: bool = False
139-
message: str = "" # Optional message for validation errors
163+
name: str = field(
164+
metadata={
165+
"title": "Name",
166+
"description": "Name of the docstring section.",
167+
},
168+
)
169+
type: Literal["free_text", "list_name", "list_type", "list_name_and_type"] = field(
170+
metadata={
171+
"title": "Type",
172+
"description": "Type of the section content.",
173+
},
174+
)
175+
order: Optional[int] = field(
176+
default=None,
177+
metadata={
178+
"title": "Order",
179+
"description": "Order of the section in the docstring.",
180+
},
181+
)
182+
admonition: Union[bool, str] = field(
183+
default=False,
184+
metadata={
185+
"title": "Admonition",
186+
"description": "Admonition style for the section. Can be False (no admonition) or a string specifying the admonition type.",
187+
},
188+
)
189+
prefix: str = field(
190+
default="",
191+
metadata={
192+
"title": "Prefix",
193+
"description": "Prefix string for the admonition values.",
194+
},
195+
)
196+
required: bool = field(
197+
default=False,
198+
metadata={
199+
"title": "Required",
200+
"description": "Whether this section is required in the docstring.",
201+
},
202+
)
203+
message: str = field(
204+
default="",
205+
metadata={
206+
"title": "Message",
207+
"description": "Optional message for validation errors.",
208+
},
209+
)
140210

141211
def __post_init__(self) -> None:
142212
"""

src/schemas/json/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# JSON Schema for pyproject.toml
2+
3+
4+
## 🌐 Overview
5+
6+
The validation of `pyproject.toml` files in modern IDEs relies on a sophisticated ecosystem of standards and community-driven repositories. Utilise JSON Schema to ensure that configuration errors are caught during authoring rather than at runtime.
7+
8+
When you open a `pyproject.toml` file in VS Code with the `Even Better TOML` extension, several processes occur:
9+
10+
1. **Schema Association**: Check if there is a mapping between the filename and a known schema. For common files like `pyproject.toml`, this mapping is often retrieved from a central registry.
11+
2. **SchemaStore.org**: Fetch schemas from this source automatically via extensions. This is the primary open-source repository where most schemas for configuration files live.
12+
3. **JSON Schema Standard**: Convert the TOML data into a JSON-compatible structure and validate it against the rules defined in the schema.
13+
14+
15+
## 🛠️ Schema Generation
16+
17+
To support `[tool.dfc]` in the `pyproject.toml` file, a JSON Schema fragment is required that describes the configuration structure. This project uses a tailored script to automate this process.
18+
19+
20+
### ⚙️ Automated Generation
21+
22+
Utilise the [src/utils/generate_config_schema.py](../../utils/generate_config_schema.py) script to generate and update the schema files. This script uses introspection on the `GlobalConfig()` and `SectionConfig()` classes to ensure the schema always reflects the actual code structure.
23+
24+
Run the following command to regenerate the schemas:
25+
26+
```bash
27+
source .venv/bin/activate
28+
uv run ./src/utils/generate_config_schema.py
29+
```
30+
31+
32+
### 🚀 Continuous Integration
33+
34+
This generation process is integrated into the project's CI workflow, as defined in [.github/workflows/ci.yml](.github/workflows/ci.yml). During a pull request, the CI environment executes the generator and validates that no drift has occurred between the code and the schema files:
35+
36+
- Execute `uv run ./src/utils/generate_config_schema.py` to generate the latest schemas.
37+
- Run `git diff --exit-code src/schemas/json/` to ensure that any changes to the configuration classes have been correctly captured and committed in the schema.
38+
39+
40+
## 📜 Generated Schema Files
41+
42+
Two primary schema files are generated in this directory:
43+
44+
- [partial-dfc.json](partial-dfc.json): Contains the core schema definitions for the `dfc` tool configuration. This is the file intended for submission to SchemaStore.org.
45+
- [pyproject.json](pyproject.json): A wrapper schema used for local testing and validation of the entire `pyproject.toml` structure.
46+
47+
48+
### 💻 Local Validation in VS Code
49+
50+
Before a schema is merged into the global SchemaStore registry, you can test validation locally in your workspace. Add the following to your `settings.json` to associate the local schema with your `pyproject.toml`:
51+
52+
```json
53+
{
54+
"toml.shared.schema": {
55+
"pyproject.toml": "./src/schemas/json/partial-dfc.json"
56+
}
57+
}
58+
```

src/schemas/json/partial-dfc.json

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"$id": "https://json.schemastore.org/partial-dfc.json",
4+
"$comment": "This is a partial schema for the `docstring-format-checker` package pyproject.toml, under the header [tool.dfc] or [tool.docstring-format-checker].",
5+
"type": "object",
6+
"additionalProperties": false,
7+
"properties": {
8+
"allow_undefined_sections": {
9+
"title": "Allow Undefined Sections",
10+
"description": "Allow sections not defined in the configuration.",
11+
"type": "boolean",
12+
"default": false
13+
},
14+
"require_docstrings": {
15+
"title": "Require Docstrings",
16+
"description": "Require docstrings for all functions/methods.",
17+
"type": "boolean",
18+
"default": true
19+
},
20+
"check_private": {
21+
"title": "Check Private Members",
22+
"description": "Check docstrings for private members (starting with an underscore).",
23+
"type": "boolean",
24+
"default": false
25+
},
26+
"validate_param_types": {
27+
"title": "Validate Parameter Types",
28+
"description": "Validate that parameter types are provided in the docstring.",
29+
"type": "boolean",
30+
"default": true
31+
},
32+
"optional_style": {
33+
"title": "Optional Style",
34+
"description": "The style for reporting issues in optional sections.",
35+
"type": "string",
36+
"enum": [
37+
"silent",
38+
"validate",
39+
"strict"
40+
],
41+
"default": "validate"
42+
},
43+
"sections": {
44+
"type": "array",
45+
"title": "Docstring Sections",
46+
"description": "List of docstring section configurations.",
47+
"items": {
48+
"type": "object",
49+
"additionalProperties": false,
50+
"required": [
51+
"name",
52+
"type"
53+
],
54+
"properties": {
55+
"name": {
56+
"title": "Name",
57+
"description": "Name of the docstring section.",
58+
"type": "string"
59+
},
60+
"type": {
61+
"title": "Type",
62+
"description": "Type of the section content.",
63+
"type": "string",
64+
"enum": [
65+
"free_text",
66+
"list_name",
67+
"list_type",
68+
"list_name_and_type"
69+
]
70+
},
71+
"order": {
72+
"title": "Order",
73+
"description": "Order of the section in the docstring.",
74+
"type": [
75+
"integer",
76+
"null"
77+
],
78+
"default": null
79+
},
80+
"admonition": {
81+
"title": "Admonition",
82+
"description": "Admonition style for the section. Can be False (no admonition) or a string specifying the admonition type.",
83+
"type": [
84+
"boolean",
85+
"string"
86+
],
87+
"default": false
88+
},
89+
"prefix": {
90+
"title": "Prefix",
91+
"description": "Prefix string for the admonition values.",
92+
"type": "string",
93+
"default": ""
94+
},
95+
"required": {
96+
"title": "Required",
97+
"description": "Whether this section is required in the docstring.",
98+
"type": "boolean",
99+
"default": false
100+
},
101+
"message": {
102+
"title": "Message",
103+
"description": "Optional message for validation errors.",
104+
"type": "string",
105+
"default": ""
106+
}
107+
}
108+
}
109+
}
110+
}
111+
}

src/schemas/json/pyproject.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"properties": {
3+
"tool": {
4+
"properties": {
5+
"dfc": {
6+
"$ref": "https://json.schemastore.org/partial-dfc.json",
7+
"title": "docstring-format-checker",
8+
"description": "A CLI tool to check and validate Python docstring formatting and completeness"
9+
},
10+
"docstring-format-checker": {
11+
"$ref": "https://json.schemastore.org/partial-dfc.json",
12+
"title": "docstring-format-checker",
13+
"description": "A CLI tool to check and validate Python docstring formatting and completeness"
14+
}
15+
}
16+
}
17+
}
18+
}

0 commit comments

Comments
 (0)