Skip to content

Commit 8f25263

Browse files
committed
Use result instead of exceptions
1 parent 1a403d6 commit 8f25263

File tree

13 files changed

+429
-282
lines changed

13 files changed

+429
-282
lines changed

poetry.lock

Lines changed: 15 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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pyyaml = ">=5.4"
3232
loguru = ">=0.7"
3333
typing-extensions = ">=4.6"
3434
greenlet = ">=3"
35+
result = "^0.17.0"
3536

3637
[tool.poetry.group.dev.dependencies]
3738
mypy = "^1.11"

sqlalchemy_to_json_schema/command/driver.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Any, Callable, Optional, Union, cast
88

99
import yaml
10+
from result import Err, Ok, Result
1011
from sqlalchemy.ext.declarative import DeclarativeMeta
1112

1213
from sqlalchemy_to_json_schema.command.transformer import (
@@ -57,7 +58,9 @@ def __init__(self, walker: Walker, decision: Decision, layout: Layout, /):
5758

5859
def build_transformer(
5960
self, walker: Walker, decision: Decision, layout: Layout, /
60-
) -> Callable[[Iterable[Union[ModuleType, DeclarativeMeta]], Optional[int]], Schema]:
61+
) -> Callable[
62+
[Iterable[Union[ModuleType, DeclarativeMeta]], Optional[int]], Result[Schema, str]
63+
]:
6164
walker_factory = WALKER_MAP[walker]
6265
relation_decision = DECISION_MAP[decision]()
6366
schema_factory = SchemaFactory(walker_factory, relation_decision=relation_decision)
@@ -73,7 +76,7 @@ def run(
7376
filename: Optional[Path] = None,
7477
format: Optional[Format] = None,
7578
depth: Optional[int] = None,
76-
) -> None:
79+
) -> Result[None, str]:
7780
modules_and_types = (load_module_or_symbol(target) for target in targets)
7881
modules_and_models = cast(
7982
Iterator[Union[ModuleType, DeclarativeMeta]],
@@ -84,8 +87,14 @@ def run(
8487
),
8588
)
8689

87-
result = self.transformer(modules_and_models, depth)
88-
self.dump(result, filename=filename, format=format)
90+
schema = self.transformer(modules_and_models, depth)
91+
92+
if schema.is_err():
93+
return Err(schema.unwrap_err())
94+
95+
self.dump(schema.unwrap(), filename=filename, format=format)
96+
97+
return Ok(None)
8998

9099
def dump(
91100
self,

sqlalchemy_to_json_schema/command/transformer.py

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import Optional, Union
66

77
from loguru import logger
8+
from result import Err, Ok, Result
89
from sqlalchemy.ext.declarative import DeclarativeMeta
910
from typing_extensions import TypeGuard
1011

@@ -18,13 +19,13 @@ def __init__(self, schema_factory: SchemaFactory, /):
1819
@abstractmethod
1920
def transform(
2021
self, rawtargets: Iterable[Union[ModuleType, DeclarativeMeta]], depth: Optional[int], /
21-
) -> Schema: ...
22+
) -> Result[Schema, str]: ...
2223

2324

2425
class JSONSchemaTransformer(AbstractTransformer):
2526
def transform(
2627
self, rawtargets: Iterable[Union[ModuleType, DeclarativeMeta]], depth: Optional[int], /
27-
) -> Schema:
28+
) -> Result[Schema, str]:
2829
definitions = {}
2930

