pak.packets.packet¶
Base code for Packets.
- exception ReservedFieldError(packet_cls, field)¶
Bases:
ExceptionAn error indicating a field was defined with a reserved name.
See
Packet.RESERVED_FIELDSfor more information.
- exception DuplicateFieldError(packet_cls, field)¶
Bases:
ExceptionAn error indicating a field was defined twice.
Raised when declaring a field already declared in a parent
Packet.
- class Packet(*, ctx=None, **fields)¶
Bases:
objectA collection of values that can be marshaled to and from raw data using
Types.The difference between a
Packetand aTypeis thatTypes only define how to marshal values to and from raw data, whilePackets actually contain values themselves.To unpack a
Packetfrom raw data, you should use theunpack()method instead of the constructor.- Parameters:
ctx (
Packet.Context) – The context for thePacket.**fields – The names and corresponding values of the fields of the
Packet.
- Raises:
TypeError – If there are any superfluous keyword arguments.
Examples
Basic functionality:
>>> import pak >>> class MyPacket(pak.Packet): ... attr1: pak.Int8 ... attr2: pak.Int16 ... >>> p = MyPacket() >>> p MyPacket(attr1=0, attr2=0) >>> p.pack() b'\x00\x00\x00' >>> p = MyPacket(attr1=1, attr2=2) >>> p.pack() b'\x01\x02\x00' >>> MyPacket.unpack(b"\xff\x00\x80") MyPacket(attr1=-1, attr2=-32768)
Additionally your attributes can be properties:
>>> class MyPacket(pak.Packet): ... prop: pak.Int8 ... @property ... def prop(self): ... return self._prop ... @prop.setter ... def prop(self, value): ... self._prop = value + 1 ... >>> p = MyPacket() >>> p # Int8's default is 0, plus 1 is 1 MyPacket(prop=1) >>> p.prop = 2 >>> p MyPacket(prop=3) >>> p.pack() b'\x03'
If an attribute is read only, it will only raise an error if you explicitly try to set it, i.e. you specify the value for it in the constructor.
- class Context¶
Bases:
objectThe context for a
Packet.Packet.Contexts are used to pass arbitrary data toPacketoperations, typically just being wrapped in aType.Contextand sent off toTypeoperations, such as unpacking and packing.You should customize this class to suit your own purposes, like so:
>>> import pak >>> class MyPacket(pak.Packet): ... class Context(pak.Packet.Context): ... def __init__(self): ... self.info = ... ... super().__init__() ... ... def __hash__(self): ... return hash(self.info) ... ... def __eq__(self, other): ... return self.info == other.info ...
When no
Packet.Contextis provided toPacketoperations that may accept one, then your subclass is attempted to be default constructed and used instead.Warning
Subclasses must be properly hashable. Accordingly, this means that subclasses should also be immutable. Therefore, the constructor of
Packet.Contextsets the constructed object to be immutable.If a subclass of
Packet.Contextdoes not provide its own__hash__implementation, then aTypeErroris raised. Since all hashable objects should also be equality comparable, if a subclass ofPacket.Contextdoes not provide its own__eq__implementation, then aTypeErroris raised as well.
- classmethod EmptyWithID(id, /)¶
Generates an empty subclass of the
Packetclass with the specified ID.Note
This method is decorated with
util.cache().This means that when called twice with the same ID, then the exact same class will be returned.
- Parameters:
id – The ID of the generated
Packet.- Returns:
The generated
Packet.- Return type:
subclass of
Packet
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... class Header(pak.Packet.Header): ... id: pak.Int8 ... >>> empty = MyPacket.EmptyWithID(1) >>> empty is MyPacket.EmptyWithID(1) True >>> issubclass(empty, MyPacket) True >>> packet = empty.unpack(b"any data") >>> packet MyPacket.EmptyWithID(1)() >>> packet.pack() b'\x01'
- classmethod GenericWithID(id, /)¶
Generates a subclass of the
Packetclass andGenericPacketwith the specified ID.Note
This method is decorated with
util.cache().This means that when called twice with the same ID, then the exact same class will be returned.
GenericPacketwill be inherited from such that itsdatafield will be after all the fields of the parentPacket.- Parameters:
id – The ID of the generated
Packet.- Returns:
The generated
Packet.- Return type:
subclass of
Packet
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... class Header(pak.Packet.Header): ... id: pak.Int8 ... ... field: pak.Int16 ... >>> generic = MyPacket.GenericWithID(1) >>> generic is MyPacket.GenericWithID(1) True >>> issubclass(generic, MyPacket) True >>> issubclass(generic, pak.GenericPacket) True >>> packet = generic.unpack(b"\x02\x00generic data") >>> packet MyPacket.GenericWithID(1)(field=2, data=bytearray(b'generic data')) >>> packet.pack() b'\x01\x02\x00generic data'
- class Header(packet=None, /, *, ctx=None, **fields)¶
Bases:
PacketThe header for a
Packet.Must be accessible from the
Headerattribute of yourPacketclass, such asMyPacket.Header.For example:
>>> import pak >>> class MyPacket(pak.Packet): ... byte: pak.Int8 ... short: pak.Int16 ... class Header(pak.Packet.Header): ... size: pak.UInt8 ... byte: pak.Int8 ... >>> MyPacket.Header(MyPacket(byte=1)) MyPacket.Header(size=3, byte=1) >>> # Alternatively, you may use the 'Packet.header' method >>> MyPacket(byte=1).header() MyPacket.Header(size=3, byte=1)
To unpack the header, simply call
Packet.unpack()on the header class, like so:>>> import pak >>> class MyPacket(pak.Packet): ... byte: pak.Int8 ... short: pak.Int16 ... class Header(pak.Packet.Header): ... size: pak.UInt8 ... byte: pak.Int8 ... >>> MyPacket.Header.unpack(b"\x03\x01") MyPacket.Header(size=3, byte=1)
Note
Subclasses of
Packet.Header(i.e. allPacketheaders) may not have headers or contexts of their own.- Parameters:
packet (
PacketorNone) –The
Packetfor which the header is for.Each field of the header will be acquired from
packet. If the corresponding attribute inpacketis a method, then it will be called with thectxkeyword argument.If not
None, then no**fieldsmust be passed.ctx (
Packet.Context) – The context for thePacket.**fields –
The names and corresponding values of the fields of the
Packet.If
packetis notNone, then no fields may be passed.
- Raises:
TypeError – If
packetis notNoneand any**fieldsare passed.
- pack(*, ctx=None)¶
Overrides
Packet.pack()to callPacket.pack_without_header()to avoid infinite recursion when packing.
- RESERVED_FIELDS = ['ctx']¶
- copy(**new_attrs)¶
Makes a mutable copy of the
Packet.See also
- Parameters:
**new_attrs – The new attributes to set on the copy.
- Returns:
The copied
Packet.- Return type:
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... field: pak.Int8 ... >>> orig = MyPacket(field=1) >>> copy = orig.copy() >>> copy == orig True >>> copy is not orig True >>> >>> # Attributes that aren't fields will also be copied: >>> orig = MyPacket(field=2) >>> orig.custom_attr = "custom" >>> copy = orig.copy() >>> copy == orig True >>> copy.custom_attr 'custom' >>> >>> # The copy can be mutated: >>> copy.field = 3 >>> copy MyPacket(field=3) >>> >>> # You can set new fields on the copy by passing keyword arguments: >>> orig.copy(field=4) MyPacket(field=4)
- classmethod enumerate_field_types()¶
Enumerates the
Types of the fields of thePacket.- Returns:
Each element of the iterable is a (
field_name,field_type) pair.- Return type:
iterable
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... attr1: pak.Int8 ... attr2: pak.Int16 ... >>> for attr, attr_type in MyPacket.enumerate_field_types(): ... print(f"{attr}: {attr_type.__qualname__}") ... attr1: Int8 attr2: Int16
- enumerate_field_types_and_values()¶
Enumerates the
Types and values of the fields of thePacket.- Returns:
Each element of the iterable is a (
field_name,field_type,field_value) triplet.- Return type:
iterable
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... attr1: pak.Int8 ... attr2: pak.Int16 ... >>> p = MyPacket(attr1=1, attr2=2) >>> for attr, attr_type, value in p.enumerate_field_types_and_values(): ... print(f"{attr}: {attr_type.__qualname__}; {value}") ... attr1: Int8; 1 attr2: Int16; 2
- enumerate_field_values()¶
Enumerates the values of the fields of the
Packet.- Returns:
Each element of the iterable is a (
field_name,field_value) pair.- Return type:
iterable
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... attr1: pak.Int8 ... attr2: pak.Int16 ... >>> p = MyPacket(attr1=1, attr2=2) >>> for attr, value in p.enumerate_field_values(): ... print(f"{attr}: {value}") ... attr1: 1 attr2: 2
- classmethod field_names()¶
Gets the names of the fields of the
Packet.- Returns:
Each element is the name of a field.
- Return type:
iterable
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... attr1: pak.Int8 ... attr2: pak.Int16 ... >>> for name in MyPacket.field_names(): ... print(name) ... attr1 attr2
- classmethod field_types()¶
Gets the
Types of each field of thePacket.- Returns:
Each element is the
Typeof a field.- Return type:
iterable
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... attr1: pak.Int8 ... attr2: pak.Int16 ... >>> for type in MyPacket.field_types(): ... print(type.__qualname__) ... Int8 Int16
- field_types_and_values()¶
Gets the
Types and values of each field of thePacket.- Returns:
Each element is a (
field_type,field_value) pair.- Return type:
iterable
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... attr1: pak.Int8 ... attr2: pak.Int16 ... >>> p = MyPacket(attr1=1, attr2=2) >>> for type, value in p.field_types_and_values(): ... print(f"{type.__qualname__}; {value}") ... Int8; 1 Int16; 2
- field_values()¶
Gets the values of each field of the
Packet.- Returns:
Each element is the value of a field.
- Return type:
iterable
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... attr1: pak.Int8 ... attr2: pak.Int16 ... >>> p = MyPacket(attr1=1, attr2=2) >>> for value in p.field_values(): ... print(value) ... 1 2
- classmethod has_field(name)¶
Gets whether the
Packethas a certain field.- Parameters:
name (
str) – The name of the field to check for.- Returns:
Whether the
Packethas a field with the specified name.- Return type:
bool
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... defined_field: pak.UInt8 ... >>> MyPacket.has_field("defined_field") True >>> MyPacket.has_field("undefined_field") False
- header(*, ctx=None)¶
Gets the
Packet.Headerfor thePacket.- Parameters:
ctx (
Packet.Context) – The context for thePacket.- Returns:
The header for the
Packet.- Return type:
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... id = 1 ... class Header(pak.Packet.Header): ... id: pak.UInt8 ... >>> MyPacket().header() MyPacket.Header(id=1)
- classmethod id(*, ctx=None)¶
Gets the ID of the
Packet.Lots of packet protocols specify
Packets with an ID to determine what type ofPacketshould be read. Unless overridden,Packethas no meaningful ID.When overriding this method with a classmethod, you do not need to default the
ctxparameter, you can just have it like so:class MyPacket(pak.Packet): @classmethod def id(cls, *, ctx): ...
The case where no
ctxis specified when calling this method will be handled for you.If the
idattribute of a subclass is enrolled in theDynamicValuemachinery, then its dynamic value is returned from this function. Otherwise the value of theidattribute is returned.Warning
The ID of a
Packetmust be both hashable and equality comparable for various facilities involving IDs to work correctly.Many protocols have a
Packet.Headerprefixing eachPacketwith just an ID. To model this common protocol, one can do something like this:>>> import pak >>> class MyPacket(pak.Packet): ... id = 3 ... array: pak.Int16[2] ... class Header(pak.Packet.Header): ... id: pak.Int8 ... >>> MyPacket(array=[1, 2]).pack() b'\x03\x01\x00\x02\x00'
- Parameters:
ctx (
Packet.Context) – The context for thePacket.- Returns:
By default returns
None, and if the ID isNonethen thePacketshouldn’t be considered when looking upPackets from their ID.Otherwise the ID of the
Packet.- Return type:
any
- immutable_copy(**new_attrs)¶
Makes an immutable copy of the
Packet.See also
- Parameters:
**new_attrs – The new attributes to set on the copy.
- Returns:
The copied, immutable
Packet.- Return type:
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... field: pak.Int8 ... >>> orig = MyPacket(field=1) >>> copy = orig.immutable_copy() >>> copy == orig True >>> copy is not orig True >>> >>> # Attributes that aren't fields will also be copied: >>> orig = MyPacket(field=2) >>> orig.custom_attr = "custom" >>> copy = orig.immutable_copy() >>> copy == orig True >>> copy is not orig True >>> copy.custom_attr 'custom' >>> >>> # You cannot modify the copy: >>> copy.field = 3 Traceback (most recent call last): ... AttributeError: This 'MyPacket' instance has been made immutable >>> >>> # You can however set fields that the immutable copy will have: >>> copy = orig.immutable_copy(field=3) >>> copy MyPacket(field=3) >>> # This copy is still immutable: >>> copy.field = 4 Traceback (most recent call last): ... AttributeError: This 'MyPacket' instance has been made immutable
- make_immutable()¶
Makes the
Packetimmutable.This may be useful for situations where e.g. you pass the same
Packetinstance to several functions and want to make sure that those functions don’t change the fields of thePacketfor the other functions being passed the instance. For example if you had:import pak class MyPacket(pak.Packet): field: pak.Int8 def foo(packet): packet.field = 2 def bar(packet): assert packet.field == 1 p = MyPacket(field=1) foo(p) bar(p)
Then the assert within
barwould fail, sincefoochangedp.fieldto2. Ifpis made immutable however, an error will be raised infoo, preventing any hard-to-track-down errors.Examples
>>> import pak >>> class MyPacket(pak.Packet): ... field: pak.Int8 ... >>> p = MyPacket(field=1) >>> p.field = 2 >>> p MyPacket(field=2) >>> p.make_immutable() >>> p.field = 3 Traceback (most recent call last): ... AttributeError: This 'MyPacket' instance has been made immutable
- pack(*, ctx=None)¶
Packs a
Packetto raw data.Note
This does pack the
Packet.Header, unlikeunpack().First the
Packet.Headeris gotten fromheader(), then it is packed, and thenpack_without_header()is called.- Parameters:
ctx (
Packet.Context) – The context for thePacket.- Returns:
The raw data marshaled from the
Packet.- Return type:
bytes
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... id = 0xFF ... array: pak.UInt8[pak.UInt8] ... class Header(pak.Packet.Header): ... id: pak.UInt8 ... >>> p = MyPacket(array=[0, 1, 2, 3]) >>> p.pack() b'\xff\x04\x00\x01\x02\x03'
- pack_without_header(*, ctx=None)¶
Packs a
Packetto raw data, excluding thePacket.Header.- Parameters:
ctx (
Packet.Context) – The context for thePacket.- Returns:
The raw data marshaled from the
Packet, excluding the header.- Return type:
bytes
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... id = 0xFF ... array: pak.UInt8[pak.UInt8] ... class Header(pak.Packet.Header): ... id: pak.UInt8 ... >>> p = MyPacket(array=[0, 1, 2, 3]) >>> p.pack_without_header() b'\x04\x00\x01\x02\x03'
- size(*, ctx=None)¶
Gets the cumulative size of the fields of the
Packet.This may be called either as a
classmethodor as an instance method. When called as aclassmethod, the static size of thePacketis attempted to be calculated, irrespective of any values. When called as an instance method, then the values of the fields of thePacketare used to calculate the size.Note
The header is not included in the size.
- Parameters:
ctx (
Packet.Context) – The context for thePacket.- Returns:
The cumulative size of the fields of the
Packet.- Return type:
int
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... array: pak.Int16[2] ... float64: pak.Float64 ... >>> MyPacket().size() 12 >>> MyPacket.size() 12
- classmethod subclass_with_id(id, /, *, ctx=None)¶
Gets the subclass with the equivalent ID.
- Parameters:
id –
The ID of the
Packet.See also
ctx (
Packet.ContextorNone) – The context for thePacket.
- Returns:
If
None, then there is noPacketwhose ID isid. Otherwise returns the appropriate subclass.- Return type:
subclass of
PacketorNone
- classmethod subclasses()¶
Gets the recursive subclasses of the
Packet.Useful for when you have categories of
Packets, such as serverbound and clientbound, and so you can have an empty class likeclass ServerboundPacket(Packet): pass
which all serverbound
Packets would inherit from, and then usesubclasses()to automatically get all the serverboundPackets.Note
This method is decorated with
util.cache(). In particular this means you can’t generate a new subclass after calling this and have it be returned fromsubclasses()the next time you call it.- Returns:
The recursive subclasses of the
Packet.- Return type:
frozenset
- type_ctx(ctx)¶
Converts a
Packet.Contextto aType.Context.- Parameters:
ctx (
Packet.ContextorNone) – The context for thePacket.- Returns:
The context for a
Type.- Return type:
- classmethod unpack(buf, *, ctx=None)¶
Unpacks a
Packetfrom raw data.Note
This doesn’t unpack the header, as you often need to unpack the
Packet.Headerto determine the correctPacketto unpack in the first place, let alone other aspects of the data.This method will call the
Type.unpack()method on the fields of thePacket.See also
- Parameters:
buf (file object or
bytesorbytearray) – The buffer containing the raw data.ctx (
Packet.Context) – The context for thePacket.
- Returns:
The
Packetmarshaled from the raw data.- Return type:
Examples
>>> import pak >>> class MyPacket(pak.Packet): ... hello: pak.RawByte[5] ... world: pak.RawByte[5] ... >>> MyPacket.unpack(b"HelloWorld") MyPacket(hello=bytearray(b'Hello'), world=bytearray(b'World'))
- async classmethod unpack_async(reader, *, ctx=None)¶
Asynchronously unpacks a
Packetfrom raw data.Note
This doesn’t unpack the header, as you often need to unpack the
Packet.Headerto determine the correctPacketto unpack in the first place, let alone other aspects of the data.This method will call the
Type.unpack_async()method on the fields of thePacket.See also
- Parameters:
reader (
asyncio.StreamReaderorbytesorbytearray) –The stream of data to unpack from.
Warning
Nothing else should read from
readeruntil this coroutine finishes executing.If
bytesorbytearray, thenreaderis turned into anio.ByteStreamReader.ctx (
Packet.Context) – The context for thePacket.
- Returns:
The
Packetmarshaled from the raw data.- Return type: