Skip to content

Certain type names causes errors for Python codegen #81

Open
@gtker

Description

@gtker

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions