-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
use result #191
base: main
Are you sure you want to change the base?
use result #191
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
from typing import Any, Callable, Optional, Union, cast | ||
|
||
import yaml | ||
from result import Err, Ok, Result | ||
from sqlalchemy.ext.declarative import DeclarativeMeta | ||
|
||
from sqlalchemy_to_json_schema.command.transformer import ( | ||
|
@@ -57,7 +58,9 @@ def __init__(self, walker: Walker, decision: Decision, layout: Layout, /): | |
|
||
def build_transformer( | ||
self, walker: Walker, decision: Decision, layout: Layout, / | ||
) -> Callable[[Iterable[Union[ModuleType, DeclarativeMeta]], Optional[int]], Schema]: | ||
) -> Callable[ | ||
[Iterable[Union[ModuleType, DeclarativeMeta]], Optional[int]], Result[Schema, str] | ||
]: | ||
walker_factory = WALKER_MAP[walker] | ||
relation_decision = DECISION_MAP[decision]() | ||
schema_factory = SchemaFactory(walker_factory, relation_decision=relation_decision) | ||
|
@@ -73,7 +76,7 @@ def run( | |
filename: Optional[Path] = None, | ||
format: Optional[Format] = None, | ||
depth: Optional[int] = None, | ||
) -> None: | ||
) -> Result[None, str]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion LGTM: Improved error handling in run method The However, there's still room for improvement in handling potential errors from Consider updating the def dump(self, ...) -> Result[None, str]:
try:
# Existing implementation
return Ok(None)
except Exception as e:
return Err(str(e))
def run(self, ...):
# Existing implementation
dump_result = self.dump(schema.unwrap(), filename=filename, format=format)
if dump_result.is_err():
return Err(dump_result.unwrap_err())
return Ok(None) This change would ensure that any errors occurring during the dump process are also captured and propagated correctly. Also applies to: 90-97 |
||
modules_and_types = (load_module_or_symbol(target) for target in targets) | ||
modules_and_models = cast( | ||
Iterator[Union[ModuleType, DeclarativeMeta]], | ||
|
@@ -84,8 +87,14 @@ def run( | |
), | ||
) | ||
|
||
result = self.transformer(modules_and_models, depth) | ||
self.dump(result, filename=filename, format=format) | ||
schema = self.transformer(modules_and_models, depth) | ||
|
||
if schema.is_err(): | ||
return Err(schema.unwrap_err()) | ||
|
||
self.dump(schema.unwrap(), filename=filename, format=format) | ||
|
||
return Ok(None) | ||
Comment on lines
+90
to
+97
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider handling potential exceptions from the While the Proposed Change to Modify the def dump(
self,
data: dict[str, Any],
/,
*,
filename: Optional[Path] = None,
format: Optional[Format] = None,
- ) -> None:
+ ) -> Result[None, str]:
try:
dump_function = yaml.dump if format == Format.YAML else json.dump
if filename is None:
output_stream = sys.stdout
else:
output_stream = filename.open("w")
dump_function(data, output_stream) # type: ignore[operator]
+ return Ok(None)
except Exception as e:
+ return Err(str(e)) Update schema = self.transformer(modules_and_models, depth)
if schema.is_err():
return Err(schema.unwrap_err())
- self.dump(schema.unwrap(), filename=filename, format=format)
+ dump_result = self.dump(schema.unwrap(), filename=filename, format=format)
+ if dump_result.is_err():
+ return Err(dump_result.unwrap_err())
return Ok(None) This ensures that any errors from
|
||
|
||
def dump( | ||
self, | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -5,6 +5,7 @@ | |||||
from typing import Optional, Union | ||||||
|
||||||
from loguru import logger | ||||||
from result import Err, Ok, Result | ||||||
from sqlalchemy.ext.declarative import DeclarativeMeta | ||||||
from typing_extensions import TypeGuard | ||||||
|
||||||
|
@@ -18,13 +19,13 @@ def __init__(self, schema_factory: SchemaFactory, /): | |||||
@abstractmethod | ||||||
def transform( | ||||||
self, rawtargets: Iterable[Union[ModuleType, DeclarativeMeta]], depth: Optional[int], / | ||||||
) -> Schema: ... | ||||||
) -> Result[Schema, str]: ... | ||||||
|
||||||
|
||||||
class JSONSchemaTransformer(AbstractTransformer): | ||||||
def transform( | ||||||
self, rawtargets: Iterable[Union[ModuleType, DeclarativeMeta]], depth: Optional[int], / | ||||||
) -> Schema: | ||||||
) -> Result[Schema, str]: | ||||||
definitions = {} | ||||||
|
||||||
for item in rawtargets: | ||||||
|
@@ -33,33 +34,46 @@ def transform( | |||||
elif inspect.ismodule(item): | ||||||
partial_definitions = self.transform_by_module(item, depth) | ||||||
else: | ||||||
TypeError(f"Expected a class or module, got {item}") | ||||||
return Err(f"Expected a class or module, got {item}") | ||||||
|
||||||
definitions.update(partial_definitions) | ||||||
if partial_definitions.is_err(): | ||||||
return partial_definitions | ||||||
|
||||||
return definitions | ||||||
definitions.update(partial_definitions.unwrap()) | ||||||
|
||||||
def transform_by_model(self, model: DeclarativeMeta, depth: Optional[int], /) -> Schema: | ||||||
return Ok(definitions) | ||||||
|
||||||
def transform_by_model( | ||||||
self, model: DeclarativeMeta, depth: Optional[int], / | ||||||
) -> Result[Schema, str]: | ||||||
return self.schema_factory(model, depth=depth) | ||||||
|
||||||
def transform_by_module(self, module: ModuleType, depth: Optional[int], /) -> Schema: | ||||||
def transform_by_module( | ||||||
self, module: ModuleType, depth: Optional[int], / | ||||||
) -> Result[Schema, str]: | ||||||
subdefinitions = {} | ||||||
definitions = {} | ||||||
for basemodel in collect_models(module): | ||||||
schema = self.schema_factory(basemodel, depth=depth) | ||||||
schema_result = self.schema_factory(basemodel, depth=depth) | ||||||
|
||||||
if schema_result.is_err(): | ||||||
return schema_result | ||||||
|
||||||
schema = schema_result.unwrap() | ||||||
|
||||||
Comment on lines
+58
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Refactor duplicated code into a shared method The code handling schema_result = self.schema_factory(basemodel, depth=depth)
if schema_result.is_err():
return schema_result
schema = schema_result.unwrap() Consider refactoring this logic into a shared helper method or moving it to the Also applies to: 119-124 |
||||||
if "definitions" in schema: | ||||||
subdefinitions.update(schema.pop("definitions")) | ||||||
definitions[schema["title"]] = schema | ||||||
d = {} | ||||||
d.update(subdefinitions) | ||||||
d.update(definitions) | ||||||
return {"definitions": definitions} | ||||||
return Ok({"definitions": definitions}) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent wrapping of 'definitions' in the return statement In Apply this diff to align the return statement with other methods: - return Ok({"definitions": definitions})
+ return Ok(definitions) 📝 Committable suggestion
Suggested change
|
||||||
|
||||||
|
||||||
class OpenAPI2Transformer(AbstractTransformer): | ||||||
def transform( | ||||||
self, rawtargets: Iterable[Union[ModuleType, DeclarativeMeta]], depth: Optional[int], / | ||||||
) -> Schema: | ||||||
) -> Result[Schema, str]: | ||||||
definitions = {} | ||||||
|
||||||
for target in rawtargets: | ||||||
|
@@ -68,29 +82,46 @@ def transform( | |||||
elif inspect.ismodule(target): | ||||||
partial_definitions = self.transform_by_module(target, depth) | ||||||
else: | ||||||
raise TypeError(f"Expected a class or module, got {target}") | ||||||
return Err(f"Expected a class or module, got {target}") | ||||||
|
||||||
if partial_definitions.is_err(): | ||||||
return partial_definitions | ||||||
|
||||||
definitions.update(partial_definitions) | ||||||
definitions.update(partial_definitions.unwrap()) | ||||||
|
||||||
return {"definitions": definitions} | ||||||
return Ok({"definitions": definitions}) | ||||||
|
||||||
def transform_by_model(self, model: DeclarativeMeta, depth: Optional[int], /) -> Schema: | ||||||
def transform_by_model( | ||||||
self, model: DeclarativeMeta, depth: Optional[int], / | ||||||
) -> Result[Schema, str]: | ||||||
definitions = {} | ||||||
schema = self.schema_factory(model, depth=depth) | ||||||
schema_result = self.schema_factory(model, depth=depth) | ||||||
|
||||||
if schema_result.is_err(): | ||||||
return schema_result | ||||||
|
||||||
schema = schema_result.unwrap() | ||||||
|
||||||
if "definitions" in schema: | ||||||
definitions.update(schema.pop("definitions")) | ||||||
|
||||||
definitions[schema["title"]] = schema | ||||||
|
||||||
return definitions | ||||||
return Ok(definitions) | ||||||
|
||||||
def transform_by_module(self, module: ModuleType, depth: Optional[int], /) -> Schema: | ||||||
def transform_by_module( | ||||||
self, module: ModuleType, depth: Optional[int], / | ||||||
) -> Result[Schema, str]: | ||||||
subdefinitions = {} | ||||||
definitions = {} | ||||||
|
||||||
for basemodel in collect_models(module): | ||||||
schema = self.schema_factory(basemodel, depth=depth) | ||||||
schema_result = self.schema_factory(basemodel, depth=depth) | ||||||
|
||||||
if schema_result.is_err(): | ||||||
return schema_result | ||||||
|
||||||
schema = schema_result.unwrap() | ||||||
|
||||||
if "definitions" in schema: | ||||||
subdefinitions.update(schema.pop("definitions")) | ||||||
|
@@ -101,7 +132,7 @@ def transform_by_module(self, module: ModuleType, depth: Optional[int], /) -> Sc | |||||
d.update(subdefinitions) | ||||||
d.update(definitions) | ||||||
|
||||||
return definitions | ||||||
return Ok(definitions) | ||||||
|
||||||
|
||||||
class OpenAPI3Transformer(OpenAPI2Transformer): | ||||||
|
@@ -118,8 +149,13 @@ def replace_ref(self, d: Union[dict, list], old_prefix: str, new_prefix: str, /) | |||||
|
||||||
def transform( | ||||||
self, rawtargets: Iterable[Union[ModuleType, DeclarativeMeta]], depth: Optional[int], / | ||||||
) -> Schema: | ||||||
definitions = super().transform(rawtargets, depth) | ||||||
) -> Result[Schema, str]: | ||||||
definitions_result = super().transform(rawtargets, depth) | ||||||
|
||||||
if definitions_result.is_err(): | ||||||
return Err(definitions_result.unwrap_err()) | ||||||
Comment on lines
+155
to
+156
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Simplify error propagation in In if definitions_result.is_err():
return Err(definitions_result.unwrap_err()) You can simplify this by returning the if definitions_result.is_err():
return definitions_result This approach maintains consistency with other methods and reduces unnecessary code. |
||||||
|
||||||
definitions = definitions_result.unwrap() | ||||||
|
||||||
self.replace_ref(definitions, "#/definitions/", "#/components/schemas/") | ||||||
|
||||||
|
@@ -128,7 +164,8 @@ def transform( | |||||
if "schemas" not in definitions["components"]: | ||||||
definitions["components"]["schemas"] = {} | ||||||
definitions["components"]["schemas"] = definitions.pop("definitions", {}) | ||||||
return definitions | ||||||
|
||||||
return Ok(definitions) | ||||||
|
||||||
|
||||||
def collect_models(module: ModuleType, /) -> Iterator[DeclarativeMeta]: | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
from collections.abc import Iterator | ||
from typing import Any, Union | ||
|
||
from result import Err, Ok, Result | ||
from sqlalchemy.orm import MapperProperty | ||
from sqlalchemy.orm.base import MANYTOMANY, MANYTOONE | ||
from sqlalchemy.orm.properties import ColumnProperty | ||
|
@@ -24,7 +25,7 @@ def decision( | |
/, | ||
*, | ||
toplevel: bool = False, | ||
) -> Iterator[DecisionResult]: | ||
) -> Iterator[Result[DecisionResult, MapperProperty]]: | ||
pass | ||
|
||
|
||
|
@@ -36,13 +37,13 @@ def decision( | |
/, | ||
*, | ||
toplevel: bool = False, | ||
) -> Iterator[DecisionResult]: | ||
) -> Iterator[Result[DecisionResult, MapperProperty]]: | ||
if hasattr(prop, "mapper"): | ||
yield ColumnPropertyType.RELATIONSHIP, prop, {} | ||
yield Ok((ColumnPropertyType.RELATIONSHIP, prop, {})) | ||
elif hasattr(prop, "columns"): | ||
yield ColumnPropertyType.FOREIGNKEY, prop, {} | ||
yield Ok((ColumnPropertyType.FOREIGNKEY, prop, {})) | ||
else: | ||
raise NotImplementedError(prop) | ||
yield Err(prop) | ||
|
||
|
||
class UseForeignKeyIfPossibleDecision(AbstractDecision): | ||
|
@@ -53,32 +54,42 @@ def decision( | |
/, | ||
*, | ||
toplevel: bool = False, | ||
) -> Iterator[DecisionResult]: | ||
) -> Iterator[Result[DecisionResult, MapperProperty]]: | ||
if hasattr(prop, "mapper"): | ||
if prop.direction == MANYTOONE: | ||
if toplevel: | ||
for c in prop.local_columns: | ||
yield ColumnPropertyType.FOREIGNKEY, walker.mapper._props[c.name], { | ||
"relation": prop.key | ||
} | ||
yield Ok( | ||
( | ||
ColumnPropertyType.FOREIGNKEY, | ||
walker.mapper._props[c.name], | ||
{"relation": prop.key}, | ||
) | ||
) | ||
else: | ||
rp = walker.history[0] | ||
if prop.local_columns != rp.remote_side: | ||
for c in prop.local_columns: | ||
yield ColumnPropertyType.FOREIGNKEY, walker.mapper._props[c.name], { | ||
"relation": prop.key | ||
} | ||
yield Ok( | ||
( | ||
ColumnPropertyType.FOREIGNKEY, | ||
walker.mapper._props[c.name], | ||
{"relation": prop.key}, | ||
) | ||
) | ||
elif prop.direction == MANYTOMANY: | ||
# logger.warning("skip mapper=%s, prop=%s is many to many.", walker.mapper, prop) | ||
# fixme: this must return a ColumnPropertyType member | ||
yield ( | ||
{"type": "array", "items": {"type": "string"}}, # type: ignore[misc] | ||
prop, | ||
{}, | ||
yield Ok( | ||
( # type: ignore[arg-type] | ||
{"type": "array", "items": {"type": "string"}}, | ||
prop, | ||
{}, | ||
) | ||
Comment on lines
+83
to
+88
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix type inconsistency in yield statement for In the Consider defining a new member in Apply this diff to correct the issue: - yield Ok(
- ( # type: ignore[arg-type]
- {"type": "array", "items": {"type": "string"}},
- prop,
- {},
- )
- )
+ yield Ok(
+ (
+ ColumnPropertyType.ARRAY,
+ prop,
+ {"type": "array", "items": {"type": "string"}},
+ )
+ )
|
||
) | ||
else: | ||
yield ColumnPropertyType.RELATIONSHIP, prop, {} | ||
yield Ok((ColumnPropertyType.RELATIONSHIP, prop, {})) | ||
elif hasattr(prop, "columns"): | ||
yield ColumnPropertyType.FOREIGNKEY, prop, {} | ||
yield Ok((ColumnPropertyType.FOREIGNKEY, prop, {})) | ||
else: | ||
raise NotImplementedError(prop) | ||
yield Err(prop) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Exception-based error handling still present.
The 'result' dependency has been integrated and used in several areas. However, multiple instances of exception-based error handling (
raise
,except
,try:
) remain in the codebase. Please ensure that all error handling aligns with the PR objective of using results instead of exceptions.🔗 Analysis chain
LGTM! Verify integration of the new 'result' dependency.
The addition of the 'result' dependency (version ^0.17.0) aligns with the PR objective of using results instead of exceptions for error handling. This is a good practice for more structured error handling.
To ensure smooth integration, please run the following script to check for any potential conflicts or issues:
This script will help identify where and how the new 'result' dependency is being used, and if there are any remaining exception-based error handling that might need to be updated.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 669
Script:
Length of output: 5369