Skip to content

Commit 9ce3ab3

Browse files
committed
change to ruff also in CI. add mypy and fix typing errors, add mypy to CI
1 parent 3e50cf3 commit 9ce3ab3

17 files changed

Lines changed: 325 additions & 110 deletions

.github/workflows/auto-merge-dependabot.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ jobs:
2323
if: "${{ steps.metadata.outputs.update-type ==
2424
'version-update:semver-minor' ||
2525
steps.metadata.outputs.update-type ==
26-
'version-update:semver-patch' ||
27-
steps.metadata.outputs.dependency-names ==
28-
'black' }}"
26+
'version-update:semver-patch' }}"
2927

3028
# https://cli.github.com/manual/gh_pr_merge
3129
run: gh pr merge --auto --squash "$PR_URL"

.github/workflows/black.yml

Lines changed: 0 additions & 32 deletions
This file was deleted.

.github/workflows/python-ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ jobs:
4242
- name: Install Python wheel support to speed up things
4343
run: pip install wheel
4444

45-
- name: Pre-install black
46-
run: pip install black
47-
4845
# https://github.com/marketplace/actions/install-poetry-action
4946
- name: Install Poetry
5047
uses: snok/install-poetry@v1.4.1
@@ -77,8 +74,11 @@ jobs:
7774
pip install coveralls
7875
poetry run coveralls --service=github
7976
80-
- name: Lint with pylint
77+
- name: Lint with ruff
8178
run: make lint
8279

80+
- name: Type check with mypy
81+
run: make type_check
82+
8383
- name: Build a distribution package
8484
run: poetry build -vvv

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ lint:
1313
format:
1414
poetry run ruff format .
1515

16+
type_check:
17+
poetry run mypy sql_metadata
18+
1619
publish:
1720
# run git tag -a v0.0.0 before running make publish
1821
poetry build

poetry.lock

Lines changed: 206 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ coverage = {extras = ["toml"], version = "^7.13"}
2222
pytest = "^9.0.2"
2323
pytest-cov = "^7.1.0"
2424
ruff = "^0.11"
25+
mypy = "^1.19"
2526

2627
[build-system]
2728
requires = ["poetry-core>=1.0.0"]
@@ -37,6 +38,12 @@ select = ["E", "F", "W", "C90", "I"]
3738
[tool.ruff.lint.mccabe]
3839
max-complexity = 8
3940

41+
[tool.mypy]
42+
python_version = "3.10"
43+
warn_return_any = true
44+
warn_unused_configs = true
45+
ignore_missing_imports = true
46+
4047
[tool.coverage.run]
4148
relative_files = true
4249

sql_metadata/ast_parser.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
``ValueError``).
88
"""
99

10+
from typing import Optional
11+
1012
from sqlglot import exp
1113

1214
from sql_metadata.dialect_parser import DialectParser
@@ -27,14 +29,14 @@ class ASTParser:
2729

2830
def __init__(self, sql: str) -> None:
2931
self._raw_sql = sql
30-
self._ast = None
31-
self._dialect = None
32+
self._ast: Optional[exp.Expression] = None
33+
self._dialect: object = None
3234
self._parsed = False
3335
self._is_replace = False
34-
self._cte_name_map = {}
36+
self._cte_name_map: dict[str, str] = {}
3537

3638
@property
37-
def ast(self) -> exp.Expression:
39+
def ast(self) -> Optional[exp.Expression]:
3840
"""The sqlglot AST for the query, lazily parsed on first access.
3941
4042
:returns: Root AST node, or ``None`` for empty/comment-only queries.
@@ -80,7 +82,7 @@ def cte_name_map(self) -> dict:
8082
_ = self.ast
8183
return self._cte_name_map
8284

