Skip to content

Commit a304b13

Browse files
Standardize coding style in DSPy (#7885)
* enable style check * update rules * allow wildcard imports * exclude retrieve dir from ruff * exclude reliability test and retrieve test from ruff rule * some exclusions * cleanup * some fixes * cleanup * fix comment * update precommit hook * update pre-commit * update contributing guide * merge main and fix lints
1 parent a6bca71 commit a304b13

File tree

6 files changed

+114
-79
lines changed

6 files changed

+114
-79
lines changed

.github/workflows/run_tests.yml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,10 @@ jobs:
3131
uv venv .venv
3232
echo "${{ github.workspace }}/.venv/bin" >> $GITHUB_PATH
3333
- name: Install dependencies
34-
run: uv sync --dev -p .venv
34+
run: uv sync --dev -p .venv --extra dev
3535
- name: Ruff Fix Attempt
3636
id: ruff_fix
37-
uses: chartboost/ruff-action@v1
38-
with:
39-
args: check --fix-only --diff --exit-non-zero-on-fix
40-
continue-on-error: true
41-
- name: Fail Workflow if Ruff Fix Failed
42-
if: steps.ruff_fix.outcome == 'failure'
43-
run: |
44-
echo "Ruff fix failed, failing the workflow."
45-
echo "Please run 'ruff check . --fix-only' locally and push the changes."
46-
exit 1
37+
run: ruff check --fix-only --diff --exit-non-zero-on-fix
4738

4839
test:
4940
name: Run Tests

.pre-commit-config.yaml

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,27 @@ default_language_version:
22
python: python3.9
33

44
default_stages: [pre-commit]
5-
default_install_hook_types: [pre-commit, commit-msg]
5+
default_install_hook_types: [pre-commit]
66

77
repos:
8-
- repo: https://github.com/astral-sh/ruff-pre-commit
9-
rev: v0.1.11
10-
hooks:
11-
- id: ruff
12-
args: [--fix]
13-
- id: ruff-format
14-
15-
- repo: https://github.com/timothycrosley/isort
16-
rev: 5.12.0
8+
- repo: local
179
hooks:
18-
- id: isort
19-
args:
20-
[
21-
"--profile=black",
22-
"--py=39",
23-
"--line-length=120",
24-
"--multi-line=3",
25-
"--trailing-comma",
26-
"--force-grid-wrap=0",
27-
"--use-parentheses",
28-
"--ensure-newline-before-comments",
29-
"--project=CORE,src,config,preprocess,train,transform,main,model",
30-
]
10+
- id: ruff-check
11+
name: ruff (lint)
12+
entry: ruff
13+
language: system
14+
types_or: [python, pyi]
15+
files: ^(dspy|tests)/.*\.py$
16+
exclude: ^(dspy/__metadata__\.py|dspy/retrieve/.*\.py|tests/reliability/.*\.py|tests/retrieve/.*\.py)$
17+
args: [check, --fix-only]
3118

3219
- repo: https://github.com/pre-commit/pre-commit-hooks
33-
rev: v4.1.0
20+
rev: v5.0.0
3421
hooks:
3522
- id: check-yaml
3623
args: ["--allow-multiple-documents", "--unsafe"]
37-
- id: end-of-file-fixer
38-
- id: trailing-whitespace
39-
- id: check-docstring-first
4024
- id: check-toml
4125
- id: check-added-large-files
4226
args: ["--maxkb=1024"]
43-
- id: requirements-txt-fixer
4427
- id: check-merge-conflict
4528
- id: debug-statements
46-
- id: pretty-format-json
47-
args:
48-
- "--autofix"
49-
- "--indent=2"
50-
51-
- repo: local
52-
hooks:
53-
- id: validate-commit-msg
54-
name: Commit Message is Valid
55-
language: pygrep
56-
entry: ^(break|build|ci|docs|feat|fix|perf|refactor|style|test|ops|hotfix|release|maint|init|enh|revert)\([\w,\.,\-,\(,\),\/]+\)(!?)(:)\s{1}([\w,\W,:]+)
57-
stages: [commit-msg]
58-
args: [--negate]
59-
60-
- repo: https://github.com/pre-commit/mirrors-prettier
61-
rev: v3.0.3
62-
hooks:
63-
- id: prettier
64-
additional_dependencies:
65-
66-
- "@prettier/[email protected]"

CONTRIBUTING.md

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,55 @@ For minor changes (simple bug fixes or documentation fixes), feel free to open a
3535
To make code changes, fork the repository and set up your local development environment following the
3636
instructions in the "Environment Setup" section below.
3737

38-
### Step 3. Create a Pull Request
38+
### Step 3 Commit Your Code and Run Autoformatting
39+
40+
We follow the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html) and use `ruff` for both linting and formatting. To ensure consistent code quality, we use pre-commit hooks that automatically check and fix common issues.
41+
42+
43+
First you need to set up the pre-commit hooks (do this once after cloning the repository):
44+
45+
```shell
46+
pre-commit install
47+
```
48+
49+
Then stage and commit your changes. When you run `git commit`, the pre-commit hook will be
50+
automatically run.
51+
52+
```shell
53+
git add .
54+
git commit -m "your commit message"
55+
```
56+
57+
If the hooks make any changes, you'll need to stage and commit those changes as well.
58+
59+
You can also run the hooks manually:
60+
61+
- Check staged files only:
62+
63+
```shell
64+
pre-commit run
65+
```
66+
67+
- Check specific files:
68+
69+
```shell
70+
pre-commit run --files path/to/file1.py path/to/file2.py
71+
```
72+
73+
Please ensure all pre-commit checks pass before creating your pull request. If you're unsure about any
74+
formatting issues, feel free to commit your changes and let the pre-commit hooks fix them automatically.
75+
76+
### Step 4. Create a Pull Request
3977

