Skip to content

Commit d7d34c9

Browse files
committed
2 parents 5ae78d3 + f96cbb1 commit d7d34c9

File tree

9 files changed

+127
-101
lines changed

9 files changed

+127
-101
lines changed

.pre-commit-config.yaml

+8-35
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v4.6.0
3+
rev: v5.0.0
44
hooks:
55
# Identify invalid files
66
- id: check-ast
@@ -27,42 +27,15 @@ repos:
2727
args: [--markdown-linebreak-ext=md]
2828
- id: end-of-file-fixer
2929

30-
- repo: https://github.com/PyCQA/autoflake
31-
rev: v2.3.1
30+
- repo: https://github.com/astral-sh/ruff-pre-commit
31+
rev: v0.9.4
3232
hooks:
33-
- id: autoflake
34-
35-
- repo: https://github.com/asottile/pyupgrade
36-
rev: v3.15.2
37-
hooks:
38-
- id: pyupgrade
39-
args:
40-
- '--py39-plus'
41-
42-
- repo: https://github.com/pycqa/isort
43-
rev: 5.13.2
44-
hooks:
45-
- id: isort
46-
name: isort
47-
args:
48-
- '.'
49-
50-
- repo: https://github.com/psf/black
51-
rev: 24.4.2
52-
hooks:
53-
- id: black
54-
55-
- repo: https://github.com/pycqa/flake8
56-
rev: 7.0.0
57-
hooks:
58-
- id: flake8
59-
additional_dependencies:
60-
- 'Flake8-pyproject~=1.1'
61-
args:
62-
- '.'
33+
- id: ruff
34+
args: [ --fix ]
35+
- id: ruff-format
6336

6437
- repo: https://github.com/pre-commit/mirrors-mypy
65-
rev: v1.3.0
38+
rev: v1.14.1
6639
hooks:
6740
- id: mypy
6841
args:
@@ -74,7 +47,7 @@ repos:
7447
exclude: ^tests/
7548

7649
- repo: https://github.com/igorshubovych/markdownlint-cli
77-
rev: v0.40.0
50+
rev: v0.44.0
7851
hooks:
7952
- id: markdownlint
8053
args:

pydantic_glue/cli.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
"""CLI to convert pydantic class to AWS Glue schema."""
2+
13
import json
24
import logging
3-
import os
45
import sys
56
from argparse import ArgumentParser
67
from dataclasses import dataclass
@@ -15,6 +16,8 @@
1516

1617
@dataclass
1718
class Arguments:
19+
"""CLI Arguments."""
20+
1821
module_file: str
1922
class_name: str
2023
output_file: str
@@ -23,6 +26,7 @@ class Arguments:
2326

2427

2528
def parse_args(argv: list[str]) -> Arguments:
29+
"""Parse CLI arguments."""
2630
parser = ArgumentParser()
2731
parser.add_argument("-f", dest="source_file", required=True, type=str, help="Path to the python file")
2832
parser.add_argument("-c", dest="class_name", required=True, type=str, help="Python class name")
@@ -49,9 +53,10 @@ def parse_args(argv: list[str]) -> Arguments:
4953

5054

5155
def cli() -> None:
56+
"""CLI entry point."""
5257
args = parse_args(sys.argv[1:])
53-
sys.path.append(os.path.dirname(args.module_file))
54-
imported = __import__(os.path.basename(args.module_file))
58+
sys.path.append(Path(args.module_file).parent.as_posix())
59+
imported = __import__(Path(args.module_file).name)
5560

5661
model = getattr(imported, args.class_name)
5762
input_schema = json.dumps(model.model_json_schema(by_alias=args.json_schema_by_alias))
@@ -60,7 +65,7 @@ def cli() -> None:
6065
output = json.dumps(
6166
{
6267
"//": f"Generated by pydantic-glue at {datetime.now(tz=timezone.utc)}. DO NOT EDIT",
63-
"columns": {k: v for (k, v) in converted},
68+
"columns": dict(converted),
6469
},
6570
indent=2,
6671
)