83-
def _parse(self, sql: str) -> exp.Expression:
85+
def _parse(self, sql: str) -> Optional[exp.Expression]:
8486
"""Parse *sql* into a sqlglot AST.
8587
8688
Delegates preprocessing to :class:`SqlCleaner` and dialect

sql_metadata/column_extractor.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ def __init__(
205205
self,
206206
ast: exp.Expression,
207207
table_aliases: Dict[str, str],
208-
cte_name_map: Dict = None,
208+
cte_name_map: Optional[Dict] = None,
209209
):
210210
self._ast = ast
211211
self._table_aliases = table_aliases
@@ -548,6 +548,7 @@ def _handle_cte(self, cte: exp.CTE, depth: int) -> None:
548548
body = cte.this
549549

550550
if has_col_defs and body and isinstance(body, exp.Select):
551+
assert table_alias is not None # guarded by has_col_defs
551552
body_cols = self._flat_columns(body)
552553
real_cols = [x for x in body_cols if x != "*"]
553554
cte_col_names = [col.name for col in table_alias.columns]
@@ -615,7 +616,7 @@ def _flat_columns(self, node: exp.Expression) -> list:
615616
cols = []
616617
if node is None:
617618
return cols
618-
seen_stars = set()
619+
seen_stars: set[int] = set()
619620
for child in _dfs(node):
620621
name = self._collect_column_from_node(child, seen_stars)
621622
if name is not None:

sql_metadata/comments.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def extract_comments(sql: str) -> List[str]:
9292
tokens = list(_choose_tokenizer(sql).tokenize(sql))
9393
except Exception:
9494
return []
95-
comments = []
95+
comments: list[str] = []
9696
prev_end = -1
9797
for tok in tokens:
9898
_scan_gap(sql, prev_end + 1, tok.start, comments)

sql_metadata/dialect_parser.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ class so that callers only need to call :meth:`DialectParser.parse`.
66
"""
77

88
import logging
9+
from typing import Optional
910

1011
import sqlglot
1112
from sqlglot import Dialect, exp
1213
from sqlglot.dialects.tsql import TSQL
1314
from sqlglot.errors import ParseError, TokenError
14-
from sqlglot.tokens import Tokenizer
15+
from sqlglot.tokens import Tokenizer as BaseTokenizer
1516

1617
from sql_metadata.comments import _has_hash_variables
1718

@@ -37,12 +38,12 @@ class HashVarDialect(Dialect):
3738
of a ``VAR`` token instead.
3839
"""
3940

40-
class Tokenizer(Tokenizer):
41+
class Tokenizer(BaseTokenizer):
4142
"""Tokenizer subclass that includes ``#`` in variable tokens."""
4243

43-
SINGLE_TOKENS = {**Tokenizer.SINGLE_TOKENS}
44+
SINGLE_TOKENS = {**BaseTokenizer.SINGLE_TOKENS}
4445
SINGLE_TOKENS.pop("#", None)
45-
VAR_SINGLE_TOKENS = {*Tokenizer.VAR_SINGLE_TOKENS, "#"}
46+
VAR_SINGLE_TOKENS = {*BaseTokenizer.VAR_SINGLE_TOKENS, "#"}
4647

4748

4849
class BracketedTableDialect(TSQL):
@@ -63,7 +64,7 @@ class BracketedTableDialect(TSQL):
6364
class DialectParser:
6465
"""Detect the appropriate sqlglot dialect and parse SQL into an AST."""
6566

66-
def parse(self, clean_sql: str) -> tuple:
67+
def parse(self, clean_sql: str) -> tuple[exp.Expression, object]:
6768
"""Parse *clean_sql*, returning ``(ast, dialect)``.
6869
6970
Detects candidate dialects via heuristics, tries each in order,
@@ -112,7 +113,9 @@ def _detect_dialects(sql: str) -> list:
112113

113114
# -- parsing ------------------------------------------------------------
114115

115-
def _try_dialects(self, clean_sql: str, dialects: list) -> tuple:
116+
def _try_dialects(
117+
self, clean_sql: str, dialects: list
118+
) -> tuple[exp.Expression, object]:
116119
"""Try parsing *clean_sql* with each dialect, returning the best.
117120
118121
:returns: 2-tuple of ``(ast_node, winning_dialect)``.
@@ -141,7 +144,7 @@ def _try_dialects(self, clean_sql: str, dialects: list) -> tuple:
141144
raise ValueError("This query is wrong")
142145

143146
@staticmethod
144-
def _parse_with_dialect(clean_sql: str, dialect) -> exp.Expression:
147+
def _parse_with_dialect(clean_sql: str, dialect) -> Optional[exp.Expression]:
145148
"""Parse *clean_sql* with a single dialect, suppressing warnings."""
146149
logger = logging.getLogger("sqlglot")
147150
old_level = logger.level
@@ -158,9 +161,13 @@ def _parse_with_dialect(clean_sql: str, dialect) -> exp.Expression:
158161
if not results or results[0] is None:
159162
return None
160163
result = results[0]
164+
if result is None:
165+
return None
161166
if isinstance(result, exp.Subquery) and not result.alias:
162-
result = result.this
163-
return result
167+
inner = result.this
168+
if isinstance(inner, exp.Expression):
169+
return inner
170+
return result # type: ignore[return-value]
164171

165172
# -- quality checks -----------------------------------------------------
166173

0 commit comments

Comments
 (0)