py2max 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py2max/__init__.py +67 -0
- py2max/__main__.py +6 -0
- py2max/cli.py +1251 -0
- py2max/core/__init__.py +39 -0
- py2max/core/abstract.py +146 -0
- py2max/core/box.py +231 -0
- py2max/core/common.py +19 -0
- py2max/core/patcher.py +1658 -0
- py2max/core/patchline.py +68 -0
- py2max/exceptions.py +385 -0
- py2max/export/__init__.py +20 -0
- py2max/export/converters.py +345 -0
- py2max/export/svg.py +393 -0
- py2max/layout/__init__.py +26 -0
- py2max/layout/base.py +463 -0
- py2max/layout/flow.py +405 -0
- py2max/layout/grid.py +374 -0
- py2max/layout/matrix.py +628 -0
- py2max/log.py +338 -0
- py2max/maxref/__init__.py +78 -0
- py2max/maxref/category.py +163 -0
- py2max/maxref/db.py +1082 -0
- py2max/maxref/legacy.py +324 -0
- py2max/maxref/parser.py +703 -0
- py2max/py.typed +0 -0
- py2max/server/__init__.py +54 -0
- py2max/server/client.py +295 -0
- py2max/server/inline.py +312 -0
- py2max/server/repl.py +561 -0
- py2max/server/rpc.py +240 -0
- py2max/server/websocket.py +997 -0
- py2max/static/cola.min.js +4 -0
- py2max/static/d3.v7.min.js +2 -0
- py2max/static/dagre-bundle.js +328 -0
- py2max/static/elk.bundled.js +6663 -0
- py2max/static/index.html +168 -0
- py2max/static/interactive.html +589 -0
- py2max/static/interactive.js +2111 -0
- py2max/static/live-preview.js +324 -0
- py2max/static/svg.min.js +13 -0
- py2max/static/svg.min.js.map +1 -0
- py2max/transformers.py +168 -0
- py2max/utils.py +83 -0
- py2max-0.2.1.dist-info/METADATA +390 -0
- py2max-0.2.1.dist-info/RECORD +48 -0
- py2max-0.2.1.dist-info/WHEEL +4 -0
- py2max-0.2.1.dist-info/entry_points.txt +3 -0
- py2max-0.2.1.dist-info/licenses/LICENSE +19 -0
py2max/core/patchline.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Patchline class for representing connections between Max objects."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Tuple, Union
|
|
4
|
+
|
|
5
|
+
from .abstract import AbstractPatchline
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Patchline(AbstractPatchline):
|
|
9
|
+
"""Represents a connection between two Max objects.
|
|
10
|
+
|
|
11
|
+
A Patchline connects an outlet of one object to an inlet of another,
|
|
12
|
+
enabling signal or message flow between objects in a patch.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
source: Source connection as [object_id, outlet_index].
|
|
16
|
+
destination: Destination connection as [object_id, inlet_index].
|
|
17
|
+
**kwds: Additional patchline properties.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
source: Source object ID and outlet index.
|
|
21
|
+
destination: Destination object ID and inlet index.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self, source: Optional[list] = None, destination: Optional[list] = None, **kwds
|
|
26
|
+
):
|
|
27
|
+
self.source = source or []
|
|
28
|
+
self.destination = destination or []
|
|
29
|
+
self._kwds = kwds
|
|
30
|
+
|
|
31
|
+
def __repr__(self):
|
|
32
|
+
return f"Patchline({self.source} -> {self.destination})"
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def src(self):
|
|
36
|
+
"""first object from source list"""
|
|
37
|
+
return self.source[0]
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def dst(self):
|
|
41
|
+
"""first object from destination list"""
|
|
42
|
+
return self.destination[0]
|
|
43
|
+
|
|
44
|
+
def to_tuple(self) -> Tuple[str, str, str, str, Union[str, int]]:
|
|
45
|
+
"""Return a tuple describing the patchline."""
|
|
46
|
+
return (
|
|
47
|
+
self.source[0],
|
|
48
|
+
self.source[1],
|
|
49
|
+
self.destination[0],
|
|
50
|
+
self.destination[1],
|
|
51
|
+
self._kwds.get("order", 0),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def to_dict(self):
|
|
55
|
+
"""create dict from object with extra kwds included"""
|
|
56
|
+
d = vars(self).copy()
|
|
57
|
+
to_del = [k for k in d if k.startswith("_")]
|
|
58
|
+
for k in to_del:
|
|
59
|
+
del d[k]
|
|
60
|
+
d.update(self._kwds)
|
|
61
|
+
return dict(patchline=d)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def from_dict(cls, obj_dict: dict):
|
|
65
|
+
"""convert to`Patchline` object from dict"""
|
|
66
|
+
patchline = cls()
|
|
67
|
+
patchline.__dict__.update(obj_dict)
|
|
68
|
+
return patchline
|
py2max/exceptions.py
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""Exception hierarchy for py2max.
|
|
2
|
+
|
|
3
|
+
This module defines a comprehensive exception hierarchy for error handling
|
|
4
|
+
throughout the py2max library. All exceptions inherit from Py2MaxError for
|
|
5
|
+
easy catching of library-specific errors.
|
|
6
|
+
|
|
7
|
+
Exception Hierarchy:
|
|
8
|
+
Py2MaxError (base)
|
|
9
|
+
├── ValidationError
|
|
10
|
+
│ ├── InvalidConnectionError (connections between objects)
|
|
11
|
+
│ ├── InvalidObjectError (invalid object configuration)
|
|
12
|
+
│ └── InvalidPatchError (invalid patcher state)
|
|
13
|
+
├── ConfigurationError
|
|
14
|
+
│ ├── LayoutError (layout manager issues)
|
|
15
|
+
│ └── DatabaseError (database configuration issues)
|
|
16
|
+
├── IOError (subclass of built-in IOError)
|
|
17
|
+
│ ├── PatcherIOError (file I/O errors)
|
|
18
|
+
│ └── MaxRefError (maxref.xml parsing errors)
|
|
19
|
+
└── InternalError (unexpected internal errors)
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
>>> from py2max.exceptions import InvalidConnectionError
|
|
23
|
+
>>> try:
|
|
24
|
+
... p.add_line(osc, gain, outlet=999)
|
|
25
|
+
... except InvalidConnectionError as e:
|
|
26
|
+
... print(f"Connection error: {e}")
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from typing import Any, Optional
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Py2MaxError(Exception):
|
|
33
|
+
"""Base exception for all py2max errors.
|
|
34
|
+
|
|
35
|
+
All py2max-specific exceptions inherit from this class, allowing users
|
|
36
|
+
to catch all library errors with a single exception handler.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
message: Error message describing what went wrong.
|
|
40
|
+
context: Optional dict with additional error context.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, message: str, context: Optional[dict[str, Any]] = None):
|
|
44
|
+
"""Initialize exception with message and optional context.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
message: Error message describing the problem.
|
|
48
|
+
context: Optional dict with additional error context (object IDs, etc.).
|
|
49
|
+
"""
|
|
50
|
+
super().__init__(message)
|
|
51
|
+
self.message = message
|
|
52
|
+
self.context = context or {}
|
|
53
|
+
|
|
54
|
+
def __str__(self) -> str:
|
|
55
|
+
"""Return formatted error message with context.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Formatted error message including context if available.
|
|
59
|
+
"""
|
|
60
|
+
if self.context:
|
|
61
|
+
context_str = ", ".join(f"{k}={v}" for k, v in self.context.items())
|
|
62
|
+
return f"{self.message} ({context_str})"
|
|
63
|
+
return self.message
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ----------------------------------------------------------------------------
|
|
67
|
+
# Validation Errors
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ValidationError(Py2MaxError):
|
|
71
|
+
"""Base class for validation errors.
|
|
72
|
+
|
|
73
|
+
Raised when user input or object state fails validation checks.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class InvalidConnectionError(ValidationError):
|
|
78
|
+
"""Raised when attempting to create an invalid patchline connection.
|
|
79
|
+
|
|
80
|
+
This exception is raised when validation is enabled and an invalid
|
|
81
|
+
connection is attempted, such as:
|
|
82
|
+
- Connecting to a non-existent inlet or outlet
|
|
83
|
+
- Connecting incompatible signal types
|
|
84
|
+
- Creating duplicate connections
|
|
85
|
+
|
|
86
|
+
Attributes:
|
|
87
|
+
src: Source object identifier.
|
|
88
|
+
dst: Destination object identifier.
|
|
89
|
+
outlet: Source outlet index (if applicable).
|
|
90
|
+
inlet: Destination inlet index (if applicable).
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
message: str,
|
|
96
|
+
src: Optional[str] = None,
|
|
97
|
+
dst: Optional[str] = None,
|
|
98
|
+
outlet: Optional[int] = None,
|
|
99
|
+
inlet: Optional[int] = None,
|
|
100
|
+
):
|
|
101
|
+
"""Initialize connection error with connection details.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
message: Error message.
|
|
105
|
+
src: Source object ID.
|
|
106
|
+
dst: Destination object ID.
|
|
107
|
+
outlet: Source outlet index.
|
|
108
|
+
inlet: Destination inlet index.
|
|
109
|
+
"""
|
|
110
|
+
context: dict[str, Any] = {}
|
|
111
|
+
if src:
|
|
112
|
+
context["src"] = src
|
|
113
|
+
if dst:
|
|
114
|
+
context["dst"] = dst
|
|
115
|
+
if outlet is not None:
|
|
116
|
+
context["outlet"] = outlet
|
|
117
|
+
if inlet is not None:
|
|
118
|
+
context["inlet"] = inlet
|
|
119
|
+
super().__init__(message, context)
|
|
120
|
+
self.src = src
|
|
121
|
+
self.dst = dst
|
|
122
|
+
self.outlet = outlet
|
|
123
|
+
self.inlet = inlet
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class InvalidObjectError(ValidationError):
|
|
127
|
+
"""Raised when object configuration is invalid.
|
|
128
|
+
|
|
129
|
+
Examples:
|
|
130
|
+
- Invalid object type name
|
|
131
|
+
- Missing required parameters
|
|
132
|
+
- Invalid parameter values
|
|
133
|
+
- Unknown maxclass
|
|
134
|
+
|
|
135
|
+
Attributes:
|
|
136
|
+
object_id: Object identifier.
|
|
137
|
+
maxclass: Max object class name.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(
|
|
141
|
+
self,
|
|
142
|
+
message: str,
|
|
143
|
+
object_id: Optional[str] = None,
|
|
144
|
+
maxclass: Optional[str] = None,
|
|
145
|
+
):
|
|
146
|
+
"""Initialize object error with object details.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
message: Error message.
|
|
150
|
+
object_id: Object ID.
|
|
151
|
+
maxclass: Max object class name.
|
|
152
|
+
"""
|
|
153
|
+
context = {}
|
|
154
|
+
if object_id:
|
|
155
|
+
context["object_id"] = object_id
|
|
156
|
+
if maxclass:
|
|
157
|
+
context["maxclass"] = maxclass
|
|
158
|
+
super().__init__(message, context)
|
|
159
|
+
self.object_id = object_id
|
|
160
|
+
self.maxclass = maxclass
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class InvalidPatchError(ValidationError):
|
|
164
|
+
"""Raised when patcher state is invalid.
|
|
165
|
+
|
|
166
|
+
Examples:
|
|
167
|
+
- Circular subpatcher references
|
|
168
|
+
- Missing required objects
|
|
169
|
+
- Orphaned connections
|
|
170
|
+
- Invalid patch structure
|
|
171
|
+
|
|
172
|
+
Attributes:
|
|
173
|
+
patch_path: Path to the patcher file.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
def __init__(self, message: str, patch_path: Optional[str] = None):
|
|
177
|
+
"""Initialize patch error with patch details.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
message: Error message.
|
|
181
|
+
patch_path: Path to patcher file.
|
|
182
|
+
"""
|
|
183
|
+
context = {}
|
|
184
|
+
if patch_path:
|
|
185
|
+
context["patch_path"] = patch_path
|
|
186
|
+
super().__init__(message, context)
|
|
187
|
+
self.patch_path = patch_path
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ----------------------------------------------------------------------------
|
|
191
|
+
# Configuration Errors
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class ConfigurationError(Py2MaxError):
|
|
195
|
+
"""Base class for configuration errors.
|
|
196
|
+
|
|
197
|
+
Raised when library configuration is invalid or incompatible.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class LayoutError(ConfigurationError):
|
|
202
|
+
"""Raised when layout manager encounters an error.
|
|
203
|
+
|
|
204
|
+
Examples:
|
|
205
|
+
- Unknown layout type
|
|
206
|
+
- Invalid layout parameters
|
|
207
|
+
- Layout algorithm failure
|
|
208
|
+
|
|
209
|
+
Attributes:
|
|
210
|
+
layout_type: Layout manager type.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
def __init__(self, message: str, layout_type: Optional[str] = None):
|
|
214
|
+
"""Initialize layout error with layout details.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
message: Error message.
|
|
218
|
+
layout_type: Layout manager type (e.g., 'grid', 'flow').
|
|
219
|
+
"""
|
|
220
|
+
context = {}
|
|
221
|
+
if layout_type:
|
|
222
|
+
context["layout_type"] = layout_type
|
|
223
|
+
super().__init__(message, context)
|
|
224
|
+
self.layout_type = layout_type
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class DatabaseError(ConfigurationError):
|
|
228
|
+
"""Raised when database operations fail.
|
|
229
|
+
|
|
230
|
+
Examples:
|
|
231
|
+
- Database connection failure
|
|
232
|
+
- Schema mismatch
|
|
233
|
+
- Data corruption
|
|
234
|
+
- Query errors
|
|
235
|
+
|
|
236
|
+
Attributes:
|
|
237
|
+
db_path: Path to database file.
|
|
238
|
+
operation: Operation that failed.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def __init__(
|
|
242
|
+
self,
|
|
243
|
+
message: str,
|
|
244
|
+
db_path: Optional[str] = None,
|
|
245
|
+
operation: Optional[str] = None,
|
|
246
|
+
):
|
|
247
|
+
"""Initialize database error with database details.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
message: Error message.
|
|
251
|
+
db_path: Path to database file.
|
|
252
|
+
operation: Operation that failed (e.g., 'query', 'insert').
|
|
253
|
+
"""
|
|
254
|
+
context = {}
|
|
255
|
+
if db_path:
|
|
256
|
+
context["db_path"] = str(db_path)
|
|
257
|
+
if operation:
|
|
258
|
+
context["operation"] = operation
|
|
259
|
+
super().__init__(message, context)
|
|
260
|
+
self.db_path = db_path
|
|
261
|
+
self.operation = operation
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# ----------------------------------------------------------------------------
|
|
265
|
+
# I/O Errors
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class PatcherIOError(Py2MaxError, IOError):
|
|
269
|
+
"""Raised when patcher file I/O operations fail.
|
|
270
|
+
|
|
271
|
+
Examples:
|
|
272
|
+
- File not found
|
|
273
|
+
- Permission denied
|
|
274
|
+
- Invalid JSON format
|
|
275
|
+
- Disk full
|
|
276
|
+
|
|
277
|
+
Attributes:
|
|
278
|
+
file_path: Path to the file.
|
|
279
|
+
operation: I/O operation that failed.
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
def __init__(
|
|
283
|
+
self,
|
|
284
|
+
message: str,
|
|
285
|
+
file_path: Optional[str] = None,
|
|
286
|
+
operation: Optional[str] = None,
|
|
287
|
+
):
|
|
288
|
+
"""Initialize I/O error with file details.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
message: Error message.
|
|
292
|
+
file_path: Path to file.
|
|
293
|
+
operation: Operation that failed (e.g., 'read', 'write').
|
|
294
|
+
"""
|
|
295
|
+
context = {}
|
|
296
|
+
if file_path:
|
|
297
|
+
context["file_path"] = str(file_path)
|
|
298
|
+
if operation:
|
|
299
|
+
context["operation"] = operation
|
|
300
|
+
Py2MaxError.__init__(self, message, context)
|
|
301
|
+
self.file_path = file_path
|
|
302
|
+
self.operation = operation
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class MaxRefError(Py2MaxError, IOError):
|
|
306
|
+
"""Raised when MaxRef XML parsing or lookup fails.
|
|
307
|
+
|
|
308
|
+
Examples:
|
|
309
|
+
- MaxRef XML file not found
|
|
310
|
+
- Invalid XML format
|
|
311
|
+
- Object not found in MaxRef database
|
|
312
|
+
- Max installation not found
|
|
313
|
+
|
|
314
|
+
Attributes:
|
|
315
|
+
object_name: Max object name being looked up.
|
|
316
|
+
xml_path: Path to .maxref.xml file.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
def __init__(
|
|
320
|
+
self,
|
|
321
|
+
message: str,
|
|
322
|
+
object_name: Optional[str] = None,
|
|
323
|
+
xml_path: Optional[str] = None,
|
|
324
|
+
):
|
|
325
|
+
"""Initialize MaxRef error with lookup details.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
message: Error message.
|
|
329
|
+
object_name: Name of Max object being looked up.
|
|
330
|
+
xml_path: Path to .maxref.xml file.
|
|
331
|
+
"""
|
|
332
|
+
context = {}
|
|
333
|
+
if object_name:
|
|
334
|
+
context["object_name"] = object_name
|
|
335
|
+
if xml_path:
|
|
336
|
+
context["xml_path"] = str(xml_path)
|
|
337
|
+
Py2MaxError.__init__(self, message, context)
|
|
338
|
+
self.object_name = object_name
|
|
339
|
+
self.xml_path = xml_path
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# ----------------------------------------------------------------------------
|
|
343
|
+
# Internal Errors
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class InternalError(Py2MaxError):
|
|
347
|
+
"""Raised for unexpected internal errors.
|
|
348
|
+
|
|
349
|
+
This exception indicates a bug in the library code and should be reported.
|
|
350
|
+
Users should not typically need to catch this exception.
|
|
351
|
+
|
|
352
|
+
Attributes:
|
|
353
|
+
location: Location in code where error occurred.
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
def __init__(self, message: str, location: Optional[str] = None):
|
|
357
|
+
"""Initialize internal error with location.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
message: Error message.
|
|
361
|
+
location: Code location (e.g., 'Patcher.add_line').
|
|
362
|
+
"""
|
|
363
|
+
context = {}
|
|
364
|
+
if location:
|
|
365
|
+
context["location"] = location
|
|
366
|
+
super().__init__(f"Internal error: {message}", context)
|
|
367
|
+
self.location = location
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# ----------------------------------------------------------------------------
|
|
371
|
+
# Exports
|
|
372
|
+
|
|
373
|
+
__all__ = [
|
|
374
|
+
"Py2MaxError",
|
|
375
|
+
"ValidationError",
|
|
376
|
+
"InvalidConnectionError",
|
|
377
|
+
"InvalidObjectError",
|
|
378
|
+
"InvalidPatchError",
|
|
379
|
+
"ConfigurationError",
|
|
380
|
+
"LayoutError",
|
|
381
|
+
"DatabaseError",
|
|
382
|
+
"PatcherIOError",
|
|
383
|
+
"MaxRefError",
|
|
384
|
+
"InternalError",
|
|
385
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Export functionality for py2max patches.
|
|
2
|
+
|
|
3
|
+
This subpackage provides format conversion and export utilities:
|
|
4
|
+
|
|
5
|
+
- SVG export for visual patch representation
|
|
6
|
+
- Python script generation for patch recreation
|
|
7
|
+
- SQLite database population from maxref files
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .svg import export_svg, export_svg_string
|
|
11
|
+
from .converters import maxpat_to_python, maxref_to_sqlite
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
# SVG export
|
|
15
|
+
"export_svg",
|
|
16
|
+
"export_svg_string",
|
|
17
|
+
# Converters
|
|
18
|
+
"maxpat_to_python",
|
|
19
|
+
"maxref_to_sqlite",
|
|
20
|
+
]
|