Erlang Binary Term Format for Python with encoding/decoding of Erlang/Elixir data structures
npx @tessl/cli install tessl/pypi-erlang_py@2.0.00
# Erlang_py
1
2
A Python library that provides complete encoding and decoding functionality for the Erlang Binary Term Format (ETF). This library enables Python applications to communicate with Erlang/Elixir systems via binary protocols, message passing, and data serialization, with enhanced Elixir compatibility features including symmetrical encoding/decoding and proper None/nil conversion.
3
4
## Package Information
5
6
- **Package Name**: erlang_py
7
- **Language**: Python
8
- **Installation**: `pip install erlang_py`
9
10
## Core Imports
11
12
```python
13
import erlang
14
```
15
16
## Basic Usage
17
18
```python
19
import erlang
20
21
# Encode Python data to Erlang binary format
22
data = {'key': 'value', 'number': 42, 'list': [1, 2, 3]}
23
binary_data = erlang.term_to_binary(data)
24
25
# Decode Erlang binary back to Python
26
decoded_data = erlang.binary_to_term(binary_data)
27
print(decoded_data) # {'key': 'value', 'number': 42, 'list': [1, 2, 3]}
28
29
# Working with Erlang-specific types
30
atom = erlang.OtpErlangAtom('hello')
31
binary_atom = erlang.term_to_binary(atom)
32
decoded_atom = erlang.binary_to_term(binary_atom)
33
34
# Compressed encoding for large data
35
large_data = [i for i in range(10000)]
36
compressed_binary = erlang.term_to_binary(large_data, compressed=True)
37
decompressed_data = erlang.binary_to_term(compressed_binary)
38
```
39
40
## Architecture
41
42
The Erlang Binary Term Format (ETF) is a standardized binary protocol used by Erlang and Elixir for serializing data structures. This library provides a complete Python implementation that enables seamless interoperability between Python applications and Erlang/Elixir systems.
43
44
**Key Design Concepts:**
45
- **Symmetric encoding/decoding**: Data can be round-trip converted between Python and Erlang without loss
46
- **Type preservation**: Erlang-specific types (atoms, PIDs, references) are preserved using wrapper classes
47
- **Compression support**: Large data structures can be compressed using zlib to reduce network overhead
48
- **Binary compatibility**: Full compliance with the official Erlang External Term Format specification
49
50
This architecture makes the library suitable for distributed systems, message queues, and any scenario requiring Python-Erlang data exchange.
51
52
## Capabilities
53
54
### Binary Term Format Conversion
55
56
Core functionality for encoding Python data to Erlang Binary Term Format and decoding Erlang terms back to Python objects.
57
58
```python { .api }
59
def binary_to_term(data):
60
"""
61
Decode Erlang terms within binary data into Python types.
62
63
Parameters:
64
- data: bytes - Binary data containing Erlang terms
65
66
Returns:
67
Decoded Python object (any type)
68
69
Raises:
70
- ParseException: If data is invalid or cannot be parsed
71
"""
72
73
def term_to_binary(term, compressed=False):
74
"""
75
Encode Python types into Erlang terms in binary data.
76
77
Parameters:
78
- term: Any - Python object to encode
79
- compressed: bool or int - Compression level (False=no compression, True=level 6, 0-9=specific level)
80
81
Returns:
82
Binary data (bytes) containing encoded Erlang terms
83
84
Raises:
85
- InputException: If compression level is invalid (must be in [0..9])
86
- OutputException: If encoding fails (e.g., uint32 overflow)
87
"""
88
```
89
90
### Erlang Data Type Wrappers
91
92
Specialized classes representing Erlang-specific data types that don't have direct Python equivalents.
93
94
```python { .api }
95
class OtpErlangAtom:
96
"""Represents an Erlang atom."""
97
98
def __init__(self, value):
99
"""
100
Create an Erlang atom.
101
102
Parameters:
103
- value: int | str | bytes - Integer for atom cache reference,
104
string or bytes for atom name
105
"""
106
107
def binary(self):
108
"""Return encoded representation of the atom."""
109
110
class OtpErlangBinary:
111
"""Represents an Erlang binary with optional bit-level precision."""
112
113
def __init__(self, value, bits=8):
114
"""
115
Create an Erlang binary.
116
117
Parameters:
118
- value: bytes - Binary value
119
- bits: int - Number of bits in last byte (default: 8)
120
"""
121
122
def binary(self):
123
"""Return encoded representation of the binary."""
124
125
class OtpErlangFunction:
126
"""Represents an Erlang function."""
127
128
def __init__(self, tag, value):
129
"""
130
Create an Erlang function.
131
132
Parameters:
133
- tag: Function tag identifier
134
- value: Function value/data
135
"""
136
137
def binary(self):
138
"""Return encoded representation of the function."""
139
140
class OtpErlangList:
141
"""Represents an Erlang list with optional improper list support."""
142
143
def __init__(self, value, improper=False):
144
"""
145
Create an Erlang list.
146
147
Parameters:
148
- value: list - List contents
149
- improper: bool - Whether list has no empty list tail (default: False)
150
"""
151
152
def binary(self):
153
"""Return encoded representation of the list."""
154
155
class OtpErlangPid:
156
"""Represents an Erlang process identifier (PID)."""
157
158
def __init__(self, node, id_value, serial, creation):
159
"""
160
Create an Erlang PID.
161
162
Parameters:
163
- node: OtpErlangAtom - Node atom where the process resides
164
- id_value: bytes - Process ID value
165
- serial: bytes - Serial number
166
- creation: bytes - Creation identifier
167
"""
168
169
def binary(self):
170
"""Return encoded representation of the PID."""
171
172
class OtpErlangPort:
173
"""Represents an Erlang port."""
174
175
def __init__(self, node, id_value, creation):
176
"""
177
Create an Erlang port.
178
179
Parameters:
180
- node: OtpErlangAtom - Node atom where the port resides
181
- id_value: bytes - Port ID value
182
- creation: bytes - Creation identifier
183
"""
184
185
def binary(self):
186
"""Return encoded representation of the port."""
187
188
class OtpErlangReference:
189
"""Represents an Erlang reference."""
190
191
def __init__(self, node, id_value, creation):
192
"""
193
Create an Erlang reference.
194
195
Parameters:
196
- node: OtpErlangAtom - Node atom where the reference was created
197
- id_value: bytes - Reference ID value
198
- creation: bytes - Creation identifier
199
"""
200
201
def binary(self):
202
"""Return encoded representation of the reference."""
203
```
204
205
### Exception Handling
206
207
Exception classes for different error conditions during encoding/decoding operations.
208
209
```python { .api }
210
class InputException(ValueError):
211
"""
212
InputError describes problems with function input parameters.
213
214
Extends ValueError.
215
"""
216
217
def __init__(self, s):
218
"""
219
Parameters:
220
- s: Error message string
221
"""
222
223
class OutputException(TypeError):
224
"""
225
OutputError describes problems with creating function output data.
226
227
Extends TypeError.
228
"""
229
230
def __init__(self, s):
231
"""
232
Parameters:
233
- s: Error message string
234
"""
235
236
class ParseException(SyntaxError):
237
"""
238
ParseError provides specific parsing failure information.
239
240
Extends SyntaxError.
241
"""
242
243
def __init__(self, s):
244
"""
245
Parameters:
246
- s: Error message string
247
"""
248
```
249
250
### Additional Capabilities
251
252
Advanced functionality that extends beyond the core binary term format conversion.
253
254
```python { .api }
255
def consult(string_in):
256
"""
257
Provide file:consult/1 functionality with Python types.
258
259
Parse textual Erlang data representation into Python objects,
260
avoiding external dependencies for simple data parsing scenarios.
261
262
Parameters:
263
- string_in: str - String containing textual Erlang data
264
265
Returns:
266
Python object representation of the parsed Erlang data
267
268
Note: This function is not in __all__ and should be considered
269
internal/advanced API. Use with caution in production code.
270
271
Raises:
272
- ParseException: If the string cannot be parsed as valid Erlang data
273
"""
274
```
275
276
## Types
277
278
```python { .api }
279
# All OTP classes support these common methods:
280
def __repr__(self) -> str:
281
"""Return string representation of the object."""
282
283
def __hash__(self) -> int:
284
"""Return hash value for use in sets and dictionaries."""
285
286
def __eq__(self, other) -> bool:
287
"""Test equality with another object."""
288
289
# All exception classes support:
290
def __str__(self) -> str:
291
"""Return string representation of the error message."""
292
293
# Internal types available for advanced usage:
294
class frozendict(dict):
295
"""
296
Immutable dictionary that cannot be modified after creation.
297
Used internally for representing Erlang maps.
298
Available for import but not part of official public API.
299
"""
300
301
def __init__(self, *args, **kw):
302
"""
303
Create immutable dictionary, recursively converting nested dicts.
304
305
Parameters:
306
- *args: Positional arguments passed to dict constructor
307
- **kw: Keyword arguments passed to dict constructor
308
"""
309
310
def __hash__(self) -> int:
311
"""Return hash value based on dictionary contents."""
312
313
def __setitem__(self, key, value):
314
"""Raise TypeError - frozendict is immutable."""
315
316
def __delitem__(self, key):
317
"""Raise TypeError - frozendict is immutable."""
318
319
def clear(self):
320
"""Raise TypeError - frozendict is immutable."""
321
322
def pop(self, key, *args):
323
"""Raise TypeError - frozendict is immutable."""
324
325
def popitem(self):
326
"""Raise TypeError - frozendict is immutable."""
327
328
def setdefault(self, key, default=None):
329
"""Raise TypeError - frozendict is immutable."""
330
331
def update(self, *args, **kw):
332
"""Raise TypeError - frozendict is immutable."""
333
```
334
335
## Usage Examples
336
337
### Working with Atoms
338
339
```python
340
import erlang
341
342
# Create atoms from strings
343
atom1 = erlang.OtpErlangAtom("hello")
344
atom2 = erlang.OtpErlangAtom("world")
345
346
# Encode and decode
347
encoded = erlang.term_to_binary([atom1, atom2])
348
decoded = erlang.binary_to_term(encoded)
349
350
print(decoded) # [OtpErlangAtom('hello'), OtpErlangAtom('world')]
351
```
352
353
### Working with Complex Data Structures
354
355
```python
356
import erlang
357
358
# Mix of Python types and Erlang-specific types
359
complex_data = {
360
'atoms': [erlang.OtpErlangAtom('ok'), erlang.OtpErlangAtom('error')],
361
'binary': erlang.OtpErlangBinary(b'hello world'),
362
'regular_list': [1, 2, 3, 'string'],
363
'erlang_list': erlang.OtpErlangList([1, 2, 3]),
364
'nested': {'inner': {'value': 42}}
365
}
366
367
# Round-trip encoding/decoding
368
encoded = erlang.term_to_binary(complex_data)
369
decoded = erlang.binary_to_term(encoded)
370
371
# Verify round-trip integrity
372
assert decoded == complex_data
373
```
374
375
### Error Handling
376
377
```python
378
import erlang
379
380
try:
381
# Invalid binary data
382
erlang.binary_to_term(b'invalid')
383
except erlang.ParseException as e:
384
print(f"Parse error: {e}")
385
386
try:
387
# Invalid compression level
388
erlang.term_to_binary("test", compressed=15)
389
except erlang.InputException as e:
390
print(f"Input error: {e}")
391
```
392
393
### Compression
394
395
```python
396
import erlang
397
398
large_data = list(range(10000))
399
400
# No compression
401
uncompressed = erlang.term_to_binary(large_data)
402
403
# Default compression (level 6)
404
compressed = erlang.term_to_binary(large_data, compressed=True)
405
406
# Specific compression level
407
highly_compressed = erlang.term_to_binary(large_data, compressed=9)
408
409
print(f"Uncompressed: {len(uncompressed)} bytes")
410
print(f"Compressed: {len(compressed)} bytes")
411
print(f"Highly compressed: {len(highly_compressed)} bytes")
412
413
# All decode to the same data
414
assert erlang.binary_to_term(uncompressed) == large_data
415
assert erlang.binary_to_term(compressed) == large_data
416
assert erlang.binary_to_term(highly_compressed) == large_data
417
```
418
419
### Textual Data Parsing
420
421
```python
422
import erlang
423
424
# Parse textual Erlang data (advanced usage)
425
erlang_text = "{ok, [1, 2, 3]}."
426
try:
427
parsed_data = erlang.consult(erlang_text)
428
print(parsed_data) # {'ok': [1, 2, 3]}
429
except erlang.ParseException as e:
430
print(f"Parse error: {e}")
431
432
# Note: consult is not in __all__ and should be used carefully
433
# For most use cases, prefer binary_to_term/term_to_binary
434
```