3031
for item in rawtargets:
@@ -33,33 +34,46 @@ def transform(
3334
elif inspect.ismodule(item):
3435
partial_definitions = self.transform_by_module(item, depth)
3536
else:
36-
TypeError(f"Expected a class or module, got {item}")
37+
return Err(f"Expected a class or module, got {item}")
3738

38-
definitions.update(partial_definitions)
39+
if partial_definitions.is_err():
40+
return partial_definitions
3941

40-
return definitions
42+
definitions.update(partial_definitions.unwrap())
4143

42-
def transform_by_model(self, model: DeclarativeMeta, depth: Optional[int], /) -> Schema:
44+
return Ok(definitions)
45+
46+
def transform_by_model(
47+
self, model: DeclarativeMeta, depth: Optional[int], /
48+
) -> Result[Schema, str]:
4349
return self.schema_factory(model, depth=depth)
4450

45-
def transform_by_module(self, module: ModuleType, depth: Optional[int], /) -> Schema:
51+
def transform_by_module(
52+
self, module: ModuleType, depth: Optional[int], /
53+
) -> Result[Schema, str]:
4654
subdefinitions = {}
4755
definitions = {}
4856
for basemodel in collect_models(module):
49-
schema = self.schema_factory(basemodel, depth=depth)
57+
schema_result = self.schema_factory(basemodel, depth=depth)
58+
59+
if schema_result.is_err():
60+
return schema_result
61+
62+
schema = schema_result.unwrap()
63+
5064
if "definitions" in schema:
5165
subdefinitions.update(schema.pop("definitions"))
5266
definitions[schema["title"]] = schema
5367
d = {}
5468
d.update(subdefinitions)
5569
d.update(definitions)
56-
return {"definitions": definitions}
70+
return Ok({"definitions": definitions})
5771

5872

5973
class OpenAPI2Transformer(AbstractTransformer):
6074
def transform(
6175
self, rawtargets: Iterable[Union[ModuleType, DeclarativeMeta]], depth: Optional[int], /
62-
) -> Schema:
76+
) -> Result[Schema, str]:
6377
definitions = {}
6478

6579
for target in rawtargets:
@@ -68,29 +82,46 @@ def transform(
6882
elif inspect.ismodule(target):
6983
partial_definitions = self.transform_by_module(target, depth)
7084
else:
71-
raise TypeError(f"Expected a class or module, got {target}")
85+
return Err(f"Expected a class or module, got {target}")
86+
87+
if partial_definitions.is_err():
88+
return partial_definitions
7289

73-
definitions.update(partial_definitions)
90+
definitions.update(partial_definitions.unwrap())
7491

75-
return {"definitions": definitions}
92+
return Ok({"definitions": definitions})
7693

77-
def transform_by_model(self, model: DeclarativeMeta, depth: Optional[int], /) -> Schema:
94+
def transform_by_model(
95+
self, model: DeclarativeMeta, depth: Optional[int], /
96+
) -> Result[Schema, str]:
7897
definitions = {}
79-
schema = self.schema_factory(model, depth=depth)
98+
schema_result = self.schema_factory(model, depth=depth)
99+
100+
if schema_result.is_err():
101+
return schema_result
102+
103+
schema = schema_result.unwrap()
80104

81105
if "definitions" in schema:
82106
definitions.update(schema.pop("definitions"))
83107

84108
definitions[schema["title"]] = schema
85109

86-
return definitions
110+
return Ok(definitions)
87111

88-
def transform_by_module(self, module: ModuleType, depth: Optional[int], /) -> Schema:
112+
def transform_by_module(
113+
self, module: ModuleType, depth: Optional[int], /
114+
) -> Result[Schema, str]:
89115
subdefinitions = {}
90116
definitions = {}
91117

92118
for basemodel in collect_models(module):
93-
schema = self.schema_factory(basemodel, depth=depth)
119+
schema_result = self.schema_factory(basemodel, depth=depth)
120+
121+
if schema_result.is_err():
122+
return schema_result
123+
124+
schema = schema_result.unwrap()
94125

95126
if "definitions" in schema:
96127
subdefinitions.update(schema.pop("definitions"))
@@ -101,7 +132,7 @@ def transform_by_module(self, module: ModuleType, depth: Optional[int], /) -> Sc
101132
d.update(subdefinitions)
102133
d.update(definitions)
103134

104-
return definitions
135+
return Ok(definitions)
105136

106137

107138
class OpenAPI3Transformer(OpenAPI2Transformer):
@@ -118,8 +149,13 @@ def replace_ref(self, d: Union[dict, list], old_prefix: str, new_prefix: str, /)
118149

119150
def transform(
120151
self, rawtargets: Iterable[Union[ModuleType, DeclarativeMeta]], depth: Optional[int], /
121-
) -> Schema:
122-
definitions = super().transform(rawtargets, depth)
152+
) -> Result[Schema, str]:
153+
definitions_result = super().transform(rawtargets, depth)
154+
155+
if definitions_result.is_err():
156+
return Err(definitions_result.unwrap_err())
157+
158+
definitions = definitions_result.unwrap()
123159

