Skip to content
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

Add custom_get_name config option #889

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -7,3 +7,4 @@ __pycache__
/dist
/.github/changelog-generator-cache
/tests/recursion.py
/.vscode
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be on your own ~/.gitignore file...

28 changes: 24 additions & 4 deletions jsonschema_gentypes/api.py
Expand Up @@ -53,12 +53,14 @@ def __init__(
self,
resolver: RefResolver,
additional_properties: configuration.AdditionalProperties = configuration.ADDITIONALPROPERTIES_ONLY_EXPLICIT,
custom_get_name: Optional[configuration.GetNameFunction] = None,
) -> None:
"""
Initialize with a resolver.
"""
self.resolver = resolver
self.additional_properties = additional_properties
self.custom_get_name: Optional[configuration.GetNameFunction] = custom_get_name
# types by reference
self.ref_type: dict[str, Type] = {}
self.root: Optional[TypeProxy] = None
Expand Down Expand Up @@ -170,13 +172,15 @@ def get_type(
if description:
if not isinstance(the_type, NamedType):
if auto_alias:
the_type = TypeAlias(get_name(schema_meta_data, proposed_name), the_type, description)
the_type = TypeAlias(
self.get_name(schema_meta_data, proposed_name), the_type, description
)
the_type.set_comments(description)

if "default" in schema_meta_data:
the_type.add_depends_on(
Constant(
f"{get_name(schema_meta_data, proposed_name, True)}_DEFAULT",
f"{self.get_name(schema_meta_data, proposed_name, True)}_DEFAULT",
schema_meta_data["default"],
[f"Default value of the field path '{proposed_name}'"],
)
Expand All @@ -191,6 +195,22 @@ def get_type(

return the_type

def get_name(
self,
schema: Optional[
Union[
jsonschema_draft_04.JSONSchemaD4,
jsonschema_draft_2019_09_meta_data.JSONSchemaItemD2019,
]
],
proposed_name: Optional[str] = None,
upper: bool = False,
) -> str:
if self.custom_get_name:
return self.custom_get_name(schema, proposed_name, upper)
else:
return get_name(schema, proposed_name, upper)

def resolve_ref(
self,
schema: Union[
Expand Down Expand Up @@ -342,7 +362,7 @@ def build_type(
"anyof",
)
if not isinstance(type_, NamedType):
type_ = TypeAlias(get_name(schema_meta_data, proposed_name), type_)
type_ = TypeAlias(self.get_name(schema_meta_data, proposed_name), type_)
elif type_.comments():
type_.comments().append("")
type_.comments().append("Aggregation type: anyOf")
Expand Down Expand Up @@ -394,7 +414,7 @@ def build_type(
if len(schema_type) == 0:
return BuiltinType("None")
inner_types = []
name = get_name(schema_meta_data, proposed_name)
name = self.get_name(schema_meta_data, proposed_name)
has_title = "title" in schema_meta_data
proposed_name = schema_meta_data.get("title", proposed_name)

Expand Down
16 changes: 10 additions & 6 deletions jsonschema_gentypes/api_draft_04.py
Expand Up @@ -55,7 +55,7 @@ def enum(
)

return TypeEnum(
get_name(schema_meta_data, proposed_name),
self.get_name(schema_meta_data, proposed_name),
cast(list[Union[int, float, bool, str, None]], schema["enum"]),
get_description(schema_meta_data),
)
Expand Down Expand Up @@ -99,7 +99,7 @@ def object(
)

std_dict = None
name = get_name(schema_meta_data, proposed_name)
name = self.get_name(schema_meta_data, proposed_name)
schema.setdefault("used", set()).add("additionalProperties") # type: ignore[typeddict-item]
additional_properties = cast(
Union[jsonschema_draft_04.JSONSchemaD4, jsonschema_draft_2020_12_applicator.JSONSchemaD2020],
Expand Down Expand Up @@ -294,7 +294,9 @@ def any_of(
)
if not isinstance(type_, NamedType):
type_ = TypeAlias(
get_name(combined_schema_meta_data, proposed_name + " " + sub_name), type_, []
self.get_name(combined_schema_meta_data, proposed_name + " " + sub_name),
type_,
[],
)

additional_types.append(type_)
Expand Down Expand Up @@ -326,7 +328,9 @@ def any_of(
)
if not isinstance(type_, NamedType):
type_ = TypeAlias(
get_name(combined_schema_meta_data, proposed_name + " " + sub_name), type_, []
self.get_name(combined_schema_meta_data, proposed_name + " " + sub_name),
type_,
[],
)
additional_types.append(type_)
inner_types.append(type_)
Expand Down Expand Up @@ -495,7 +499,7 @@ def all_of(
)
if not isinstance(type_, NamedType):
type_ = TypeAlias(
get_name(combined_schema_meta_data, f"{proposed_name} {sub_name}{index}"),
self.get_name(combined_schema_meta_data, f"{proposed_name} {sub_name}{index}"),
type_,
[],
)
Expand All @@ -522,7 +526,7 @@ def all_of(
)
if not isinstance(type_, NamedType):
type_ = TypeAlias(
get_name(combined_schema_meta_data, f"{proposed_name} {sub_name}{index}"),
self.get_name(combined_schema_meta_data, f"{proposed_name} {sub_name}{index}"),
type_,
[],
)
Expand Down
20 changes: 19 additions & 1 deletion jsonschema_gentypes/configuration.py
Expand Up @@ -2,10 +2,12 @@
Automatically generated file from a JSON schema.
"""

from typing import Any, Literal, TypedDict, Union
from typing import Any, Callable, Literal, Optional, TypedDict, Union
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is generated from jsonschema_gentypes/schema.json and describes the schema of the configurations file.

Then if we want to be able to configure it only with python code we should get the function definition out of this file :-)

But in fact, I didn't see well how you plan to use it?
Like in the test?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbrunner ok! I fixed the two other issues you pointed out.

I can totally get the definition out and instead put it in the configuration file.

what's the command to generate the schema.json from the configuration file?

I plan to use this exactly how I use it in the test.

Without the custom_get_name, you wind up with the .title version like Subresourceuri as the name and the test fails. I need the only-first-letter-capitalized version like SubresourceUri.

I also invoke this by importing the process_config function from a Python script, not from the cli.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbrunner want me to pull that definition out of the configuration file? any other changes?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the command to generate the schema.json from the configuration file?

It's the inverse, this file is generated from this schema https://github.com/sbrunner/jsonschema-gentypes/blob/7c7fe0d6f5539c13334580762bfacef292c6edba/jsonschema_gentypes/schema.json

It's done by pre-commit :-)


from typing_extensions import Required

from jsonschema_gentypes import jsonschema_draft_04, jsonschema_draft_2019_09_meta_data

AdditionalProperties = Union[Literal["Always"], Literal["Only explicit"]]
"""
Additional properties.
Expand All @@ -18,6 +20,21 @@
"""The values for the 'Additional properties' enum"""


GetNameFunction = Callable[
[
Optional[
Union[
jsonschema_draft_04.JSONSchemaD4,
jsonschema_draft_2019_09_meta_data.JSONSchemaItemD2019,
]
],
Optional[str],
bool,
],
str,
]


class ApiArguments(TypedDict, total=False):
"""
API arguments.
Expand All @@ -26,6 +43,7 @@ class ApiArguments(TypedDict, total=False):
"""

additional_properties: "AdditionalProperties"
custom_get_name: GetNameFunction
"""
Additional properties.

Expand Down
23 changes: 23 additions & 0 deletions tests/custom_get_name.json
@@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-06/schema#",
"definitions": {
"SubresourceUris": {
"type": "object",
"properties": {
"feedback": {
"type": "string"
}
},
"required": ["feedback"],
"title": "SubresourceUris"
}
},
"type": "object",
"properties": {
"subresource_uris": {
"$ref": "#/definitions/SubresourceUris"
}
},
"required": ["subresource_uris"],
"title": "ResponseType"
}
21 changes: 21 additions & 0 deletions tests/custom_get_name.py
@@ -0,0 +1,21 @@
from typing import TypedDict

from typing_extensions import Required


class ResponseType(TypedDict, total=False):
"""ResponseType."""

subresource_uris: Required["SubresourceUris"]
"""
SubresourceUris.

Required property
"""


class SubresourceUris(TypedDict, total=False):
"""SubresourceUris."""

feedback: Required[str]
""" Required property """
78 changes: 78 additions & 0 deletions tests/test_custom_get_name.py
@@ -0,0 +1,78 @@
from typing import Callable, Optional, Union

from jsonschema_gentypes import jsonschema_draft_04, jsonschema_draft_2019_09_meta_data, normalize
from jsonschema_gentypes.cli import process_config
from jsonschema_gentypes.configuration import Configuration

GetNameFunction = Callable[
[
Optional[
Union[
jsonschema_draft_04.JSONSchemaD4,
jsonschema_draft_2019_09_meta_data.JSONSchemaItemD2019,
]
],
Optional[str],
bool,
],
str,
]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this copy?



def custom_get_name(
schema: Optional[
Union[
jsonschema_draft_04.JSONSchemaD4,
jsonschema_draft_2019_09_meta_data.JSONSchemaItemD2019,
]
],
proposed_name: Optional[str] = None,
upper: bool = False,
) -> str:
"""
Custom get the name for an element.

Just capitalize first Letter, don't do `.title`

Parameter:
schema: the concerned schema
proposed_name: a name that we will use it the schema hasn't any title
upper: should we use an upper case (For constants)
"""
# Get the base name
has_title = isinstance(schema, dict) and "title" in schema
name = schema["title"] if has_title else proposed_name # type: ignore
assert name is not None
name = normalize(name)

prefix = "" if has_title else "_"
if upper:
# Upper case
name = name.upper()
# Remove spaces
return prefix + "".join(["_" if char.isspace() else char for char in name])
else:
# Title case
name = name[0].upper() + name[1:]
# Remove spaces
return prefix + "".join([char for char in name if not char.isspace()])


def test_empty_array() -> None:
config: Configuration = Configuration(
generate=[
{
"source": "tests/custom_get_name.json",
"destination": "tests/custom_get_name.py",
"api_arguments": {"custom_get_name": custom_get_name},
}
],
)
process_config(
config,
["tests/custom_get_name.json"],
)

with open("tests/custom_get_name.py") as f:
content = f.read()
assert "class SubresourceUris" in content