Skip to content

Commit

Permalink
fix: exception NameSpaceRequiredException removed. Close #246 (#253)
Browse files Browse the repository at this point in the history
Co-authored-by: Marcos Schroh <marcos.schroh@kpn.com>
  • Loading branch information
marcosschroh and marcosschroh committed Mar 6, 2023
1 parent d2334ff commit 38c0a06
Show file tree
Hide file tree
Showing 9 changed files with 48 additions and 58 deletions.
16 changes: 0 additions & 16 deletions dataclasses_avroschema/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
import typing


class NameSpaceRequiredException(Exception):
def __init__(self, field_type: typing.Any, field_name: str) -> None:
self.field_type = field_type
self.field_name = field_name

def __repr__(self) -> str:
class_name = self.__class__.__name__
return f"{class_name} {self.field_name},{self.field_type}"

def __str__(self) -> str:
return ( # pragma: no cover
f"Required namespace in Meta for type {self.field_type}. "
f"The field {self.field_name} is using an exiting type"
)


class InvalidMap(Exception):
def __init__(self, field_name: str, key_type: typing.Any) -> None:
self.field_name = field_name
Expand Down
12 changes: 7 additions & 5 deletions dataclasses_avroschema/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from dataclasses_avroschema import schema_generator, serialization, types, utils

from . import field_utils
from .exceptions import InvalidMap, NameSpaceRequiredException
from .exceptions import InvalidMap
from .types import JsonDict

PY_VER = sys.version_info
Expand Down Expand Up @@ -483,8 +483,9 @@ def get_avro_type(self) -> typing.Union[str, JsonDict]:
else:
namespace = metadata.get("namespace")
if namespace is None:
raise NameSpaceRequiredException(field_type=self.type, field_name=name)
return f"{namespace}.{name}"
return name
else:
return f"{namespace}.{name}"

def get_default_value(self) -> typing.Union[str, dataclasses._MISSING_TYPE, None]:
if self.default == types.MissingSentinel:
Expand Down Expand Up @@ -770,8 +771,9 @@ def get_avro_type(self) -> typing.Union[str, typing.List, typing.Dict]:
record_type["name"] = name
else:
if metadata.namespace is None:
raise NameSpaceRequiredException(field_type=self.type, field_name=self.name)
record_type = f"{metadata.namespace}.{name}"
record_type = name
else:
record_type = f"{metadata.namespace}.{name}"

if self.default is None:
return [field_utils.NULL, record_type]
Expand Down
8 changes: 3 additions & 5 deletions docs/complex_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ User.avro_schema()

### Repeated Enums

Sometimes we have cases where an `Enum` is used more than once with a particular class, for those cases, you `MUST` define the namespace in order to generate a valid `avro schema`
Sometimes we have cases where an `Enum` is used more than once with a particular class, for those cases the same `type` is used in order to generate a valid schema.
It is a good practice but *NOT* neccesary to a define the `namespace` on the repeated `type`.

```python
import enum
Expand Down Expand Up @@ -123,7 +124,7 @@ resulting in
"name": "optional_distance",
"type": [
"null",
"trip.TripDistance"
"trip.TripDistance" // using the namespace and the TripDistance type
],
"default": null
}
Expand All @@ -132,9 +133,6 @@ resulting in
}
```

!!! warning
If you do not specify the `namespace` in the `Enum` the exception `NameSpaceRequiredException` is raised

## Arrays

