Skip to main content
Version: dev

Python

Output Layout​

Python output is one module per schema file, for example:

  • <python_out>/addressbook.py

Type Generation​

Unions generate a case enum plus a Union subclass with typed helpers:

class AnimalCase(Enum):
DOG = 1
CAT = 2

class Animal(Union):
@classmethod
def dog(cls, v: Dog) -> "Animal": ...

def case(self) -> AnimalCase: ...
def case_id(self) -> int: ...

def is_dog(self) -> bool: ...
def dog_value(self) -> Dog: ...
def set_dog(self, v: Dog) -> None: ...

Messages generate @pyfory.dataclass types, and nested types stay nested:

@pyfory.dataclass
class Person:
class PhoneType(IntEnum):
MOBILE = 0
HOME = 1
WORK = 2

@pyfory.dataclass
class PhoneNumber:
number: str = pyfory.field(id=1, default="")
phone_type: Person.PhoneType = pyfory.field(id=2, default=None)

name: str = pyfory.field(id=1, default="")
phones: List[Person.PhoneNumber] = pyfory.field(id=7, default_factory=list)
pet: Animal = pyfory.field(id=8, default=None)

def to_bytes(self) -> bytes: ...
@classmethod
def from_bytes(cls, data: bytes) -> "Person": ...

Field Names​

Generated Python attributes use snake_case, with a trailing underscore for Python keywords: class becomes class_. Already-suffixed keyword names stay unchanged: class_ remains class_, and class__ remains class__.

If two fields in the same message produce the same Python attribute name after conversion and escaping, compilation fails. For example, class and class_ both produce class_, while userName and user_name both produce user_name. Rename one of the fields in the schema to resolve the conflict.

The names field, pyfory, decimal, list, and dict also receive a trailing underscore. Names consisting of one of these five names followed by underscores receive one additional underscore so fields remain distinct: field, field_, and field__ become field_, field__, and field___. Field tag IDs remain unchanged.

Registration​

Generated registration function:

def register_addressbook_types(fory: pyfory.Fory):
fory.register_union(Animal, type_id=106, serializer=AnimalSerializer(fory))
fory.register_type(Person, type_id=100)
fory.register_type(Person.PhoneType, type_id=101)
fory.register_type(Person.PhoneNumber, type_id=102)
fory.register_type(Dog, type_id=104)
fory.register_type(Cat, type_id=105)
fory.register_type(AddressBook, type_id=103)

For schemas without explicit [id=...], generated registration uses computed numeric IDs:

fory.register_type(Status, type_id=1124725126)
fory.register_union(Wrapper, type_id=1471345060, serializer=WrapperSerializer(fory))
fory.register_type(Envelope, type_id=3022445236)
fory.register_union(Envelope.Detail, type_id=1609214087, serializer=Envelope.DetailSerializer(fory))
fory.register_type(Envelope.Payload, type_id=2862577837)

If option enable_auto_type_id = false; is set:

fory.register_type(Config, name="myapp.models.Config")
fory.register_union(
Holder,
name="myapp.models.Holder",
serializer=HolderSerializer(fory),
)

Usage​

person = Person(name="Alice", pet=Animal.dog(Dog(name="Rex", bark_volume=10)))

data = person.to_bytes()
restored = Person.from_bytes(data)

gRPC Service Companions​

With --grpc, Python emits <module>_grpc.py with stubs, servicer bases, and registration functions. Async grpc.aio is the default; --grpc-python-mode=sync keeps the same filename and public names. See Python gRPC for dependencies and usage.