Skip to content

Commit

Permalink
fix: fallback to native types if custom logicalTypes are defined when…
Browse files Browse the repository at this point in the history
… rendering Model. Closes #707 and closes #209 (#724)
  • Loading branch information
marcosschroh authored Aug 22, 2024
1 parent 36dbe4e commit bea0bfd
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 0 deletions.
3 changes: 3 additions & 0 deletions dataclasses_avroschema/model_generator/lang/python/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ def parse_logical_type(self, *, field: JsonDict) -> str:
elif logical_type == field_utils.DECIMAL:
# this is a special case for logical types
type = self.parse_decimal(field=field, default=default)
elif logical_type not in self.logical_types_imports:
# Then it is a custom logicalType, so we default to the native type
type = self.get_language_type(type=field["type"])
else:
# add the logical type import
self.imports.add(self.logical_types_imports[logical_type])
Expand Down
3 changes: 3 additions & 0 deletions docs/logical_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ The following list represent the avro logical types mapped to python types:
| string | uuid | uuid.UUID |
| bytes | decimal | types.condecimal |

!!! note
Custom `logicalTypes` are not supported

## Date

```python title="Date example"
Expand Down
40 changes: 40 additions & 0 deletions docs/model_generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,46 @@ class Address(AvroModel):

Generating a single module from multiple schemas is useful for example to group schemas that belong to the same namespace.

## LogicalTypes

Native `logicalTypes` are supported by `dataclasses-avroschema` but custom ones are not. If you defined a custom `logicalType` then
the fallback is used when generating the field. In the next example we have a `logicalType` defined as `url`, which is not a native one,
then the model generated will use `string`

```python
from dataclasses_avroschema import ModelGenerator, ModelType

model_generator = ModelGenerator()


schema = {
"type": "record",
"name": "TestEvent",
"fields": [
{
"name": "regular",
"type": {
"type": "string",
"logicalType": "url"
},
"doc": "Urls"
}
],
}

print(model_generator.render(schema=schema))

"""
from dataclasses_avroschema import AvroModel
import dataclasses
@dataclasses.dataclass
class TestEvent(AvroModel):
regular: str = dataclasses.field(metadata={'doc': 'Urls'})
"""
```

## Render Pydantic models

It is also possible to render `BaseModel` (pydantic) and `AvroBaseModel` (avro + pydantic) models as well.
Expand Down
20 changes: 20 additions & 0 deletions tests/model_generator/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,26 @@ def schema_with_logical_types_field_order() -> JsonDict:
}


@pytest.fixture
def schema_with_unknown_logical_types() -> JsonDict:
return {
"type": "record",
"name": "TestEvent",
"namespace": "com.example",
"fields": [
{"name": "occurredAt", "type": {"type": "long", "logicalType": "timestamp-millis"}, "doc": "Event time"},
{
"name": "previous",
"type": {
"type": "record",
"name": "urls",
"fields": [{"name": "regular", "type": {"type": "string", "logicalType": "url"}, "doc": "Urls"}],
},
},
],
}


@pytest.fixture
def schema_with_pydantic_fields() -> JsonDict:
return {
Expand Down
30 changes: 30 additions & 0 deletions tests/model_generator/test_model_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,36 @@ class LogicalTypes(AvroModel):
assert result.strip() == expected_result.strip()


def test_schema_with_unknown_logical_types(schema_with_unknown_logical_types: types.JsonDict) -> None:
expected_result = """
from dataclasses_avroschema import AvroModel
import dataclasses
import datetime
@dataclasses.dataclass
class Urls(AvroModel):
regular: str = dataclasses.field(metadata={'doc': 'Urls'})
class Meta:
schema_name = "urls"
@dataclasses.dataclass
class TestEvent(AvroModel):
occurredAt: datetime.datetime = dataclasses.field(metadata={'doc': 'Event time'})
previous: Urls
class Meta:
namespace = "com.example"
"""
model_generator = ModelGenerator()
result = model_generator.render(schema=schema_with_unknown_logical_types)
assert result.strip() == expected_result.strip()


def test_field_order(schema_with_logical_types_field_order: types.JsonDict) -> None:
release_datetime = render_datetime(value=1570903062000, format=field_utils.TIMESTAMP_MILLIS)
release_datetime_micro = render_datetime(value=1570903062000000, format=field_utils.TIMESTAMP_MICROS)
Expand Down

0 comments on commit bea0bfd

Please sign in to comment.