124160
self.replace_ref(definitions, "#/definitions/", "#/components/schemas/")
125161

@@ -128,7 +164,8 @@ def transform(
128164
if "schemas" not in definitions["components"]:
129165
definitions["components"]["schemas"] = {}
130166
definitions["components"]["schemas"] = definitions.pop("definitions", {})
131-
return definitions
167+
168+
return Ok(definitions)
132169

133170

134171
def collect_models(module: ModuleType, /) -> Iterator[DeclarativeMeta]:

sqlalchemy_to_json_schema/decisions.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from collections.abc import Iterator
33
from typing import Any, Union
44

5+
from result import Err, Ok, Result
56
from sqlalchemy.orm import MapperProperty
67
from sqlalchemy.orm.base import MANYTOMANY, MANYTOONE
78
from sqlalchemy.orm.properties import ColumnProperty
@@ -24,7 +25,7 @@ def decision(
2425
/,
2526
*,
2627
toplevel: bool = False,
27-
) -> Iterator[DecisionResult]:
28+
) -> Iterator[Result[DecisionResult, MapperProperty]]:
2829
pass
2930

3031

@@ -36,13 +37,13 @@ def decision(
3637
/,
3738
*,
3839
toplevel: bool = False,
39-
) -> Iterator[DecisionResult]:
40+
) -> Iterator[Result[DecisionResult, MapperProperty]]:
4041
if hasattr(prop, "mapper"):
41-
yield ColumnPropertyType.RELATIONSHIP, prop, {}
42+
yield Ok((ColumnPropertyType.RELATIONSHIP, prop, {}))
4243
elif hasattr(prop, "columns"):
43-
yield ColumnPropertyType.FOREIGNKEY, prop, {}
44+
yield Ok((ColumnPropertyType.FOREIGNKEY, prop, {}))
4445
else:
45-
raise NotImplementedError(prop)
46+
yield Err(prop)
4647

4748

4849
class UseForeignKeyIfPossibleDecision(AbstractDecision):
@@ -53,32 +54,42 @@ def decision(
5354
/,
5455
*,
5556
toplevel: bool = False,
56-
) -> Iterator[DecisionResult]:
57+
) -> Iterator[Result[DecisionResult, MapperProperty]]:
5758
if hasattr(prop, "mapper"):
5859
if prop.direction == MANYTOONE:
5960
if toplevel:
6061
for c in prop.local_columns:
61-
yield ColumnPropertyType.FOREIGNKEY, walker.mapper._props[c.name], {
62-
"relation": prop.key
63-
}
62+
yield Ok(
63+
(
64+
ColumnPropertyType.FOREIGNKEY,
65+
walker.mapper._props[c.name],
66+
{"relation": prop.key},
67+
)
68+
)
6469
else:
6570
rp = walker.history[0]
6671
if prop.local_columns != rp.remote_side:
6772
for c in prop.local_columns:
68-
yield ColumnPropertyType.FOREIGNKEY, walker.mapper._props[c.name], {
69-
"relation": prop.key
70-
}
73+
yield Ok(
74+
(
75+
ColumnPropertyType.FOREIGNKEY,
76+
walker.mapper._props[c.name],
77+
{"relation": prop.key},
78+
)
79+
)
7180
elif prop.direction == MANYTOMANY:
7281
# logger.warning("skip mapper=%s, prop=%s is many to many.", walker.mapper, prop)
7382
# fixme: this must return a ColumnPropertyType member
74-
yield (
75-
{"type": "array", "items": {"type": "string"}}, # type: ignore[misc]
76-
prop,
77-
{},
83+
yield Ok(
84+
( # type: ignore[arg-type]
85+
{"type": "array", "items": {"type": "string"}},
86+
prop,
87+
{},
88+
)
7889
)
7990
else:
80-
yield ColumnPropertyType.RELATIONSHIP, prop, {}
91+
yield Ok((ColumnPropertyType.RELATIONSHIP, prop, {}))
8192
elif hasattr(prop, "columns"):
82-
yield ColumnPropertyType.FOREIGNKEY, prop, {}
93+
yield Ok((ColumnPropertyType.FOREIGNKEY, prop, {}))
8394
else:
84-
raise NotImplementedError(prop)
95+
yield Err(prop)

0 commit comments

Comments
 (0)