```python title="Array example"
Expand Down
21 changes: 15 additions & 6 deletions docs/good_practices.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Streaming
# Streaming

### Schema server and AvroModel
## Schema server and AvroModel

First, let's clarify what a schema server is: It is a `central place/repository` that contains schemas with formats like `avro`, `json` or `protobuf`, with the purpose of exposing them through an `API`, so applications can access them and `serialize/deserialize` events. The schema server could have a `RESTful` interface so tasks like `create`, `delete` `get` schemas can be performed easily.

Expand Down Expand Up @@ -29,8 +29,7 @@ class User(AvroModel):

The purpose of the `schema_id` is to give a fast notion what the model is representing. Also, could be used as `documentation`


### Include event metadata
## Include event metadata

`avro schemas` are used widely in `streaming` to `serialize` events, and with `dataclasses-avroschemas` it is straigtforward. Once
that you have the event, it is a good practice to also add the `event metadata` at the moment of `producing` so `consumers` will know what to do.
Expand All @@ -56,7 +55,7 @@ class User(AvroModel):
money: float = 100.3

class Meta:
schema_id = "https://my-schema-server/users/schema.avsc" # or in a Concluent way: https://my-schema-server/schemas/ids/{int: id}
schema_id = "https://my-schema-server/users/schema.avsc" # or in a Confluent way: https://my-schema-server/schemas/ids/{int: id}


async def produce():
Expand All @@ -80,4 +79,14 @@ async def produce():

if __name__ == "__main__":
asyncio.run(produce)
```
```

## Define Namespaces

When there are types that are used more than once in a schema, for example `records` and `enums` it is a good practice to define `namespace` for the repeated type.
This will allow you to identify more easily the `types`, specially if you have all the schemas in a `schema server` like `confluent`.

Uses cases:

- [Reusing types with records](https://marcosschroh.github.io/dataclasses-avroschema/schema_relationships/#avoid-name-collision-in-multiple-relationships)
- [Reusing types with enums](https://marcosschroh.github.io/dataclasses-avroschema/complex_types/#repeated-enums)
2 changes: 1 addition & 1 deletion docs/model_generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The rendered result is a string that contains the proper identation, so the resu
In future releases it will be possible to generate models for other programming langagues like `java` and `rust`

!!! note
You can also use [dc-avro](https://github.com/marcosschroh/dc-avro)d to generate the models from the command line
You can also use [dc-avro](https://github.com/marcosschroh/dc-avro) to generate the models from the command line

## Usage

Expand Down
11 changes: 5 additions & 6 deletions docs/schema_relationships.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,11 +278,10 @@ User.avro_schema()

## Avoid name collision in multiple relationships

Sometimes we have relationships where a class is related more than once with a particular class,
and the name for the nested schemas must be different, otherwise we will generate an invalid `avro schema`.
For those cases, you *MUST* define the `namespace`.
Sometimes we have relationships where a class is related more than once with a particular class.
In those cases, the `predifne` type is used in order to generate a valid schema. It is a good practice but *NOT* neccesary to a define the `namespace` on the repeated `type`.

```python title="Avoiding name collision example"
```python title="Repetead types"
from dataclasses import dataclass
from datetime import datetime
import json
Expand All @@ -296,7 +295,7 @@ class Location(AvroModel):
longitude: float

class Meta:
namespace = "types.location_type"
namespace = "types.location_type" # Good practise to use `namespaces`

@dataclass
class Trip(AvroModel):
Expand Down Expand Up @@ -333,7 +332,7 @@ Trip.avro_schema()
"type": {"type": "long", "logicalType": "timestamp-millis"}
},
{
"name": "finish_location", "type": "types.location_type.Location" // using the namespace
"name": "finish_location", "type": "types.location_type.Location" // using the namespace and the Location type
}
],
"doc": "Trip(start_time: datetime.datetime, start_location: __main__.Location, finish_time: datetime.datetime, finish_location: __main__.Location)"
Expand Down
13 changes: 13 additions & 0 deletions tests/schemas/test_fastavro_paser_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,19 @@ class Trip(AvroModel):
assert Trip.fake()


def test_repeated_schema_without_namespace():
class Bus(AvroModel):
"A Bus"
engine_name: str

class UnionSchema(AvroModel):
"Some Unions"
bus_one: Bus
bus_two: Bus

parse_schema(UnionSchema.avro_schema_to_python())


def test_one_to_one_repeated_schema_in_array():
"""
Test relationship one-to-one with more than once schema
Expand Down
16 changes: 1 addition & 15 deletions tests/schemas/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest
from fastavro.validation import ValidationError

from dataclasses_avroschema import AvroModel, exceptions
from dataclasses_avroschema import AvroModel
from dataclasses_avroschema.schema_definition import BaseSchemaDefinition
from dataclasses_avroschema.types import JsonDict

Expand Down Expand Up @@ -123,20 +123,6 @@ class Aclass:
assert msg == str(excinfo.value)


def test_namespace_required():
class Bus(AvroModel):
"A Bus"
engine_name: str

class UnionSchema(AvroModel):
"Some Unions"
bus_one: Bus
bus_two: Bus

with pytest.raises(exceptions.NameSpaceRequiredException):
assert UnionSchema.avro_schema()


def test_inherit_dataclass_missing_docs():
@dataclass
class BaseUser:
Expand Down
7 changes: 3 additions & 4 deletions tests/schemas/test_schema_with_complex_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pytest
from fastavro import parse_schema

from dataclasses_avroschema import AvroModel, exceptions
from dataclasses_avroschema import AvroModel
from dataclasses_avroschema.types import JsonDict

PY_VER = sys.version_info
Expand Down Expand Up @@ -155,7 +155,7 @@ class User(AvroModel):
assert User.avro_schema() == json.dumps(default_union_schema)


def test_enum_namespace_required() -> None:
def test_repeated_enum_without_namespace() -> None:
class UserType(enum.Enum):
BASIC = "Basic"
PREMIUM = "Premium"
Expand All @@ -165,8 +165,7 @@ class User(AvroModel):
user_type: UserType
user_type_optional: typing.Optional[UserType]

with pytest.raises(exceptions.NameSpaceRequiredException):
User.avro_schema()
parse_schema(User.avro_schema_to_python())


# This is to explicitly test the behavior for a typing.Optional[T] field with no default
Expand Down

0 comments on commit 38c0a06

Please sign in to comment.