4078
Once your changes are ready, open a pull request from your branch in your fork to the main branch in the
4179
[DSPy repo](https://github.com/stanfordnlp/dspy).
4280

43-
### Step 4. Code Review
81+
### Step 5. Code Review
4482

4583
Once your PR is up and passes all CI tests, we will assign reviewers to review the code. There may be
4684
several rounds of comments and code changes before the pull request gets approved by the reviewer.
4785

48-
### Step 5. Merging
86+
### Step 6. Merging
4987

5088
Once the pull request is approved, a team member will take care of merging.
5189

dspy/signatures/signature.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class SignatureMeta(type(BaseModel)):
4141
def __call__(cls, *args, **kwargs):
4242
if cls is Signature:
4343
# We don't create an actual Signature instance, instead, we create a new Signature class.
44-
custom_types = kwargs.pop('custom_types', None)
44+
custom_types = kwargs.pop("custom_types", None)
4545

4646
if custom_types is None and args and isinstance(args[0], str):
4747
custom_types = cls._detect_custom_types_from_caller(args[0])
@@ -52,19 +52,19 @@ def __call__(cls, *args, **kwargs):
5252
@staticmethod
5353
def _detect_custom_types_from_caller(signature_str):
5454
"""Detect custom types from the caller's frame based on the signature string.
55-
55+
5656
Note: This method relies on Python's frame introspection which has some limitations:
5757
1. May not work in all Python implementations (e.g., compiled with optimizations)
5858
2. Looks up a limited number of frames in the call stack
5959
3. Cannot find types that are imported but not in the caller's namespace
60-
61-
For more reliable custom type resolution, explicitly provide types using the
60+
61+
For more reliable custom type resolution, explicitly provide types using the
6262
`custom_types` parameter when creating a Signature.
6363
"""
6464

6565
# Extract potential type names from the signature string, including dotted names
6666
# Match both simple types like 'MyType' and dotted names like 'Module.Type'
67-
type_pattern = r':\s*([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)'
67+
type_pattern = r":\s*([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)"
6868
type_names = re.findall(type_pattern, signature_str)
6969
if not type_names:
7070
return None
@@ -76,7 +76,7 @@ def _detect_custom_types_from_caller(signature_str):
7676
dotted_types = {}
7777

7878
for type_name in type_names:
79-
parts = type_name.split('.')
79+
parts = type_name.split(".")
8080
base_name = parts[0]
8181

8282
if base_name not in typing.__dict__ and base_name not in __builtins__:
@@ -115,13 +115,15 @@ def _detect_custom_types_from_caller(signature_str):
115115

116116
if needed_types and frame_count >= max_frames:
117117
import logging
118+
118119
logging.getLogger("dspy").warning(
119120
f"Reached maximum frame search depth ({max_frames}) while looking for types: {needed_types}. "
120121
"Consider providing custom_types explicitly to Signature."
121122
)
122123
except (AttributeError, ValueError):
123124
# Handle environments where frame introspection is not available
124125
import logging
126+
125127
logging.getLogger("dspy").debug(
126128
"Frame introspection failed while trying to resolve custom types. "
127129
"Consider providing custom_types explicitly to Signature."
@@ -395,11 +397,11 @@ def make_signature(
395397
"question": (str, InputField()),
396398
"answer": (str, OutputField())
397399
})
398-
400+
399401
# Using custom types
400402
class MyType:
401403
pass
402-
404+
403405
sig3 = make_signature("input: MyType -> output", custom_types={"MyType": MyType})
404406
```
405407
"""

pyproject.toml

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,27 +113,69 @@ exclude_lines = [
113113
]
114114

115115
[tool.ruff]
116+
include = ["dspy/**/*.py", "tests/**/*.py"]
117+
exclude = [
118+
"dspy/__metadata__.py",
119+
"dspy/retrieve/*.py",
120+
"tests/reliability/*.py",
121+
"tests/retrieve/*.py",
122+
]
123+
124+
116125
line-length = 120
117126
indent-width = 4
118127
target-version = "py39"
119128

120129
[tool.ruff.lint]
121130
select = [
122-
"F", # Pyflakes
123-
"E", # Pycodestyle
124-
"TID252", # Absolute imports
131+
"E", # pycodestyle errors
132+
"W", # pycodestyle warnings
133+
"F", # pyflakes
134+
"I", # isort
135+
"C", # flake8-comprehensions
136+
"B", # flake8-bugbear
137+
"UP", # pyupgrade
138+
"N", # pep8-naming
139+
"RUF", # ruff-specific rules
125140
]
141+
126142
ignore = [
127-
"E501", # Line too long
143+
"B027", # Allow non-abstract empty methods in abstract base classes
144+
"FBT003",# Allow boolean positional values in function calls
145+
"C901", # Ignore complexity checking
146+
"E501", # Ignore line length errors (handled by formatter)
147+
"UP006", # Allow python typing modules
148+
"UP035", # Allow python typing modules
149+
"RUF005", # Allow using + operator to concatenate collections
150+
"B904", # Allow raise custom exceptions in except blocks
151+
"F403", # Allow wildcard imports
152+
"E721", # Allow using == to compare with type
153+
"UP031", # Allow percent format
154+
"RUF022", # Allow unsorted __all__ value
128155
]
156+
157+
# Allow fix for all enabled rules (when `--fix`) is provided.
129158
fixable = ["ALL"]
130159
unfixable = []
131160

132161
[tool.ruff.format]
133162
docstring-code-format = false
163+
quote-style = "double"
134164
indent-style = "space"
165+
skip-magic-trailing-comma = false
135166
line-ending = "auto"
136167

168+
[tool.ruff.lint.isort]
169+
known-first-party = ["dspy"]
170+
171+
[tool.ruff.lint.flake8-tidy-imports]
172+
ban-relative-imports = "all"
173+
137174
[tool.ruff.lint.per-file-ignores]
138-
"**/{tests,testing,docs}/*" = ["ALL"]
139-
"**__init__.py" = ["ALL"]
175+
"tests/**/*.py" = [
176+
"S101", # Allow assert statements in tests
177+
"TID252", # Allow relative imports in tests
178+
"ARG001", # Allow unused arguments in tests (like pytest fixtures)
179+
]
180+
"__init__.py" = ["F401"] # Init files can be empty
181+
"dspy/__init__.py" = ["I001", "E402", "F405"]

tests/datasets/test_dataset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515

1616
class CSVDataset(Dataset):
17-
def __init__(self, file_path, input_keys=None, *args, **kwargs) -> None:
18-
super().__init__(input_keys=input_keys, *args, **kwargs)
17+
def __init__(self, file_path, input_keys=None, **kwargs) -> None:
18+
super().__init__(input_keys=input_keys, **kwargs)
1919
df = pd.read_csv(file_path)
2020
data = df.to_dict(orient="records")
2121
self._train = [

0 commit comments

Comments
 (0)