You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by rs...@apache.org on 2022/12/08 16:01:23 UTC

[avro] branch branch-1.11 updated: AVRO-3680: [Python] allow to disable name validation (#1995)

This is an automated email from the ASF dual-hosted git repository.

rskraba pushed a commit to branch branch-1.11
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/branch-1.11 by this push:
     new 99b682b7d AVRO-3680: [Python] allow to disable name validation (#1995)
99b682b7d is described below

commit 99b682b7d5409df4ee10dd88f89bc04b9d7492c8
Author: Jarkko Jaakola <91...@users.noreply.github.com>
AuthorDate: Thu Dec 8 17:08:36 2022 +0200

    AVRO-3680: [Python] allow to disable name validation (#1995)
    
    For interoperability the Python parsing API allows disabling the
    name validation. It is possible to create schema with names having
    e.g. dashes from Java SDK. The Python name validation is stricter
    and follows the Avro specification.
---
 lang/py/avro/name.py             |  16 +-
 lang/py/avro/protocol.py         |  61 ++++----
 lang/py/avro/schema.py           | 137 ++++++++--------
 lang/py/avro/test/test_name.py   | 327 +++++++++++++++++++++++++++++++++++++++
 lang/py/avro/test/test_schema.py |  88 -----------
 5 files changed, 433 insertions(+), 196 deletions(-)

diff --git a/lang/py/avro/name.py b/lang/py/avro/name.py
index e7d8710fc..0d28338de 100644
--- a/lang/py/avro/name.py
+++ b/lang/py/avro/name.py
@@ -53,7 +53,9 @@ class Name:
     _full: Optional[str] = None
     _name: Optional[str] = None
 
-    def __init__(self, name_attr: Optional[str] = None, space_attr: Optional[str] = None, default_space: Optional[str] = None) -> None:
+    def __init__(
+        self, name_attr: Optional[str] = None, space_attr: Optional[str] = None, default_space: Optional[str] = None, validate_name: bool = True
+    ) -> None:
         """The fullname is determined in one of the following ways:
 
         - A name and namespace are both specified. For example, one might use "name": "X",
@@ -87,7 +89,8 @@ class Name:
             if "." in name_attr or space_attr == "" or not (space_attr or default_space)
             else f"{space_attr or default_space!s}.{name_attr!s}"
         )
-        _validate_fullname(self._full)
+        if validate_name:
+            _validate_fullname(self._full)
 
     def __eq__(self, other: object) -> bool:
         """Equality of names is defined on the fullname and is case-sensitive."""
@@ -117,16 +120,17 @@ class Names:
 
     names: Dict[str, "NamedSchema"]
 
-    def __init__(self, default_namespace: Optional[str] = None) -> None:
+    def __init__(self, default_namespace: Optional[str] = None, validate_names: bool = True) -> None:
         self.names = {}
         self.default_namespace = default_namespace
+        self.validate_names = validate_names
 
     def has_name(self, name_attr: str, space_attr: Optional[str] = None) -> bool:
-        test = Name(name_attr, space_attr, self.default_namespace).fullname
+        test = Name(name_attr, space_attr, self.default_namespace, validate_name=self.validate_names).fullname
         return test in self.names
 
     def get_name(self, name_attr: str, space_attr: Optional[str] = None) -> Optional["NamedSchema"]:
-        test = Name(name_attr, space_attr, self.default_namespace).fullname
+        test = Name(name_attr, space_attr, self.default_namespace, validate_name=self.validate_names).fullname
         return None if test is None else self.names.get(test)
 
     def prune_namespace(self, properties: Dict[str, object]) -> Dict[str, object]:
@@ -158,7 +162,7 @@ class Names:
 
         @return: the Name that was just added.
         """
-        to_add = Name(name_attr, space_attr, self.default_namespace)
+        to_add = Name(name_attr, space_attr, self.default_namespace, validate_name=self.validate_names)
 
         if to_add.fullname in PRIMITIVE_TYPES:
             raise avro.errors.SchemaParseException(f"{to_add.fullname} is a reserved type name.")
diff --git a/lang/py/avro/protocol.py b/lang/py/avro/protocol.py
index e345bae77..5477fc45f 100644
--- a/lang/py/avro/protocol.py
+++ b/lang/py/avro/protocol.py
@@ -70,6 +70,7 @@ class Protocol:
         "_name",
         "_namespace",
         "_types",
+        "_validate_names",
     ]
 
     _md5: bytes
@@ -77,6 +78,7 @@ class Protocol:
     _name: str
     _namespace: Optional[str]
     _types: Optional[Sequence[avro.schema.NamedSchema]]
+    _validate_names: bool
 
     def __init__(
         self,
@@ -84,6 +86,7 @@ class Protocol:
         namespace: Optional[str] = None,
         types: Optional[Sequence[str]] = None,
         messages: Optional[Mapping[str, "MessageObject"]] = None,
+        validate_names: bool = True,
     ) -> None:
         if not name:
             raise avro.errors.ProtocolParseException("Protocols must have a non-empty name.")
@@ -95,12 +98,13 @@ class Protocol:
             raise avro.errors.ProtocolParseException("The types property must be a list.")
         if not (messages is None or callable(getattr(messages, "get", None))):
             raise avro.errors.ProtocolParseException("The messages property must be a JSON object.")
-        type_names = avro.name.Names()
+        self._validate_names = validate_names
+        type_names = avro.name.Names(validate_names=self._validate_names)
 
         self._name = name
         self._namespace = type_names.default_namespace = namespace
-        self._types = _parse_types(types, type_names) if types else None
-        self._messages = _parse_messages(messages, type_names) if messages else None
+        self._types = _parse_types(types, type_names, self._validate_names) if types else None
+        self._messages = _parse_messages(messages, type_names, self._validate_names) if messages else None
         self._md5 = hashlib.md5(str(self).encode()).digest()
 
     @property
@@ -113,7 +117,7 @@ class Protocol:
 
     @property
     def fullname(self) -> Optional[str]:
-        return avro.name.Name(self.name, self.namespace, None).fullname
+        return avro.name.Name(self.name, self.namespace, None, validate_name=self._validate_names).fullname
 
     @property
     def types(self) -> Optional[Sequence[avro.schema.NamedSchema]]:
@@ -132,7 +136,7 @@ class Protocol:
         return self._md5
 
     def to_json(self) -> Mapping[str, Union[str, Sequence[object], Mapping[str, "MessageObject"]]]:
-        names = avro.name.Names(default_namespace=self.namespace)
+        names = avro.name.Names(default_namespace=self.namespace, validate_names=self._validate_names)
         return {
             "protocol": self.name,
             **({"namespace": self.namespace} if self.namespace else {}),
@@ -167,12 +171,7 @@ class Message:
     The one-way parameter may only be true when the response type is "null" and no errors are listed.
     """
 
-    __slots__ = [
-        "_errors",
-        "_name",
-        "_request",
-        "_response",
-    ]
+    __slots__ = ["_errors", "_name", "_request", "_response", "_validate_names"]
 
     def __init__(
         self,
@@ -181,12 +180,14 @@ class Message:
         response: Union[str, object],
         errors: Optional[Sequence[str]] = None,
         names: Optional[avro.name.Names] = None,
+        validate_names: bool = True,
     ) -> None:
         self._name = name
-        names = names or avro.name.Names()
-        self._request = _parse_request(request, names)
-        self._response = _parse_response(response, names)
-        self._errors = _parse_errors(errors or [], names)
+        names = names or avro.name.Names(validate_names=validate_names)
+        self._request = _parse_request(request, names, validate_names)
+        self._response = _parse_response(response, names, validate_names)
+        self._errors = _parse_errors(errors or [], names, validate_names)
+        self._validate_names = validate_names
 
     @property
     def name(self) -> str:
@@ -208,7 +209,7 @@ class Message:
         return json.dumps(self.to_json())
 
     def to_json(self, names: Optional[avro.name.Names] = None) -> "MessageObject":
-        names = names or avro.name.Names()
+        names = names or avro.name.Names(validate_names=self._validate_names)
 
         try:
             to_dump = MessageObject()
@@ -225,25 +226,25 @@ class Message:
         return all(hasattr(that, prop) and getattr(self, prop) == getattr(that, prop) for prop in self.__class__.__slots__)
 
 
-def _parse_request(request: Sequence[Mapping[str, object]], names: avro.name.Names) -> avro.schema.RecordSchema:
+def _parse_request(request: Sequence[Mapping[str, object]], names: avro.name.Names, validate_names: bool = True) -> avro.schema.RecordSchema:
     if not isinstance(request, Sequence):
         raise avro.errors.ProtocolParseException(f"Request property not a list: {request}")
-    return avro.schema.RecordSchema(None, None, request, names, "request")
+    return avro.schema.RecordSchema(None, None, request, names, "request", validate_names=validate_names)
 
 
-def _parse_response(response: Union[str, object], names: avro.name.Names) -> avro.schema.Schema:
-    return (isinstance(response, str) and names.get_name(response)) or avro.schema.make_avsc_object(response, names)
+def _parse_response(response: Union[str, object], names: avro.name.Names, validate_names: bool = True) -> avro.schema.Schema:
+    return (isinstance(response, str) and names.get_name(response)) or avro.schema.make_avsc_object(response, names, validate_names=validate_names)
 
 
-def _parse_errors(errors: Sequence[str], names: avro.name.Names) -> avro.schema.ErrorUnionSchema:
+def _parse_errors(errors: Sequence[str], names: avro.name.Names, validate_names: bool = True) -> avro.schema.ErrorUnionSchema:
     """Even if errors is empty, we still want an ErrorUnionSchema with "string" in it."""
     if not isinstance(errors, Sequence):
         raise avro.errors.ProtocolParseException(f"Errors property not a list: {errors}")
     errors_for_parsing = {"type": "error_union", "declared_errors": errors}
-    return cast(avro.schema.ErrorUnionSchema, avro.schema.make_avsc_object(errors_for_parsing, names))
+    return cast(avro.schema.ErrorUnionSchema, avro.schema.make_avsc_object(errors_for_parsing, names, validate_names=validate_names))
 
 
-def make_avpr_object(json_data: "ProtocolObject") -> Protocol:
+def make_avpr_object(json_data: "ProtocolObject", validate_names: bool = True) -> Protocol:
     """Build Avro Protocol from data parsed out of JSON string."""
     if not hasattr(json_data, "get"):
         raise avro.errors.ProtocolParseException(f"Not a JSON object: {json_data}")
@@ -251,22 +252,22 @@ def make_avpr_object(json_data: "ProtocolObject") -> Protocol:
     namespace = json_data.get("namespace")
     types = json_data.get("types")
     messages = json_data.get("messages")
-    return Protocol(name, namespace, types, messages)
+    return Protocol(name, namespace, types, messages, validate_names)
 
 
-def parse(json_string: str) -> Protocol:
+def parse(json_string: str, validate_names: bool = True) -> Protocol:
     """Constructs the Protocol from the JSON text."""
     try:
         protocol_object = json.loads(json_string)
     except ValueError:
         raise avro.errors.ProtocolParseException(f"Error parsing JSON: {json_string}")
-    return make_avpr_object(protocol_object)
+    return make_avpr_object(protocol_object, validate_names)
 
 
-def _parse_types(types: Sequence[str], type_names: avro.name.Names) -> Sequence[avro.schema.NamedSchema]:
+def _parse_types(types: Sequence[str], type_names: avro.name.Names, validate_names: bool = True) -> Sequence[avro.schema.NamedSchema]:
     schemas = []
     for type_ in types:
-        schema = avro.schema.make_avsc_object(type_, type_names)
+        schema = avro.schema.make_avsc_object(type_, type_names, validate_names=validate_names)
         if isinstance(schema, avro.schema.NamedSchema):
             schemas.append(schema)
             continue
@@ -274,7 +275,7 @@ def _parse_types(types: Sequence[str], type_names: avro.name.Names) -> Sequence[
     return schemas
 
 
-def _parse_messages(message_objects: Mapping[str, "MessageObject"], names: avro.name.Names) -> Mapping[str, Message]:
+def _parse_messages(message_objects: Mapping[str, "MessageObject"], names: avro.name.Names, validate_names: bool = True) -> Mapping[str, Message]:
     messages = {}
     for name, body in message_objects.items():
         if not hasattr(body, "get"):
@@ -282,5 +283,5 @@ def _parse_messages(message_objects: Mapping[str, "MessageObject"], names: avro.
         request = body["request"]
         response = body["response"]
         errors = body.get("errors")
-        messages[name] = Message(name, request, response, errors, names)
+        messages[name] = Message(name, request, response, errors, names, validate_names=validate_names)
     return messages
diff --git a/lang/py/avro/schema.py b/lang/py/avro/schema.py
index eba1a33f8..1c810f78a 100644
--- a/lang/py/avro/schema.py
+++ b/lang/py/avro/schema.py
@@ -47,7 +47,7 @@ import math
 import uuid
 import warnings
 from pathlib import Path
-from typing import Mapping, MutableMapping, Optional, Sequence, Union, cast
+from typing import List, Mapping, MutableMapping, Optional, Sequence, Union, cast
 
 import avro.constants
 import avro.errors
@@ -174,7 +174,7 @@ class Schema(abc.ABC, CanonicalPropertiesMixin):
 
     _reserved_properties = SCHEMA_RESERVED_PROPS
 
-    def __init__(self, type_: str, other_props: Optional[Mapping[str, object]] = None) -> None:
+    def __init__(self, type_: str, other_props: Optional[Mapping[str, object]] = None, validate_names: bool = True) -> None:
         if not isinstance(type_, str):
             raise avro.errors.SchemaParseException("Schema type must be a string.")
         if type_ not in VALID_TYPES:
@@ -182,6 +182,7 @@ class Schema(abc.ABC, CanonicalPropertiesMixin):
         self.set_prop("type", type_)
         self.type = type_
         self.props.update(other_props or {})
+        self.validate_names = validate_names
 
     @abc.abstractmethod
     def match(self, writer: "Schema") -> bool:
@@ -250,8 +251,9 @@ class NamedSchema(Schema):
         namespace: Optional[str] = None,
         names: Optional[Names] = None,
         other_props: Optional[Mapping[str, object]] = None,
+        validate_names: bool = True,
     ) -> None:
-        super().__init__(type_, other_props)
+        super().__init__(type_, other_props, validate_names=validate_names)
         if not name:
             raise avro.errors.SchemaParseException("Named Schemas must have a non-empty name.")
         if not isinstance(name, str):
@@ -259,7 +261,7 @@ class NamedSchema(Schema):
         if namespace is not None and not isinstance(namespace, str):
             raise avro.errors.SchemaParseException("The namespace property must be a string.")
         namespace = namespace or None  # Empty string -> None
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
         new_name = names.add_name(name, namespace, self)
 
         # Store name and namespace as they were read in origin schema
@@ -314,17 +316,7 @@ class DecimalLogicalSchema(LogicalSchema):
 class Field(CanonicalPropertiesMixin, EqualByJsonMixin):
     _reserved_properties: Sequence[str] = FIELD_RESERVED_PROPS
 
-    def __init__(
-        self,
-        type_,
-        name,
-        has_default,
-        default=None,
-        order=None,
-        names=None,
-        doc=None,
-        other_props=None,
-    ):
+    def __init__(self, type_, name, has_default, default=None, order=None, names=None, doc=None, other_props=None, validate_names: bool = True):
         if not name:
             raise avro.errors.SchemaParseException("Fields must have a non-empty name.")
         if not isinstance(name, str):
@@ -338,13 +330,14 @@ class Field(CanonicalPropertiesMixin, EqualByJsonMixin):
             type_schema = names.get_name(type_, None)
         else:
             try:
-                type_schema = make_avsc_object(type_, names)
+                type_schema = make_avsc_object(type_, names, validate_names=validate_names)
             except Exception as e:
                 raise avro.errors.SchemaParseException(f'Type property "{type_}" not a valid Avro schema: {e}')
         self.set_prop("type", type_schema)
         self.set_prop("name", name)
         self.type = type_schema
         self.name = name
+        self.validate_names = validate_names
         # TODO(hammer): check to ensure default is valid
         if has_default:
             self.set_prop("default", default)
@@ -363,7 +356,7 @@ class Field(CanonicalPropertiesMixin, EqualByJsonMixin):
         return json.dumps(self.to_json())
 
     def to_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         to_dump = self.props.copy()
         to_dump["type"] = self.type.to_json(names)
@@ -371,7 +364,7 @@ class Field(CanonicalPropertiesMixin, EqualByJsonMixin):
         return to_dump
 
     def to_canonical_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         to_dump = self.canonical_properties
         to_dump["type"] = self.type.to_canonical_json(names)
@@ -471,14 +464,14 @@ class BytesDecimalSchema(PrimitiveSchema, DecimalLogicalSchema):
 # Complex Types (non-recursive)
 #
 class FixedSchema(EqualByPropsMixin, NamedSchema):
-    def __init__(self, name, namespace, size, names=None, other_props=None):
+    def __init__(self, name, namespace, size, names=None, other_props=None, validate_names: bool = True):
         # Ensure valid ctor args
         if not isinstance(size, int) or size < 0:
             fail_msg = "Fixed Schema requires a valid positive integer for size property."
             raise avro.errors.AvroException(fail_msg)
 
         # Call parent ctor
-        NamedSchema.__init__(self, "fixed", name, namespace, names, other_props)
+        NamedSchema.__init__(self, "fixed", name, namespace, names, other_props, validate_names=validate_names)
 
         # Add class members
         self.set_prop("size", size)
@@ -495,7 +488,7 @@ class FixedSchema(EqualByPropsMixin, NamedSchema):
         return self.type == writer.type and self.check_props(writer, ["fullname", "size"])
 
     def to_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         if self.fullname in names.names:
             return self.name_ref(names)
@@ -529,10 +522,11 @@ class FixedDecimalSchema(FixedSchema, DecimalLogicalSchema):
         namespace=None,
         names=None,
         other_props=None,
+        validate_names: bool = True,
     ):
         max_precision = int(math.floor(math.log10(2) * (8 * size - 1)))
         DecimalLogicalSchema.__init__(self, precision, scale, max_precision)
-        FixedSchema.__init__(self, name, namespace, size, names, other_props)
+        FixedSchema.__init__(self, name, namespace, size, names, other_props, validate_names=validate_names)
         self.set_prop("precision", precision)
         self.set_prop("scale", scale)
 
@@ -558,6 +552,7 @@ class EnumSchema(EqualByPropsMixin, NamedSchema):
         doc: Optional[str] = None,
         other_props: Optional[Mapping[str, object]] = None,
         validate_enum_symbols: bool = True,
+        validate_names: bool = True,
     ) -> None:
         """
         @arg validate_enum_symbols: If False, will allow enum symbols that are not valid Avro names.
@@ -573,7 +568,7 @@ class EnumSchema(EqualByPropsMixin, NamedSchema):
             raise avro.errors.AvroException(f"Duplicate symbol: {symbols}")
 
         # Call parent ctor
-        NamedSchema.__init__(self, "enum", name, namespace, names, other_props)
+        NamedSchema.__init__(self, "enum", name, namespace, names, other_props, validate_names)
 
         # Add class members
         self.set_prop("symbols", symbols)
@@ -603,7 +598,7 @@ class EnumSchema(EqualByPropsMixin, NamedSchema):
         return self.type == writer.type and self.check_props(writer, ["fullname"])
 
     def to_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         if self.fullname in names.names:
             return self.name_ref(names)
@@ -633,16 +628,16 @@ class EnumSchema(EqualByPropsMixin, NamedSchema):
 
 
 class ArraySchema(EqualByJsonMixin, Schema):
-    def __init__(self, items, names=None, other_props=None):
+    def __init__(self, items, names=None, other_props=None, validate_names: bool = True):
         # Call parent ctor
-        Schema.__init__(self, "array", other_props)
+        Schema.__init__(self, "array", other_props, validate_names=validate_names)
         # Add class members
 
         if isinstance(items, str) and names.has_name(items, None):
             items_schema = names.get_name(items, None)
         else:
             try:
-                items_schema = make_avsc_object(items, names)
+                items_schema = make_avsc_object(items, names, validate_names=self.validate_names)
             except avro.errors.SchemaParseException as e:
                 fail_msg = f"Items schema ({items}) not a valid Avro schema: {e} (known names: {names.names.keys()})"
                 raise avro.errors.SchemaParseException(fail_msg)
@@ -661,7 +656,7 @@ class ArraySchema(EqualByJsonMixin, Schema):
         return self.type == writer.type and self.items.check_props(writer.items, ["type"])
 
     def to_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         to_dump = self.props.copy()
         item_schema = self.get_prop("items")
@@ -670,7 +665,7 @@ class ArraySchema(EqualByJsonMixin, Schema):
         return to_dump
 
     def to_canonical_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         to_dump = self.canonical_properties
         item_schema = self.get_prop("items")
@@ -684,16 +679,16 @@ class ArraySchema(EqualByJsonMixin, Schema):
 
 
 class MapSchema(EqualByJsonMixin, Schema):
-    def __init__(self, values, names=None, other_props=None):
+    def __init__(self, values, names=None, other_props=None, validate_names: bool = True):
         # Call parent ctor
-        Schema.__init__(self, "map", other_props)
+        Schema.__init__(self, "map", other_props, validate_names=validate_names)
 
         # Add class members
         if isinstance(values, str) and names.has_name(values, None):
             values_schema = names.get_name(values, None)
         else:
             try:
-                values_schema = make_avsc_object(values, names)
+                values_schema = make_avsc_object(values, names, validate_names=self.validate_names)
             except avro.errors.SchemaParseException:
                 raise
             except Exception:
@@ -713,7 +708,7 @@ class MapSchema(EqualByJsonMixin, Schema):
         return writer.type == self.type and self.values.check_props(writer.values, ["type"])
 
     def to_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         to_dump = self.props.copy()
         to_dump["values"] = self.get_prop("values").to_json(names)
@@ -721,7 +716,7 @@ class MapSchema(EqualByJsonMixin, Schema):
         return to_dump
 
     def to_canonical_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         to_dump = self.canonical_properties
         to_dump["values"] = self.get_prop("values").to_canonical_json(names)
@@ -738,23 +733,23 @@ class UnionSchema(EqualByJsonMixin, Schema):
     names is a dictionary of schema objects
     """
 
-    def __init__(self, schemas, names=None):
+    def __init__(self, schemas, names=None, validate_names: bool = True):
         # Ensure valid ctor args
         if not isinstance(schemas, list):
             fail_msg = "Union schema requires a list of schemas."
             raise avro.errors.SchemaParseException(fail_msg)
 
         # Call parent ctor
-        Schema.__init__(self, "union")
+        Schema.__init__(self, "union", validate_names=validate_names)
 
         # Add class members
-        schema_objects = []
+        schema_objects: List[Schema] = []
         for schema in schemas:
             if isinstance(schema, str) and names.has_name(schema, None):
                 new_schema = names.get_name(schema, None)
             else:
                 try:
-                    new_schema = make_avsc_object(schema, names)
+                    new_schema = make_avsc_object(schema, names, validate_names=self.validate_names)
                 except Exception as e:
                     raise avro.errors.SchemaParseException(f"Union item must be a valid Avro schema: {e}")
             # check the new schema
@@ -782,7 +777,7 @@ class UnionSchema(EqualByJsonMixin, Schema):
         return writer.type in {"union", "error_union"} or any(s.match(writer) for s in self.schemas)
 
     def to_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         to_dump = []
         for schema in self.schemas:
@@ -791,7 +786,7 @@ class UnionSchema(EqualByJsonMixin, Schema):
         return to_dump
 
     def to_canonical_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         return [schema.to_canonical_json(names) for schema in self.schemas]
 
@@ -803,12 +798,12 @@ class UnionSchema(EqualByJsonMixin, Schema):
 
 
 class ErrorUnionSchema(UnionSchema):
-    def __init__(self, schemas, names=None):
+    def __init__(self, schemas, names=None, validate_names: bool = True):
         # Prepend "string" to handle system errors
-        UnionSchema.__init__(self, ["string"] + schemas, names)
+        UnionSchema.__init__(self, ["string"] + schemas, names, validate_names)
 
     def to_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         to_dump = []
         for schema in self.schemas:
@@ -822,7 +817,7 @@ class ErrorUnionSchema(UnionSchema):
 
 class RecordSchema(EqualByJsonMixin, NamedSchema):
     @staticmethod
-    def make_field_objects(field_data: Sequence[Mapping[str, object]], names: avro.name.Names) -> Sequence[Field]:
+    def make_field_objects(field_data: Sequence[Mapping[str, object]], names: avro.name.Names, validate_names: bool = True) -> Sequence[Field]:
         """We're going to need to make message parameters too."""
         field_objects = []
         field_names = []
@@ -838,7 +833,7 @@ class RecordSchema(EqualByJsonMixin, NamedSchema):
             order = field.get("order")
             doc = field.get("doc")
             other_props = get_other_props(field, FIELD_RESERVED_PROPS)
-            new_field = Field(type, name, has_default, default, order, names, doc, other_props)
+            new_field = Field(type, name, has_default, default, order, names, doc, other_props, validate_names=validate_names)
             # make sure field name has not been used yet
             if new_field.name in field_names:
                 fail_msg = f"Field name {new_field.name} already in use."
@@ -864,6 +859,7 @@ class RecordSchema(EqualByJsonMixin, NamedSchema):
         schema_type="record",
         doc=None,
         other_props=None,
+        validate_names: bool = True,
     ):
         # Ensure valid ctor args
         if fields is None:
@@ -877,14 +873,15 @@ class RecordSchema(EqualByJsonMixin, NamedSchema):
         if schema_type == "request":
             Schema.__init__(self, schema_type, other_props)
         else:
-            NamedSchema.__init__(self, schema_type, name, namespace, names, other_props)
+            NamedSchema.__init__(self, schema_type, name, namespace, names, other_props, validate_names=validate_names)
 
+        names = names or Names(validate_names=self.validate_names)
         if schema_type == "record":
             old_default = names.default_namespace
-            names.default_namespace = Name(name, namespace, names.default_namespace).space
+            names.default_namespace = Name(name, namespace, names.default_namespace, validate_name=validate_names).space
 
         # Add class members
-        field_objects = RecordSchema.make_field_objects(fields, names)
+        field_objects = RecordSchema.make_field_objects(fields, names, validate_names=validate_names)
         self.set_prop("fields", field_objects)
         if doc is not None:
             self.set_prop("doc", doc)
@@ -904,7 +901,7 @@ class RecordSchema(EqualByJsonMixin, NamedSchema):
         return fields_dict
 
     def to_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         # Request records don't have names
         if self.type == "request":
@@ -921,7 +918,7 @@ class RecordSchema(EqualByJsonMixin, NamedSchema):
         return to_dump
 
     def to_canonical_json(self, names=None):
-        names = names or Names()
+        names = names or Names(validate_names=self.validate_names)
 
         if self.type == "request":
             raise NotImplementedError("Canonical form (probably) does not make sense on type request")
@@ -1102,14 +1099,16 @@ def make_logical_schema(logical_type, type_, other_props):
     return None
 
 
-def make_avsc_object(json_data: object, names: Optional[avro.name.Names] = None, validate_enum_symbols: bool = True) -> Schema:
+def make_avsc_object(
+    json_data: object, names: Optional[avro.name.Names] = None, validate_enum_symbols: bool = True, validate_names: bool = True
+) -> Schema:
     """
     Build Avro Schema from data parsed out of JSON string.
 
     @arg names: A Names object (tracks seen names and default space)
     @arg validate_enum_symbols: If False, will allow enum symbols that are not valid Avro names.
     """
-    names = names or Names()
+    names = names or Names(validate_names=validate_names)
 
     # JSON object (non-union)
     if callable(getattr(json_data, "get", None)):
@@ -1134,10 +1133,10 @@ def make_avsc_object(json_data: object, names: Optional[avro.name.Names] = None,
                     precision = json_data.get("precision")
                     scale = json_data.get("scale", 0)
                     try:
-                        return FixedDecimalSchema(size, name, precision, scale, namespace, names, other_props)
+                        return FixedDecimalSchema(size, name, precision, scale, namespace, names, other_props, validate_names)
                     except avro.errors.IgnoredLogicalType as warning:
                         warnings.warn(warning)
-                return FixedSchema(name, namespace, size, names, other_props)
+                return FixedSchema(name, namespace, size, names, other_props, validate_names=validate_names)
             elif type_ == "enum":
                 symbols = json_data.get("symbols")
                 if not isinstance(symbols, Sequence):
@@ -1146,19 +1145,11 @@ def make_avsc_object(json_data: object, names: Optional[avro.name.Names] = None,
                     if not isinstance(symbol, str):
                         raise avro.errors.SchemaParseException(f"Enum symbols must be a sequence of strings, but one symbol is a {type(symbol)}")
                 doc = json_data.get("doc")
-                return EnumSchema(
-                    name,
-                    namespace,
-                    symbols,
-                    names,
-                    doc,
-                    other_props,
-                    validate_enum_symbols,
-                )
+                return EnumSchema(name, namespace, symbols, names, doc, other_props, validate_enum_symbols, validate_names)
             if type_ in ["record", "error"]:
                 fields = json_data.get("fields")
                 doc = json_data.get("doc")
-                return RecordSchema(name, namespace, fields, names, type_, doc, other_props)
+                return RecordSchema(name, namespace, fields, names, type_, doc, other_props, validate_names)
             raise avro.errors.SchemaParseException(f"Unknown Named Type: {type_}")
 
         if type_ in PRIMITIVE_TYPES:
@@ -1167,13 +1158,13 @@ def make_avsc_object(json_data: object, names: Optional[avro.name.Names] = None,
         if type_ in VALID_TYPES:
             if type_ == "array":
                 items = json_data.get("items")
-                return ArraySchema(items, names, other_props)
+                return ArraySchema(items, names, other_props, validate_names)
             elif type_ == "map":
                 values = json_data.get("values")
-                return MapSchema(values, names, other_props)
+                return MapSchema(values, names, other_props, validate_names)
             elif type_ == "error_union":
                 declared_errors = json_data.get("declared_errors")
-                return ErrorUnionSchema(declared_errors, names)
+                return ErrorUnionSchema(declared_errors, names, validate_names)
             else:
                 raise avro.errors.SchemaParseException(f"Unknown Valid Type: {type_}")
         elif type_ is None:
@@ -1182,7 +1173,7 @@ def make_avsc_object(json_data: object, names: Optional[avro.name.Names] = None,
             raise avro.errors.SchemaParseException(f"Undefined type: {type_}")
     # JSON array (union)
     elif isinstance(json_data, list):
-        return UnionSchema(json_data, names)
+        return UnionSchema(json_data, names, validate_names=validate_names)
     # JSON string (primitive)
     elif json_data in PRIMITIVE_TYPES:
         return PrimitiveSchema(json_data)
@@ -1191,22 +1182,24 @@ def make_avsc_object(json_data: object, names: Optional[avro.name.Names] = None,
     raise avro.errors.SchemaParseException(fail_msg)
 
 
-def parse(json_string: str, validate_enum_symbols: bool = True) -> Schema:
+def parse(json_string: str, validate_enum_symbols: bool = True, validate_names: bool = True) -> Schema:
     """Constructs the Schema from the JSON text.
 
     @arg json_string: The json string of the schema to parse
     @arg validate_enum_symbols: If False, will allow enum symbols that are not valid Avro names.
+    @arg validate_names: If False, will allow names that are not valid Avro names. When disabling the validation
+                         test the non-compliant names for non-compliant behavior, also in interoperability cases.
     @return Schema
     """
     try:
         json_data = json.loads(json_string)
     except json.decoder.JSONDecodeError as e:
         raise avro.errors.SchemaParseException(f"Error parsing JSON: {json_string}, error = {e}") from e
-    return make_avsc_object(json_data, Names(), validate_enum_symbols)
+    return make_avsc_object(json_data, Names(validate_names=validate_names), validate_enum_symbols, validate_names)
 
 
-def from_path(path: Union[Path, str], validate_enum_symbols: bool = True) -> Schema:
+def from_path(path: Union[Path, str], validate_enum_symbols: bool = True, validate_names: bool = True) -> Schema:
     """
     Constructs the Schema from a path to an avsc (json) file.
     """
-    return parse(Path(path).read_text(), validate_enum_symbols)
+    return parse(Path(path).read_text(), validate_enum_symbols, validate_names)
diff --git a/lang/py/avro/test/test_name.py b/lang/py/avro/test/test_name.py
new file mode 100644
index 000000000..3790c9d10
--- /dev/null
+++ b/lang/py/avro/test/test_name.py
@@ -0,0 +1,327 @@
+#!/usr/bin/env python3
+
+##
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Test the name parsing logic."""
+
+import json
+import unittest
+
+import avro.errors
+import avro.protocol
+import avro.schema
+
+
+class TestName(unittest.TestCase):
+    """Test name parsing"""
+
+    def test_name_is_none(self):
+        """When a name is None its namespace is None."""
+        self.assertIsNone(avro.schema.Name(None, None, None).fullname)
+        self.assertIsNone(avro.schema.Name(None, None, None).space)
+
+    def test_name_not_empty_string(self):
+        """A name cannot be the empty string."""
+        self.assertRaises(avro.errors.SchemaParseException, avro.schema.Name, "", None, None)
+
+    def test_name_space_specified(self):
+        """Space combines with a name to become the fullname."""
+        # name and namespace specified
+        fullname = avro.schema.Name("a", "o.a.h", None).fullname
+        self.assertEqual(fullname, "o.a.h.a")
+
+    def test_name_inlined_space(self):
+        """Space inlined with name is correctly splitted out."""
+        name = avro.schema.Name("o.a", None)
+        self.assertEqual(name.fullname, "o.a")
+        self.assertEqual(name.name, "a")
+        self.assertEqual(name.space, "o")
+
+        name = avro.schema.Name("o.a.h.a", None)
+        self.assertEqual(name.fullname, "o.a.h.a")
+        self.assertEqual(name.name, "a")
+        self.assertEqual(name.space, "o.a.h")
+
+    def test_fullname_space_specified(self):
+        """When name contains dots, namespace should be ignored."""
+        fullname = avro.schema.Name("a.b.c.d", "o.a.h", None).fullname
+        self.assertEqual(fullname, "a.b.c.d")
+
+    def test_name_default_specified(self):
+        """Default space becomes the namespace when the namespace is None."""
+        fullname = avro.schema.Name("a", None, "b.c.d").fullname
+        self.assertEqual(fullname, "b.c.d.a")
+
+    def test_fullname_default_specified(self):
+        """When a name contains dots, default space should be ignored."""
+        fullname = avro.schema.Name("a.b.c.d", None, "o.a.h").fullname
+        self.assertEqual(fullname, "a.b.c.d")
+
+    def test_fullname_space_default_specified(self):
+        """When a name contains dots, namespace and default space should be ignored."""
+        fullname = avro.schema.Name("a.b.c.d", "o.a.a", "o.a.h").fullname
+        self.assertEqual(fullname, "a.b.c.d")
+
+    def test_name_space_default_specified(self):
+        """When name and space are specified, default space should be ignored."""
+        fullname = avro.schema.Name("a", "o.a.a", "o.a.h").fullname
+        self.assertEqual(fullname, "o.a.a.a")
+
+    def test_equal_names(self):
+        """Equality of names is defined on the fullname and is case-sensitive."""
+        self.assertEqual(
+            avro.schema.Name("a.b.c.d", None, None),
+            avro.schema.Name("d", "a.b.c", None),
+        )
+        self.assertNotEqual(avro.schema.Name("C.d", None, None), avro.schema.Name("c.d", None, None))
+
+    def test_invalid_name(self):
+        """The name portion of a fullname, record field names, and enum symbols must:
+        start with [A-Za-z_] and subsequently contain only [A-Za-z0-9_]"""
+        self.assertRaises(
+            avro.errors.InvalidName,
+            avro.schema.Name,
+            "an especially spacey cowboy",
+            None,
+            None,
+        )
+        self.assertRaises(
+            avro.errors.InvalidName,
+            avro.schema.Name,
+            "99 problems but a name aint one",
+            None,
+            None,
+        )
+        # A name cannot start with dot.
+        self.assertRaises(avro.errors.InvalidName, avro.schema.Name, ".a", None, None)
+        self.assertRaises(avro.errors.InvalidName, avro.schema.Name, "o..a", None, None)
+        self.assertRaises(avro.errors.InvalidName, avro.schema.Name, "a.", None, None)
+
+    def test_null_namespace(self):
+        """The empty string may be used as a namespace to indicate the null namespace."""
+        name = avro.schema.Name("name", "", None)
+        self.assertEqual(name.fullname, "name")
+        self.assertIsNone(name.space)
+
+    def test_disable_name_validation(self):
+        """Test name validation disable."""
+        # Test name class
+        avro.schema.Name(name_attr="an especially spacey cowboy", space_attr=None, default_space=None, validate_name=False)
+        avro.schema.Name(name_attr="cowboy", space_attr="an especially spacey ", default_space=None, validate_name=False)
+        avro.schema.Name(name_attr="cowboy", space_attr=None, default_space="an especially spacey ", validate_name=False)
+        avro.schema.Name(name_attr="name-space.with-dash.cowboy", space_attr=None, default_space=None, validate_name=False)
+        avro.schema.Name(name_attr="cowboy", space_attr="name-space.with-dash", default_space=None, validate_name=False)
+
+        # Test record schema
+        cowboy_record_1 = avro.schema.RecordSchema(
+            name="an especially spacey cowboy", namespace=None, fields=[{"name": "value", "type": "long"}], validate_names=False
+        )
+        cowboy_record_2 = avro.schema.RecordSchema(
+            name="cowboy", namespace="an especially spacey ", fields=[{"name": "value", "type": "int"}], validate_names=False
+        )
+
+        # Test Names container class
+        names = avro.schema.Names(default_namespace=None, validate_names=False)
+        names.add_name(name_attr=cowboy_record_1.name, space_attr=cowboy_record_1.namespace, new_schema=cowboy_record_1)
+        names.add_name(name_attr=cowboy_record_2.name, space_attr=cowboy_record_2.namespace, new_schema=cowboy_record_2)
+
+        # Test fixed schema
+        avro.schema.FixedSchema(name="an especially spacey cowboy", namespace=None, size=16, validate_names=False)
+        avro.schema.FixedSchema(name="cowboy", namespace="an especially spacey", size=16, validate_names=False)
+
+        # Test fixed decimal schema
+        avro.schema.FixedDecimalSchema(name="an especially spacey cowboy", namespace=None, size=16, precision=2, validate_names=False)
+        avro.schema.FixedDecimalSchema(name="cowboy", namespace="an especially spacey", size=16, precision=2, validate_names=False)
+
+        # Test enum schema
+        avro.schema.EnumSchema(name="an especially spacey cowboy", namespace=None, symbols=["A", "B"], validate_names=False)
+        avro.schema.EnumSchema(name="cowboy", namespace="an especially spacey", symbols=["A", "B"], validate_names=False)
+
+
+EXAMPLES = [
+    # Enum
+    {"type": "enum", "name": "invalid-name", "symbols": ["A", "B"]},
+    {"type": "enum", "name": "invalid-ns.ab", "symbols": ["A", "B"]},
+    {"type": "enum", "name": "ab", "namespace": "invalid-ns", "symbols": ["A", "B"]},
+    # Record
+    {"type": "record", "name": "invalid-name", "fields": [{"name": "distance", "type": "long"}]},
+    {"type": "record", "name": "invalid-ns.journey", "fields": [{"name": "distance", "type": "long"}]},
+    {"type": "record", "name": "journey", "namespace": "invalid-ns", "fields": [{"name": "distance", "type": "long"}]},
+    # FixedSchema
+    {"type": "fixed", "name": "invalid-name", "size": 10, "precision": 2},
+    {"type": "fixed", "name": "invalid-ns.irrational", "size": 10, "precision": 2},
+    {"type": "fixed", "name": "irrational", "namespace": "invalid-ns", "size": 10, "precision": 2},
+    # FixedDecimalSchema / logical type
+    {"type": "fixed", "logicalType": "decimal", "name": "invalid-name", "size": 10, "precision": 2},
+    {"type": "fixed", "logicalType": "decimal", "name": "invalid-ns.irrational", "size": 10, "precision": 2},
+    {"type": "fixed", "logicalType": "decimal", "name": "irrational", "namespace": "invalid-ns", "size": 10, "precision": 2},
+    # In fields
+    {
+        "type": "record",
+        "name": "world",
+        "fields": [
+            {
+                "type": {"type": "record", "name": "invalid-name", "fields": [{"name": "distance", "type": "long"}]},
+                "name": "cup",
+            },
+        ],
+    },
+    # In union
+    [{"type": "string"}, {"type": "record", "name": "invalid-name", "fields": [{"name": "distance", "type": "long"}]}],
+    # In array
+    {
+        "type": "record",
+        "name": "world",
+        "fields": [
+            {
+                "name": "journeys",
+                "type": {
+                    "type": "array",
+                    "items": {
+                        "type": "record",
+                        "name": "invalid-name",
+                        "fields": [{"name": "distance", "type": "long"}],
+                    },
+                },
+            },
+        ],
+    },
+    # In map
+    {
+        "type": "record",
+        "name": "world",
+        "fields": [
+            {
+                "name": "journeys",
+                "type": {
+                    "type": "map",
+                    "values": {
+                        "type": "record",
+                        "name": "invalid-name",
+                        "fields": [{"name": "distance", "type": "long"}],
+                    },
+                },
+            },
+        ],
+    },
+]
+
+
+class ParseSchemaNameValidationDisabledTestCase(unittest.TestCase):
+    """Enable generating parse test cases over all the valid and invalid example schema."""
+
+    def __init__(self, test_schema_string):
+        """Ignore the normal signature for unittest.TestCase because we are generating
+        many test cases from this one class. This is safe as long as the autoloader
+        ignores this class. The autoloader will ignore this class as long as it has
+        no methods starting with `test_`.
+        """
+        super().__init__("parse_invalid_name")
+        self.test_schema_string = test_schema_string
+
+    def parse_invalid_name(self) -> None:
+        """Parsing a schema with invalid name should not error"""
+        schema_string = json.dumps(self.test_schema_string)
+        # Parse with validation to ensure that correct exception is raised when validation enabled.
+        with self.assertRaises(
+            (avro.errors.AvroException, avro.errors.SchemaParseException), msg=f"Invalid schema should not have parsed: {self.test_schema_string!s}"
+        ):
+            avro.schema.parse(schema_string, validate_names=True)
+
+        # The actual test with validation disabled.
+        avro.schema.parse(schema_string, validate_names=False)
+
+
+PROTOCOL_EXAMPLES = [
+    # In record
+    {
+        "namespace": "lightyear",
+        "protocol": "lightspeed",
+        "types": [
+            {"name": "current-speed", "type": "record", "fields": [{"name": "speed", "type": "int"}, {"name": "unit", "type": "string"}]},
+            {"name": "over_c", "type": "error", "fields": [{"name": "message", "type": "string"}]},
+        ],
+        "messages": {
+            "speedmessage": {"request": [{"name": "current_speed", "type": "current-speed"}], "response": "current-speed", "errors": ["over_c"]}
+        },
+    },
+    # Error union
+    {
+        "namespace": "lightyear",
+        "protocol": "lightspeed",
+        "types": [
+            {"name": "current_speed", "type": "record", "fields": [{"name": "speed", "type": "int"}, {"name": "unit", "type": "string"}]},
+            {"name": "over-c", "type": "error", "fields": [{"name": "message", "type": "string"}]},
+        ],
+        "messages": {
+            "speedmessage": {"request": [{"name": "current_speed", "type": "current_speed"}], "response": "current_speed", "errors": ["over-c"]}
+        },
+    },
+    {
+        "namespace": "lightyear",
+        "protocol": "lightspeed",
+        "types": [
+            {"name": "current_speed", "type": "record", "fields": [{"name": "speed", "type": "int"}, {"name": "unit", "type": "string"}]},
+            {"name": "over_c", "namespace": "error-speed", "type": "error", "fields": [{"name": "message", "type": "string"}]},
+        ],
+        "messages": {
+            "speedmessage": {
+                "request": [{"name": "current_speed", "type": "current_speed"}],
+                "response": "current_speed",
+                "errors": ["error-speed.over_c"],
+            }
+        },
+    },
+]
+
+
+class ParseProtocolNameValidationDisabledTestCase(unittest.TestCase):
+    """Test parsing of protocol when name validation is disabled."""
+
+    def __init__(self, test_protocol_string):
+        """Ignore the normal signature for unittest.TestCase because we are generating
+        many test cases from this one class. This is safe as long as the autoloader
+        ignores this class. The autoloader will ignore this class as long as it has
+        no methods starting with `test_`.
+        """
+        super().__init__("parse_protocol_with_invalid_name")
+        self.test_protocol_string = test_protocol_string
+
+    def parse_protocol_with_invalid_name(self):
+        """Test error union"""
+        protocol = json.dumps(self.test_protocol_string)
+
+        with self.assertRaises(
+            (avro.errors.AvroException, avro.errors.SchemaParseException), msg=f"Invalid schema should not have parsed: {self.test_protocol_string!s}"
+        ):
+            avro.protocol.parse(protocol, validate_names=True)
+
+        avro.protocol.parse(protocol, validate_names=False)
+
+
+def load_tests(loader, default_tests, pattern):
+    """Generate test cases across many test schema."""
+    suite = unittest.TestSuite()
+    suite.addTests(loader.loadTestsFromTestCase(TestName))
+    suite.addTests(ParseSchemaNameValidationDisabledTestCase(ex) for ex in EXAMPLES)
+    suite.addTests(ParseProtocolNameValidationDisabledTestCase(ex) for ex in PROTOCOL_EXAMPLES)
+    return suite
+
+
+if __name__ == "__main__":  # pragma: no coverage
+    unittest.main()
diff --git a/lang/py/avro/test/test_schema.py b/lang/py/avro/test/test_schema.py
index ab61918d6..8286567f9 100644
--- a/lang/py/avro/test/test_schema.py
+++ b/lang/py/avro/test/test_schema.py
@@ -564,94 +564,6 @@ class TestMisc(unittest.TestCase):
         # If we've made it this far, the subschema was reasonably stringified; it ccould be reparsed.
         self.assertEqual("X", t.fields[0].type.name)
 
-    def test_name_is_none(self):
-        """When a name is None its namespace is None."""
-        self.assertIsNone(avro.schema.Name(None, None, None).fullname)
-        self.assertIsNone(avro.schema.Name(None, None, None).space)
-
-    def test_name_not_empty_string(self):
-        """A name cannot be the empty string."""
-        self.assertRaises(avro.errors.SchemaParseException, avro.schema.Name, "", None, None)
-
-    def test_name_space_specified(self):
-        """Space combines with a name to become the fullname."""
-        # name and namespace specified
-        fullname = avro.schema.Name("a", "o.a.h", None).fullname
-        self.assertEqual(fullname, "o.a.h.a")
-
-    def test_name_inlined_space(self):
-        """Space inlined with name is correctly splitted out."""
-        name = avro.schema.Name("o.a", None)
-        self.assertEqual(name.fullname, "o.a")
-        self.assertEqual(name.name, "a")
-        self.assertEqual(name.space, "o")
-
-        name = avro.schema.Name("o.a.h.a", None)
-        self.assertEqual(name.fullname, "o.a.h.a")
-        self.assertEqual(name.name, "a")
-        self.assertEqual(name.space, "o.a.h")
-
-    def test_fullname_space_specified(self):
-        """When name contains dots, namespace should be ignored."""
-        fullname = avro.schema.Name("a.b.c.d", "o.a.h", None).fullname
-        self.assertEqual(fullname, "a.b.c.d")
-
-    def test_name_default_specified(self):
-        """Default space becomes the namespace when the namespace is None."""
-        fullname = avro.schema.Name("a", None, "b.c.d").fullname
-        self.assertEqual(fullname, "b.c.d.a")
-
-    def test_fullname_default_specified(self):
-        """When a name contains dots, default space should be ignored."""
-        fullname = avro.schema.Name("a.b.c.d", None, "o.a.h").fullname
-        self.assertEqual(fullname, "a.b.c.d")
-
-    def test_fullname_space_default_specified(self):
-        """When a name contains dots, namespace and default space should be ignored."""
-        fullname = avro.schema.Name("a.b.c.d", "o.a.a", "o.a.h").fullname
-        self.assertEqual(fullname, "a.b.c.d")
-
-    def test_name_space_default_specified(self):
-        """When name and space are specified, default space should be ignored."""
-        fullname = avro.schema.Name("a", "o.a.a", "o.a.h").fullname
-        self.assertEqual(fullname, "o.a.a.a")
-
-    def test_equal_names(self):
-        """Equality of names is defined on the fullname and is case-sensitive."""
-        self.assertEqual(
-            avro.schema.Name("a.b.c.d", None, None),
-            avro.schema.Name("d", "a.b.c", None),
-        )
-        self.assertNotEqual(avro.schema.Name("C.d", None, None), avro.schema.Name("c.d", None, None))
-
-    def test_invalid_name(self):
-        """The name portion of a fullname, record field names, and enum symbols must:
-        start with [A-Za-z_] and subsequently contain only [A-Za-z0-9_]"""
-        self.assertRaises(
-            avro.errors.InvalidName,
-            avro.schema.Name,
-            "an especially spacey cowboy",
-            None,
-            None,
-        )
-        self.assertRaises(
-            avro.errors.InvalidName,
-            avro.schema.Name,
-            "99 problems but a name aint one",
-            None,
-            None,
-        )
-        # A name cannot start with dot.
-        self.assertRaises(avro.errors.InvalidName, avro.schema.Name, ".a", None, None)
-        self.assertRaises(avro.errors.InvalidName, avro.schema.Name, "o..a", None, None)
-        self.assertRaises(avro.errors.InvalidName, avro.schema.Name, "a.", None, None)
-
-    def test_null_namespace(self):
-        """The empty string may be used as a namespace to indicate the null namespace."""
-        name = avro.schema.Name("name", "", None)
-        self.assertEqual(name.fullname, "name")
-        self.assertIsNone(name.space)
-
     def test_exception_is_not_swallowed_on_parse_error(self):
         """A specific exception message should appear on a json parse error."""
         self.assertRaisesRegex(