Skip to content

Commit 18aef69

Browse files
feat: Upgrade linters for ruff and mypy (#330)
1 parent c2dd5af commit 18aef69

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+922
-798
lines changed

.pre-commit-config.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,8 @@ repos:
6060
# "./pyproject.toml",
6161
# ]
6262

63-
exclude: "CHANGELOG.md"
63+
exclude: |
64+
(?x)^(
65+
CHANGELOG\.md|
66+
src/galileo/resources/.*
67+
)$

examples/langgraph/basic_langgraph.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ class State(TypedDict):
2727
messages: Annotated[list, add_messages]
2828

2929

30-
def node(state: State):
30+
def node(state: State) -> dict:
3131
messages = state["messages"]
3232
new_message = AIMessage("Hello!")
3333

34-
return {"messages": messages + [new_message], "extra_field": 10}
34+
return {"messages": [*messages, new_message], "extra_field": 10}
3535

3636

37-
def node2(state: State):
37+
def node2(state: State) -> dict:
3838
return {"messages": state["messages"]}
3939

4040

examples/langgraph/with_openai.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class State(TypedDict):
2828
llm = ChatOpenAI(model="gpt-4")
2929

3030

31-
def chatbot(state: State):
31+
def chatbot(state: State) -> dict:
3232
return {"messages": [llm.invoke(state["messages"])]}
3333

3434

pyproject.toml

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,113 @@ fix = true
8484
src = ["src"]
8585
unsafe-fixes = true
8686
target-version = "py39"
87+
exclude = [
88+
"src/galileo/resources",
89+
]
8790

8891
[tool.ruff.format]
8992
skip-magic-trailing-comma = true
9093

9194
[tool.ruff.lint]
92-
select = ["E4", "E7", "E9", "F", "I", "UP", "ASYNC"]
93-
ignore = []
95+
select = [
96+
# Pyflakes (basic errors)
97+
"F",
98+
# Pycodestyle (style errors)
99+
"E4", "E7", "E9", "W6",
100+
# Import sorting
101+
"I",
102+
# Pyupgrade (Python version upgrades)
103+
"UP",
104+
# Async-related checks
105+
"ASYNC",
106+
# Flake8-bugbear (common bugs)
107+
"B",
108+
# Flake8-comprehensions (list/set/dict comprehension issues)
109+
"C4",
110+
# Flake8-pie (unnecessary code patterns)
111+
"PIE",
112+
# Flake8-simplify (code simplification)
113+
"SIM",
114+
# Pylint-like rules (selective)
115+
"PLC", "PLE", "PLW",
116+
# Ruff-specific rules
117+
"RUF",
118+
# Flake8-bandit (security issues)
119+
"S",
120+
# Flake8-blind-except (bare except clauses)
121+
"BLE",
122+
# Flake8-boolean-trap (boolean trap antipattern)
123+
"FBT",
124+
# Flake8-unused-arguments
125+
"ARG",
126+
# Flake8-pytest-style
127+
"PT",
128+
# Flake8-return (return statement issues)
129+
"RET",
130+
# Flake8-implicit-str-concat
131+
"ISC",
132+
# Type checking related (important for bug catching)
133+
"ANN001", "ANN201", "ANN202", "ANN205", "ANN206",
134+
]
135+
ignore = [
136+
# Ignore overly strict rules (aligned with popular frameworks)
137+
"S101", # Use of assert (needed for tests)
138+
"PLR0913", # Too many arguments
139+
"PLR2004", # Magic value used in comparison
140+
"B008", # Do not perform function calls in argument defaults
141+
"FBT001", # Boolean positional arg in function definition
142+
"FBT002", # Boolean default arg in function definition
143+
"S603", # subprocess call: check for execution of untrusted input
144+
"S607", # Starting a process with a partial executable path
145+
"PLR0911", # Too many return statements
146+
"PLR0912", # Too many branches
147+
"PLR0915", # Too many statements
148+
"PLW2901", # Redefined loop variable
149+
# Type annotation related (balance strictness)
150+
"ANN101", # Missing type annotation for `self` (unnecessary)
151+
"ANN102", # Missing type annotation for `cls` (unnecessary)
152+
"ANN002", # Missing type annotation for `*args` (impractical to enforce)
153+
"ANN003", # Missing type annotation for `**kwargs` (impractical to enforce)
154+
"ANN401", # Dynamically typed expressions (Any) are disallowed
155+
# Unused arguments (often legitimate in wrappers, callbacks, etc.)
156+
"ARG001", # Unused function argument
157+
"ARG002", # Unused method argument
158+
"ARG005", # Unused lambda argument (common in callbacks)
159+
# Exception handling (often too strict)
160+
"BLE001", # Do not catch blind exception (sometimes necessary)
161+
"B904", # Use raise ... from err (not always needed)
162+
# Style preferences (overly pedantic)
163+
"SIM102", # Use single if statement (readability preference)
164+
"PLC0206", # Extracting value from dictionary without calling .items()
165+
"PLW0127", # Self-assignment of variable (sometimes needed)
166+
# Additional common ignores from popular frameworks
167+
"S311", # Standard pseudo-random generators (false positive for backoff/timing)
168+
"S324", # Insecure hash functions (sometimes needed for non-crypto)
169+
"PLR0914", # Too many local variables
170+
]
171+
172+
[tool.ruff.lint.per-file-ignores]
173+
# Ignore most linting rules for test files (focus on functionality, not style)
174+
"tests/**/*.py" = [
175+
# Type annotations (not critical for tests)
176+
"ANN", # All annotation rules
177+
# Complexity (tests can be complex)
178+
"PLR", # All pylint refactor rules (complexity, etc.)
179+
# Security (tests often need assertions, subprocess, etc.)
180+
"S", # All bandit security rules
181+
# Style preferences (less important in tests)
182+
"FBT", # Boolean trap rules
183+
"ARG", # Unused argument rules
184+
"RET", # Return statement rules
185+
"SIM", # Simplify rules
186+
"C4", # Comprehension rules
187+
"PIE", # Unnecessary code patterns
188+
"ISC", # Implicit string concatenation
189+
# Common test patterns
190+
"B008", # Function calls in argument defaults
191+
"B017", # Do not assert blind exception (needed for pytest.raises)
192+
"PT", # Pytest style rules (can be overly strict)
193+
]
94194

95195
[tool.ruff.lint.isort]
96196
known-first-party = ["galileo_core"]
@@ -108,12 +208,41 @@ wrap-descriptions = 120
108208

109209
[tool.mypy]
110210
mypy_path = ["src"]
211+
# Type checking strictness (balanced for production use)
111212
disallow_untyped_defs = true
112-
disable_error_code = ["import-untyped"]
213+
disallow_incomplete_defs = true
214+
disallow_untyped_decorators = false # Often problematic with third-party decorators
215+
disallow_untyped_calls = false # Set to false to avoid issues with external libraries
216+
# Error detection
217+
warn_redundant_casts = true
218+
warn_unused_ignores = true
219+
warn_return_any = false # Too noisy with external APIs
220+
warn_unreachable = true
221+
# Import handling
113222
ignore_missing_imports = true
114-
no_implicit_optional = false
115223
follow_imports = "skip"
224+
# Optional handling
225+
no_implicit_optional = true # Good practice for modern Python
226+
strict_optional = true
227+
# Error codes (balanced approach)
228+
disable_error_code = [
229+
"import-untyped",
230+
"misc", # Disable misc warnings (like untyped decorators we can't control)
231+
]
232+
enable_error_code = ["truthy-bool", "redundant-expr", "unused-awaitable"]
233+
# Plugins
116234
plugins = ["pydantic.mypy"]
235+
# Additional strictness (keep what catches real bugs)
236+
check_untyped_defs = true
237+
strict_equality = true
238+
extra_checks = true
239+
# Performance and compatibility
240+
show_error_codes = true
241+
pretty = true
242+
show_column_numbers = true
243+
# Incremental mode for better performance on individual files
244+
incremental = true
245+
sqlite_cache = true
117246

118247
# Release.
119248

scripts/convert-md-to-mdx.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pathlib import Path
88

99

10-
def process_markdown_files():
10+
def process_markdown_files() -> None:
1111
"""
1212
Process all .md files in .generated_docs/reference folder and subdirectories:
1313
- Remove sidebar_label: line

scripts/lint.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#!/bin/sh -ex
22

3-
pre-commit run ruff --all-files
4-
pre-commit run mypy --all-files
3+
echo "🔍 Running lint checks (excluding auto-generated files)..."
4+
poetry run pre-commit run ruff --all-files
5+
poetry run pre-commit run mypy --all-files
6+
echo "✅ All lint checks completed successfully!"

src/galileo/config.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# mypy: disable-error-code=syntax
22
# We need to ignore syntax errors until https://github.com/python/mypy/issues/17535 is resolved.
3-
from typing import Any, Optional
3+
from typing import Any, ClassVar, Optional
44

55
from pydantic_core import Url
66

@@ -12,18 +12,14 @@ class GalileoPythonConfig(GalileoConfig):
1212
config_filename: str = "galileo-python-config.json"
1313
console_url: Url = "https://app.galileo.ai"
1414

15-
def reset(self) -> None:
16-
global _galileo_config
17-
_galileo_config = None
15+
_instance: ClassVar[Optional["GalileoPythonConfig"]] = None
1816

17+
def reset(self) -> None:
18+
GalileoPythonConfig._instance = None
1919
super().reset()
2020

2121
@classmethod
2222
def get(cls, **kwargs: Any) -> "GalileoPythonConfig":
23-
global _galileo_config
24-
_galileo_config = cls._get(_galileo_config, **kwargs) # type: ignore[arg-type]
25-
assert _galileo_config is not None, "Failed to initialize GalileoPythonConfig"
26-
return _galileo_config
27-
28-
29-
_galileo_config: Optional[GalileoPythonConfig] = None
23+
cls._instance = cls._get(cls._instance, **kwargs)
24+
assert cls._instance is not None, "Failed to initialize GalileoPythonConfig"
25+
return cls._instance

src/galileo/constants/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33

44
DEFAULT_API_URL = "https://api.galileo.ai/"
55

6-
__all__ = ("DEFAULT_PROJECT_NAME", "DEFAULT_LOG_STREAM_NAME", "DEFAULT_API_URL")
6+
__all__ = ("DEFAULT_API_URL", "DEFAULT_LOG_STREAM_NAME", "DEFAULT_PROJECT_NAME")

src/galileo/constants/routes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class Routes(str, Enum):
55
healthcheck = "healthcheck"
66
login = "login"
77
api_key_login = "login/api_key"
8-
get_token = "get-token"
8+
get_token = "get-token" # noqa: S105 # This is a URL path, not a password
99

1010
projects = "projects"
1111
all_projects = "projects/all"

src/galileo/datasets.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,9 @@ def add_rows(self, row_data: list[dict[str, Any]]) -> "Dataset":
137137
return self
138138

139139
def get_version_history(self) -> Optional[Union[HTTPValidationError, ListDatasetVersionResponse]]:
140-
list_dataset = query_dataset_versions_datasets_dataset_id_versions_query_post.sync(
140+
return query_dataset_versions_datasets_dataset_id_versions_query_post.sync(
141141
dataset_id=self.dataset.id, client=self.config.api_client, body=ListDatasetVersionParams()
142142
)
143-
return list_dataset
144143

145144
def load_version(self, version_index: int) -> DatasetContent:
146145
return get_dataset_version_content_datasets_dataset_id_versions_version_index_content_get.sync(
@@ -557,7 +556,7 @@ def create_dataset(name: str, content: DatasetType) -> Dataset:
557556

558557

559558
def get_dataset_version_history(
560-
*, dataset_name: str = None, dataset_id: str = None
559+
*, dataset_name: Optional[str] = None, dataset_id: Optional[str] = None
561560
) -> Optional[Union[HTTPValidationError, ListDatasetVersionResponse]]:
562561
"""
563562
Retrieves a dataset version history by dataset name or dataset id.
@@ -582,17 +581,16 @@ def get_dataset_version_history(
582581
if dataset is None:
583582
raise ValueError(f"Dataset '{dataset_name}' not found")
584583
return dataset.get_version_history()
585-
elif dataset_id is not None:
584+
if dataset_id is not None:
586585
dataset = Datasets().get(id=dataset_id)
587586
if dataset is None:
588587
raise ValueError(f"Dataset '{dataset_id}' not found")
589588
return dataset.get_version_history()
590-
else:
591-
raise ValueError("Either dataset_name or dataset_id must be provided.")
589+
raise ValueError("Either dataset_name or dataset_id must be provided.")
592590

593591

594592
def get_dataset_version(
595-
*, version_index: int, dataset_name: str = None, dataset_id: str = None
593+
*, version_index: int, dataset_name: Optional[str] = None, dataset_id: Optional[str] = None
596594
) -> Optional[DatasetContent]:
597595
"""
598596
Retrieves a dataset version by dataset name or dataset id.
@@ -618,13 +616,12 @@ def get_dataset_version(
618616
raise ValueError(f"Dataset '{dataset_name}' not found")
619617
return dataset.load_version(version_index)
620618

621-
elif dataset_id is not None:
619+
if dataset_id is not None:
622620
dataset = Datasets().get(id=dataset_id)
623621
if dataset is None:
624622
raise ValueError(f"Dataset '{dataset_id}' not found")
625623
return dataset.load_version(version_index)
626-
else:
627-
raise ValueError("Either dataset_name or dataset_id must be provided.")
624+
raise ValueError("Either dataset_name or dataset_id must be provided.")
628625

629626

630627
def extend_dataset(
@@ -706,6 +703,6 @@ def convert_dataset_row_to_record(dataset_row: DatasetRow) -> "DatasetRecord":
706703
return DatasetRecord(
707704
id=dataset_row.row_id,
708705
input=values_dict["input"],
709-
output=values_dict["output"] if "output" in values_dict else None,
710-
metadata=values_dict["metadata"] if "metadata" in values_dict else None,
706+
output=values_dict.get("output", None),
707+
metadata=values_dict.get("metadata", None),
711708
)

0 commit comments

Comments
 (0)