pydantic_glue/errors.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""Errors."""
2+
3+
4+
class ObjectWithoutPropertiesError(Exception):
5+
def __init__(self) -> None:
6+
super().__init__("Object without properties or additionalProperties can't be represented")
7+
8+
9+
class UnknownTypeError(Exception):
10+
def __init__(self, type_name: str) -> None:
11+
super().__init__(f"Unknown type: {type_name}")
12+
13+
14+
class GlueMapWithoutTypesError(Exception):
15+
def __init__(self) -> None:
16+
super().__init__("Glue Cannot Support a Map Without Types")

pydantic_glue/handler.py

+28-22
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
1+
"""Convert Json schema to glue."""
2+
3+
from __future__ import annotations
4+
15
from typing import Any, Union
26

37
import jsonref
48

9+
from pydantic_glue.errors import GlueMapWithoutTypesError, ObjectWithoutPropertiesError, UnknownTypeError
510

6-
def dispatch(v: dict[str, Any]) -> str:
7-
8-
glue_type = v.get("glue_type", None)
911

10-
if glue_type is not None:
12+
def dispatch(v: dict[str, Any]) -> str: # noqa: PLR0911
13+
"""Dispatch json schema element as glue type."""
14+
if glue_type := v.get("glue_type"):
1115
return str(glue_type)
1216

1317
if "anyOf" in v:
14-
return handle_union(v)
18+
return _handle_union(v)
1519

1620
t = v["type"]
1721

1822
if t == "object":
19-
return handle_object(v)
23+
return _handle_object(v)
2024

2125
if t == "array":
22-
return handle_array(v)
26+
return _handle_array(v)
2327

2428
if t == "string":
2529
if v.get("format") == "date-time":
@@ -37,53 +41,55 @@ def dispatch(v: dict[str, Any]) -> str:
3741
if t == "number":
3842
return "float"
3943

40-
raise Exception(f"unknown type: {t}")
44+
raise UnknownTypeError(t)
4145

4246

43-
def handle_map(o: dict[str, Any]) -> str:
47+
def _handle_map(o: dict[str, Any]) -> str:
4448
t = o["additionalProperties"]
4549
res = dispatch(t)
4650
return f"map<string,{res}>"
4751

4852

49-
def handle_union(o: dict[str, Any]) -> str:
53+
def _handle_union(o: dict[str, Any]) -> str:
5054
types = [i for i in o["anyOf"] if i["type"] != "null"]
5155
if len(types) > 1:
5256
res = [dispatch(v) for v in types]
5357
return f"union<{','.join(res)}>"
5458
return dispatch(types[0])
5559

5660

57-
def map_dispatch(o: dict[str, Any]) -> list[tuple[str, str]]:
61+
def _map_dispatch(o: dict[str, Any]) -> list[tuple[str, str]]:
5862
return [(k, dispatch(v)) for k, v in o["properties"].items()]
5963

6064

61-
def handle_object(o: dict[str, Any]) -> str:
65+
def _handle_object(o: dict[str, Any]) -> str:
6266
if "additionalProperties" in o:
6367
if o["additionalProperties"] is True:
64-
raise Exception("Glue Cannot Support a Map Without Types")
65-
elif o["additionalProperties"]:
68+
raise GlueMapWithoutTypesError
69+
if o["additionalProperties"]:
6670
if "properties" in o:
67-
raise NotImplementedError("Merging types of properties and additionalProperties")
68-
return handle_map(o)
71+
msg = "Merging types of properties and additionalProperties"
72+
raise NotImplementedError(msg)
73+
return _handle_map(o)
6974

7075
if "properties" not in o:
71-
raise Exception("Object without properties or additionalProperties can't be represented")
76+
raise ObjectWithoutPropertiesError
7277

73-
res = [f"{k}:{v}" for (k, v) in map_dispatch(o)]
78+
res = [f"{k}:{v}" for (k, v) in _map_dispatch(o)]
7479
return f"struct<{','.join(res)}>"
7580

7681

77-
def handle_array(o: dict[str, Any]) -> str:
82+
def _handle_array(o: dict[str, Any]) -> str:
7883
t = dispatch(o["items"])
7984
return f"array<{t}>"
8085

8186

82-
def handle_root(o: dict[str, Any]) -> list[tuple[str, str]]:
83-
return map_dispatch(o)
87+
def _handle_root(o: dict[str, Any]) -> list[tuple[str, str]]:
88+
return _map_dispatch(o)
8489

8590

8691
def convert(schema: str) -> Union[list[Any], list[tuple[str, str]]]:
92+
"""Convert json schema to glue."""
8793
if not schema:
8894
return []
89-
return handle_root(jsonref.loads(schema))
95+
return _handle_root(jsonref.loads(schema))

pyproject.toml

+54-28
Original file line numberDiff line numberDiff line change
@@ -21,46 +21,72 @@ jsonref = "^1.1.0"
2121
pydantic = "^2.7.1"
2222

2323
[tool.poetry.group.dev.dependencies]
24-
autoflake = "^2.3.1"
2524
pytest = "^8.2.0"
25+
cli-test-helpers = "^4.1.0"
2626
mypy = "^1.10.0"
27-
flake8 = "^7.0.0"
28-
black = "^24.4.2"
29-
isort = "^5.13.2"
3027
pre-commit = "^3.7.0"
31-
cli-test-helpers = "^4.1.0"
32-
33-
[tool.autoflake]
34-
recursive = true
35-
in-place = true
36-
remove-all-unused-imports = true
37-
remove-unused-variables = true
28+
ruff = "^0.9.4"
29+
pytest-cov = "^6.0.0"
3830

39-
[tool.black]
31+
[tool.ruff]
4032
line-length = 120
41-
target-version = ['py39', 'py310', 'py311', 'py312']
42-
include = '\.pyi?$'
33+
indent-width = 4
34+
target-version = "py39"
4335

44-
[tool.flake8]
45-
files = '.*\.py'
46-
max-line-length = 120
47-
exclude = ['.git', '.eggs', '__pycache__', 'venv', '.venv']
36+
[tool.ruff.lint]
37+
select = ["ALL"]
4838
ignore = [
49-
# space before: (needed for how black formats slicing)
50-
'E203',
51-
# line break before binary operator (needed for how black formats long lines)
52-
'W503'
39+
# The following rules may cause conflicts when used with the formatter
40+
"COM812",
41+
"ISC001",
42+
43+
# General ignores
44+
"ANN204", # Missing return type annotation for special method `__init__`
45+
"D107", # Missing docstring in `__init__`
46+
"D203", # one-blank-line-before-class
47+
"D213", # multi-line-summary-second-line
48+
"G004", # Logging statement uses f-string
49+
"UP007", # Use `X | Y` for type annotations
5350
]
5451

55-
[tool.isort]
56-
profile = 'black'
57-
src_paths = ['athena_udf', 'test']
52+
fixable = ["ALL"]
53+
unfixable = []
54+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
5855

59-
[tool.pytest.ini_options]
60-
testpaths = [
61-
"tests"
56+
[tool.ruff.lint.per-file-ignores]
57+
"**/{tests}/*" = [
58+
"ANN001", # Missing type annotation for function argument
59+
"ANN201", # Missing return type annotation for public function
60+
"D100", # Missing docstring in public module
61+
"D101", # Missing docstring in public class
62+
"D103", # Missing docstring in public function
63+
"S101", # Use of `assert` detected
64+
"S603", # `subprocess` call: check for execution of untrusted input
65+
]
66+
"__init__.py" = ["D104"] # Missing docstring in public package
67+
68+
"pydantic_glue/errors.py" = [
69+
"D101", # Missing docstring in public class
6270
]
6371

72+
[tool.ruff.lint.flake8-type-checking]
73+
runtime-evaluated-base-classes = ["pydantic.BaseModel"]
74+
75+
[tool.ruff.lint.mccabe]
76+
max-complexity = 15
77+
78+
[tool.ruff.format]
79+
quote-style = "double"
80+
indent-style = "space"
81+
skip-magic-trailing-comma = false
82+
line-ending = "auto"
83+
docstring-code-format = false
84+
docstring-code-line-length = "dynamic"
85+
86+
[tool.pytest.ini_options]
87+
testpaths = ["tests"]
88+
addopts = "--cov"
89+
6490
[tool.mypy]
6591
ignore_missing_imports = true
6692
strict = true

tests/data/__init__.py

Whitespace-only changes.

tests/unit/__init__.py

Whitespace-only changes.

tests/unit/test_cli.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import json
22
from pathlib import Path
3-
from unittest import TestCase
43

54
from cli_test_helpers import shell
5+
66
from pydantic_glue.cli import parse_args
77

88

99
def test_parse_args():
10-
1110
args = parse_args(["-f", "foo/bar/file.py", "-c", "Test"])
1211
assert args.module_file == "foo/bar/file"
1312
assert args.class_name == "Test"
@@ -17,7 +16,6 @@ def test_parse_args():
1716

1817

1918
def test_parse_args_schema_by_name():
20-
2119
args = parse_args(["-f", "foo/bar/file.py", "-c", "Test", "--schema-by-name"])
2220
assert args.module_file == "foo/bar/file"
2321
assert args.class_name == "Test"
@@ -30,11 +28,11 @@ def test_cli():
3028
shell("pydantic-glue -f tests/data/input.py -c A -o tests/tmp/actual.json")
3129
actual = json.loads(Path("tests/tmp/actual.json").read_text())
3230
expected = json.loads(Path("tests/data/expected.json").read_text())
33-
TestCase().assertDictEqual(actual["columns"], expected["columns"])
31+
assert actual["columns"] == expected["columns"]
3432

3533

3634
def test_cli_schema_by_name():
3735
shell("pydantic-glue -f tests/data/input.py -c A -o tests/tmp/actual_by_name.json --schema-by-name")
3836
actual = json.loads(Path("tests/tmp/actual_by_name.json").read_text())
3937
expected = json.loads(Path("tests/data/expected_by_name.json").read_text())
40-
TestCase().assertDictEqual(actual["columns"], expected["columns"])
38+
assert actual["columns"] == expected["columns"]

0 commit comments

Comments
 (0)