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

Certain type names causes errors for Python codegen #81

Open
gtker opened this issue Jul 31, 2023 · 0 comments
Open

Certain type names causes errors for Python codegen #81

gtker opened this issue Jul 31, 2023 · 0 comments

Comments

@gtker
Copy link

gtker commented Jul 31, 2023

Having user defined types with names like Optional will make Python unable to use the typing.Optional type since it is from imported.

Not importing typing.Optional but instead using it with the full module prefix would fix this.

The same would happen with Enum, Any, or List.

Shame that this is unmaintained, I would not mind making a PR for this since it's a simple fix.
Creating this issue so that others will realize their issue faster.


With the following schema:

{
    "properties": {
        "opt": {
            "ref": "optional"
        }
    },
    "optionalProperties": {
        "t": {"type":"string"}
    },
    "definitions": {
        "optional": {}
    }
}

Running jtd-codegen --python-out . schema.json creates the following:

# Code generated by jtd-codegen for Python v0.3.1

import re
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Optional, Union, get_args, get_origin


@dataclass
class Schema:
    opt: 'Optional'
    t: 'Optional[str]'

    @classmethod
    def from_json_data(cls, data: Any) -> 'Schema':
        return cls(
            _from_json_data(Optional, data.get("opt")),
            _from_json_data(Optional[str], data.get("t")),
        )

    def to_json_data(self) -> Any:
        data: Dict[str, Any] = {}
        data["opt"] = _to_json_data(self.opt)
        if self.t is not None:
             data["t"] = _to_json_data(self.t)
        return data

@dataclass
class Optional:
    value: 'Any'

    @classmethod
    def from_json_data(cls, data: Any) -> 'Optional':
        return cls(_from_json_data(Any, data))

    def to_json_data(self) -> Any:
        return _to_json_data(self.value)

def _from_json_data(cls: Any, data: Any) -> Any:
    if data is None or cls in [bool, int, float, str, object] or cls is Any:
        return data
    if cls is datetime:
        return _parse_rfc3339(data)
    if get_origin(cls) is Union:
        return _from_json_data(get_args(cls)[0], data)
    if get_origin(cls) is list:
        return [_from_json_data(get_args(cls)[0], d) for d in data]
    if get_origin(cls) is dict:
        return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() }
    return cls.from_json_data(data)

def _to_json_data(data: Any) -> Any:
    if data is None or type(data) in [bool, int, float, str, object]:
        return data
    if type(data) is datetime:
        return data.isoformat()
    if type(data) is list:
        return [_to_json_data(d) for d in data]
    if type(data) is dict:
        return { k: _to_json_data(v) for k, v in data.items() }
    return data.to_json_data()

def _parse_rfc3339(s: str) -> datetime:
    datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$'
    match = re.match(datetime_re, s)
    if not match:
        raise ValueError('Invalid RFC3339 date/time', s)

    (year, month, day, hour, minute, second, frac_seconds, offset,
     *tz) = match.groups()

    frac_seconds_parsed = None
    if frac_seconds:
        frac_seconds_parsed = int(float(frac_seconds) * 1_000_000)
    else:
        frac_seconds_parsed = 0

    tzinfo = None
    if offset == 'Z':
        tzinfo = timezone.utc
    else:
        hours = int(tz[2])
        minutes = int(tz[3])
        sign = 1 if tz[1] == '+' else -1

        if minutes not in range(60):
            raise ValueError('minute offset must be in 0..59')

        tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes)))

    second_parsed = int(second)
    if second_parsed == 60:
        second_parsed = 59

    return datetime(int(year), int(month), int(day), int(hour), int(minute),
                    second_parsed, frac_seconds_parsed, tzinfo)            

Running it with

import model
import json
data = json.loads("{}")
t = model.Schema.from_json_data(data)

causes

Traceback (most recent call last):
  File "/media/asd/96B4-13F2/jtdc/t.py", line 7, in <module>
    t = model.Schema.from_json_data(data)
  File "/media/asd/96B4-13F2/jtdc/model.py", line 18, in from_json_data
    _from_json_data(Optional[str], data.get("t")),
TypeError: 'type' object is not subscriptable
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant