karrio 2023.9.2__py3-none-any.whl → 2025.5rc3__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.
karrio/core/plugins.py ADDED
@@ -0,0 +1,606 @@
1
+ """
2
+ Karrio Plugins Module.
3
+
4
+ This module provides functionality for loading and managing Karrio plugins.
5
+ Plugins allow extending Karrio's functionality with custom carriers, mappers,
6
+ and address validators.
7
+
8
+ Usage:
9
+ There are several ways to use plugins with Karrio:
10
+
11
+ 1. Default plugins directory:
12
+ By default, Karrio looks for plugins in a 'plugins' directory in the current working directory.
13
+
14
+ 2. Custom plugin directory via environment variable:
15
+ Set the KARRIO_PLUGINS environment variable to the path of your plugins directory:
16
+
17
+ export KARRIO_PLUGINS=/path/to/your/plugins
18
+
19
+ 3. Programmatically add plugin directories:
20
+
21
+ import karrio.plugins as plugins
22
+ plugins.add_plugin_directory('/path/to/your/plugins')
23
+
24
+ Plugin Directory Structure:
25
+ A valid plugin directory can have either of the following structures:
26
+
27
+ 1. New Structure (Recommended):
28
+ /your_plugin_directory/
29
+ ├── plugin_name/
30
+ │ └── karrio/
31
+ │ ├── plugins/ # For plugins (carrier integrations, address validators, etc...)
32
+ │ │ └── plugin_name/
33
+ │ │ └── __init__.py # Contains METADATA
34
+ │ ├── mappers/ # For carrier integrations
35
+ │ │ └── plugin_name/
36
+ │ │ ├── __init__.py # Contains METADATA
37
+ │ │ ├── mapper.py # Implementation of the mapper
38
+ │ │ ├── proxy.py # Implementation of the proxy
39
+ │ │ └── settings.py # Settings schema
40
+ │ ├── validators/ # For address validators
41
+ │ │ └── plugin_name/
42
+ │ │ ├── __init__.py # Contains METADATA
43
+ │ │ └── validator.py # Implementation of the validator
44
+ │ ├── providers/ # Provider-specific implementations
45
+ │ │ └── plugin_name/
46
+ │ │ ├── __init__.py
47
+ │ │ ├── error.py # Error handling
48
+ │ │ ├── units.py # Units and enums
49
+ │ │ ├── utils.py # Utility functions
50
+ │ │ ├── rate.py # Rating functionality
51
+ │ │ ├── tracking.py # Tracking functionality
52
+ │ │ ├── manifest.py # Manifest functionality
53
+ │ │ ├── shipment/ # Shipment operations
54
+ │ │ │ ├── __init__.py
55
+ │ │ │ ├── create.py
56
+ │ │ │ └── cancel.py
57
+ │ │ └── pickup/ # Pickup operations
58
+ │ │ ├── __init__.py
59
+ │ │ ├── create.py
60
+ │ │ ├── update.py
61
+ │ │ └── cancel.py
62
+ │ └── schemas/ # API schema definitions
63
+ │ └── plugin_name/
64
+ │ ├── __init__.py
65
+ │ └── various schema files...
66
+
67
+ 2. Legacy Structure (Supported for backward compatibility):
68
+ /your_plugin_directory/
69
+ ├── plugin_name/
70
+ │ └── karrio/
71
+ │ ├── mappers/ # For carrier integrations
72
+ │ │ └── plugin_name/
73
+ │ │ ├── __init__.py # Contains METADATA
74
+ │ │ ├── mapper.py # Implementation of the mapper
75
+ │ │ ├── proxy.py # Implementation of the proxy
76
+ │ │ └── settings.py # Settings schema
77
+ │ ├── validators/ # For address validators
78
+ │ │ └── plugin_name/
79
+ │ │ ├── __init__.py # Contains METADATA
80
+ │ │ └── validator.py # Implementation of the validator
81
+ │ ├── providers/ # Provider-specific implementations
82
+ │ │ └── plugin_name/
83
+ │ │ ├── __init__.py
84
+ │ │ ├── error.py # Error handling
85
+ │ │ ├── units.py # Units and enums
86
+ │ │ ├── utils.py # Utility functions
87
+ │ │ ├── rate.py # Rating functionality
88
+ │ │ ├── tracking.py # Tracking functionality
89
+ │ │ ├── manifest.py # Manifest functionality
90
+ │ │ ├── shipment/ # Shipment operations
91
+ │ │ │ ├── __init__.py
92
+ │ │ │ ├── create.py
93
+ │ │ │ └── cancel.py
94
+ │ │ └── pickup/ # Pickup operations
95
+ │ │ ├── __init__.py
96
+ │ │ ├── create.py
97
+ │ │ ├── update.py
98
+ │ │ └── cancel.py
99
+ │ └── schemas/ # API schema definitions
100
+ │ └── plugin_name/
101
+ │ ├── __init__.py
102
+ │ └── various schema files...
103
+
104
+ Plugin Metadata:
105
+ Each plugin must define a METADATA object of type PluginMetadata in its __init__.py file.
106
+ The metadata should specify the plugin's capabilities, features, and other relevant information.
107
+
108
+ The plugin's capabilities (carrier integration, address validation, etc.) are automatically
109
+ determined based on the components defined in the metadata and registered modules.
110
+ """
111
+
112
+ import os
113
+ import sys
114
+ import inspect
115
+ import logging
116
+ import importlib
117
+ import traceback
118
+ import pkgutil
119
+ from typing import List, Optional, Dict, Any, Tuple
120
+ import importlib.metadata as importlib_metadata
121
+
122
+ # Configure logger with a higher default level to reduce noise
123
+ logger = logging.getLogger(__name__)
124
+ if not logger.level:
125
+ logger.setLevel(logging.INFO)
126
+
127
+ # Default plugin directories to scan
128
+ DEFAULT_PLUGINS = [
129
+ os.path.join(os.getcwd(), "plugins"), # Local plugins directory
130
+ os.path.join(os.getcwd(), "community/plugins"), # Community plugins directory
131
+ ]
132
+
133
+ # Track failed plugin loads
134
+ FAILED_PLUGIN_MODULES: Dict[str, Any] = {}
135
+
136
+ # Entrypoint group for Karrio plugins
137
+ ENTRYPOINT_GROUP = "karrio.plugins"
138
+
139
+ # Ensure the karrio.plugins module exists
140
+ try:
141
+ import karrio.plugins
142
+ except ImportError:
143
+ # Create an empty module for plugins if it doesn't exist
144
+ try:
145
+ import types
146
+ import karrio
147
+ karrio.plugins = types.ModuleType('karrio.plugins')
148
+ karrio.plugins.__path__ = []
149
+ sys.modules['karrio.plugins'] = karrio.plugins
150
+ logger.debug("Created karrio.plugins module")
151
+ except (ImportError, AttributeError) as e:
152
+ logger.error(f"Failed to create karrio.plugins module: {e}")
153
+
154
+ def get_custom_plugin_dirs() -> List[str]:
155
+ """
156
+ Get custom plugin directory from environment variable.
157
+
158
+ Checks if the KARRIO_PLUGINS environment variable is defined
159
+ and if the directory exists. Returns a list containing the
160
+ custom plugin directory if it's valid.
161
+
162
+ Returns:
163
+ List[str]: List of valid custom plugin directories from environment variables
164
+ """
165
+ custom_dirs = []
166
+ env_plugins = os.environ.get("KARRIO_PLUGINS", "")
167
+
168
+ if env_plugins and os.path.exists(env_plugins):
169
+ custom_dirs.append(env_plugins)
170
+
171
+ return custom_dirs
172
+
173
+ # Initialize DEFAULT_PLUGINS with environment variable directories
174
+ custom_dirs = get_custom_plugin_dirs()
175
+ for directory in custom_dirs:
176
+ if directory not in DEFAULT_PLUGINS:
177
+ DEFAULT_PLUGINS.append(directory)
178
+
179
+ def add_plugin_directory(directory: str) -> None:
180
+ """
181
+ Add a custom plugin directory programmatically.
182
+
183
+ This function checks if the directory exists and if it's not already
184
+ in the DEFAULT_PLUGINS list. If it passes these checks, the directory
185
+ is added to the list and the extensions are reloaded.
186
+
187
+ Args:
188
+ directory (str): Path to the plugin directory to add
189
+
190
+ Example:
191
+ >>> import karrio.plugins as plugins
192
+ >>> plugins.add_plugin_directory('/path/to/your/plugins')
193
+ """
194
+ global DEFAULT_PLUGINS
195
+ if os.path.exists(directory) and directory not in DEFAULT_PLUGINS:
196
+ DEFAULT_PLUGINS.append(directory)
197
+ # Trigger a reload of plugins when a new directory is added
198
+ try:
199
+ import karrio.references
200
+ if hasattr(karrio.references, "import_extensions"):
201
+ karrio.references.import_extensions()
202
+ except (ImportError, AttributeError):
203
+ pass # Silently ignore if references module can't be imported
204
+
205
+ def discover_plugins(plugin_dirs: Optional[List[str]] = None) -> List[str]:
206
+ """
207
+ Discover available plugins in the specified directories.
208
+
209
+ Scans the given directories (or DEFAULT_PLUGINS if none specified)
210
+ for valid Karrio plugin structures. A valid plugin structure must
211
+ have a 'karrio' subdirectory with plugins, mappers and/or validators.
212
+
213
+ Args:
214
+ plugin_dirs: List of directories to scan for plugins.
215
+ If None, uses DEFAULT_PLUGINS.
216
+
217
+ Returns:
218
+ List of paths to valid plugin directories
219
+ """
220
+ if plugin_dirs is None:
221
+ plugin_dirs = DEFAULT_PLUGINS
222
+ else:
223
+ # Ensure plugin_dirs has unique entries
224
+ plugin_dirs = list(dict.fromkeys(plugin_dirs))
225
+
226
+ plugins = []
227
+ for plugin_dir in plugin_dirs:
228
+ if not os.path.exists(plugin_dir):
229
+ continue
230
+
231
+ # Look for directories that might be plugins
232
+ for item in os.listdir(plugin_dir):
233
+ item_path = os.path.join(plugin_dir, item)
234
+ if os.path.isdir(item_path):
235
+ # Check if this directory has a karrio subdirectory
236
+ karrio_dir = os.path.join(item_path, "karrio")
237
+
238
+ # Skip if karrio directory doesn't exist
239
+ if not os.path.isdir(karrio_dir):
240
+ continue
241
+
242
+ # Check for plugins, mappers or validators subdirectories
243
+ plugins_dir = os.path.join(karrio_dir, "plugins")
244
+ mappers_dir = os.path.join(karrio_dir, "mappers")
245
+ validators_dir = os.path.join(karrio_dir, "validators")
246
+
247
+ if (os.path.isdir(plugins_dir) or
248
+ os.path.isdir(mappers_dir) or
249
+ os.path.isdir(validators_dir)):
250
+ plugins.append(item_path)
251
+
252
+ # Ensure returned plugin paths are unique
253
+ return list(dict.fromkeys(plugins))
254
+
255
+ def discover_plugin_modules(plugin_dirs: Optional[List[str]] = None,
256
+ module_types: List[str] = None) -> Dict[str, Dict[str, Any]]:
257
+ """
258
+ Discover and collect modules from plugins by type.
259
+
260
+ Scans plugin directories for modules of specified types (plugins, mappers, validators, etc.)
261
+ and returns them organized by plugin name and module type.
262
+
263
+ Args:
264
+ plugin_dirs: List of plugin directories to scan (uses DEFAULT_PLUGINS if None)
265
+ module_types: List of module types to discover (defaults to ["plugins", "mappers", "validators"])
266
+
267
+ Returns:
268
+ Dict mapping plugin names to a dict of module types and their module objects
269
+ """
270
+ global FAILED_PLUGIN_MODULES
271
+ FAILED_PLUGIN_MODULES = {}
272
+
273
+ if module_types is None:
274
+ module_types = ["plugins", "mappers", "validators"]
275
+
276
+ plugin_paths = discover_plugins(plugin_dirs)
277
+ plugin_modules: Dict[str, Dict[str, Any]] = {}
278
+
279
+ for plugin_path in plugin_paths:
280
+ plugin_name = os.path.basename(plugin_path)
281
+ karrio_dir = os.path.join(plugin_path, "karrio")
282
+
283
+ # Skip if karrio directory doesn't exist
284
+ if not os.path.isdir(karrio_dir):
285
+ continue
286
+
287
+ plugin_modules[plugin_name] = {}
288
+
289
+ # Check for each module type
290
+ for module_type in module_types:
291
+ module_dir = os.path.join(karrio_dir, module_type)
292
+
293
+ # Skip if this module type doesn't exist in the plugin
294
+ if not os.path.isdir(module_dir):
295
+ continue
296
+
297
+ # Look for plugin-specific submodules (e.g., plugins/plugin_name or mappers/plugin_name)
298
+ for subitem in os.listdir(module_dir):
299
+ subitem_path = os.path.join(module_dir, subitem)
300
+ submodule_init = os.path.join(subitem_path, "__init__.py")
301
+
302
+ if os.path.isdir(subitem_path) and os.path.exists(submodule_init):
303
+ submodule_name = subitem
304
+
305
+ # Try to import the module
306
+ try:
307
+ # Add the plugin's parent dir to sys.path if not already there
308
+ plugin_parent = os.path.dirname(plugin_path)
309
+ if plugin_parent not in sys.path:
310
+ sys.path.insert(0, plugin_parent)
311
+
312
+ # Import the module
313
+ module_path = f"karrio.{module_type}.{submodule_name}"
314
+ module = importlib.import_module(module_path)
315
+
316
+ # Store successful imports
317
+ if module_type not in plugin_modules[plugin_name]:
318
+ plugin_modules[plugin_name][module_type] = {}
319
+
320
+ plugin_modules[plugin_name][module_type][submodule_name] = module
321
+
322
+ except Exception as e:
323
+ # Track failed module imports
324
+ logger.error(f"Error importing {module_type}.{submodule_name} from {plugin_name}: {str(e)}")
325
+ key = f"{plugin_name}.{module_type}.{submodule_name}"
326
+ FAILED_PLUGIN_MODULES[key] = {
327
+ "plugin": plugin_name,
328
+ "module_type": module_type,
329
+ "submodule": submodule_name,
330
+ "error": str(e),
331
+ "traceback": traceback.format_exc()
332
+ }
333
+
334
+ return plugin_modules
335
+
336
+ def collect_plugin_metadata(plugin_modules: Dict[str, Dict[str, Any]]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
337
+ """
338
+ Collect metadata from discovered plugin modules.
339
+
340
+ Args:
341
+ plugin_modules: Dictionary of plugin modules organized by plugin name and module type
342
+
343
+ Returns:
344
+ Tuple containing:
345
+ - Dictionary of successful plugin metadata
346
+ - Dictionary of failed plugin metadata attempts
347
+ """
348
+ plugin_metadata = {}
349
+ failed_metadata = {}
350
+
351
+ for plugin_name, modules_by_type in plugin_modules.items():
352
+ metadata_found = False
353
+
354
+ # First try to find metadata in plugins (new structure)
355
+ if "plugins" in modules_by_type:
356
+ for submodule_name, module in modules_by_type["plugins"].items():
357
+ try:
358
+ if hasattr(module, "METADATA"):
359
+ plugin_metadata[plugin_name] = module.METADATA
360
+ metadata_found = True
361
+ break
362
+ except Exception as e:
363
+ key = f"{plugin_name}.plugins.{submodule_name}"
364
+ failed_metadata[key] = {
365
+ "plugin": plugin_name,
366
+ "module_type": "plugins",
367
+ "submodule": submodule_name,
368
+ "error": str(e)
369
+ }
370
+
371
+ # If not found in plugins, try mappers (legacy structure)
372
+ if not metadata_found and "mappers" in modules_by_type:
373
+ for submodule_name, module in modules_by_type["mappers"].items():
374
+ try:
375
+ if hasattr(module, "METADATA"):
376
+ plugin_metadata[plugin_name] = module.METADATA
377
+ metadata_found = True
378
+ break
379
+ except Exception as e:
380
+ key = f"{plugin_name}.mappers.{submodule_name}"
381
+ failed_metadata[key] = {
382
+ "plugin": plugin_name,
383
+ "module_type": "mappers",
384
+ "submodule": submodule_name,
385
+ "error": str(e)
386
+ }
387
+
388
+ # If not found in mappers, try validators (legacy structure)
389
+ if not metadata_found and "validators" in modules_by_type:
390
+ for submodule_name, module in modules_by_type["validators"].items():
391
+ try:
392
+ if hasattr(module, "METADATA"):
393
+ plugin_metadata[plugin_name] = module.METADATA
394
+ metadata_found = True
395
+ break
396
+ except Exception as e:
397
+ key = f"{plugin_name}.validators.{submodule_name}"
398
+ failed_metadata[key] = {
399
+ "plugin": plugin_name,
400
+ "module_type": "validators",
401
+ "submodule": submodule_name,
402
+ "error": str(e)
403
+ }
404
+
405
+ if not metadata_found and modules_by_type:
406
+ # Record error only if we have some modules but no metadata
407
+ failed_metadata[plugin_name] = {
408
+ "plugin": plugin_name,
409
+ "error": "No METADATA found in any module"
410
+ }
411
+
412
+ # NEW: Try direct import of karrio.plugins.[plugin_name] and karrio.mappers.[plugin_name] if not found
413
+ if not metadata_found:
414
+ for modtype in ["plugins", "mappers"]:
415
+ try:
416
+ module_path = f"karrio.{modtype}.{plugin_name}"
417
+ module = importlib.import_module(module_path)
418
+ if hasattr(module, "METADATA"):
419
+ plugin_metadata[plugin_name] = module.METADATA
420
+ metadata_found = True
421
+ break
422
+ except Exception as e:
423
+ key = f"{plugin_name}.{modtype}.__init__"
424
+ failed_metadata[key] = {
425
+ "plugin": plugin_name,
426
+ "module_type": modtype,
427
+ "submodule": "__init__",
428
+ "error": str(e)
429
+ }
430
+
431
+ return plugin_metadata, failed_metadata
432
+
433
+ def load_local_plugins(plugin_dirs: Optional[List[str]] = None) -> List[str]:
434
+ """
435
+ Load plugins from local directories into the karrio namespace.
436
+
437
+ This function:
438
+ 1. Discovers plugins in the specified directories
439
+ 2. Adds the plugin parent directory to sys.path
440
+ 3. Extends the karrio namespace to include the plugin modules
441
+ 4. Refreshes karrio.references (if not called from references)
442
+
443
+ Args:
444
+ plugin_dirs: List of directories to scan for plugins.
445
+ If None, uses DEFAULT_PLUGINS.
446
+
447
+ Returns:
448
+ List of plugin names that were successfully loaded
449
+ """
450
+
451
+ plugins = discover_plugins(plugin_dirs)
452
+ loaded_plugins = []
453
+ already_processed = set() # Track which plugins have been processed to avoid duplicates
454
+
455
+ # Ensure all required namespaces exist
456
+ required_namespaces = ["plugins", "mappers", "providers", "schemas", "validators"]
457
+ for namespace in required_namespaces:
458
+ module_name = f"karrio.{namespace}"
459
+ try:
460
+ # Try to import the module
461
+ importlib.import_module(module_name)
462
+ except ImportError:
463
+ # Create the module if it doesn't exist
464
+ try:
465
+ import types
466
+ import karrio
467
+ module = types.ModuleType(module_name)
468
+ module.__path__ = []
469
+ setattr(karrio, namespace, module)
470
+ sys.modules[module_name] = module
471
+ logger.debug(f"Created {module_name} module")
472
+ except (ImportError, AttributeError) as e:
473
+ logger.error(f"Failed to create {module_name} module: {e}")
474
+
475
+ for plugin_path in plugins:
476
+ # Skip if we've already processed this plugin path
477
+ if plugin_path in already_processed:
478
+ continue
479
+
480
+ already_processed.add(plugin_path)
481
+ plugin_name = os.path.basename(plugin_path)
482
+
483
+ # Add the plugin's parent directory to sys.path
484
+ plugin_parent = os.path.dirname(plugin_path)
485
+ if plugin_parent not in sys.path:
486
+ sys.path.insert(0, plugin_parent)
487
+
488
+ # Check if the plugin has the necessary structure
489
+ karrio_dir = os.path.join(plugin_path, "karrio")
490
+
491
+ # Skip if karrio directory doesn't exist
492
+ if not os.path.isdir(karrio_dir):
493
+ logger.error(f"Invalid plugin structure: missing karrio directory in {plugin_path}")
494
+ continue
495
+
496
+ # Look for plugins, mappers, providers, schemas, and validators directories
497
+ for module_name in required_namespaces:
498
+ module_dir = os.path.join(karrio_dir, module_name)
499
+
500
+ # Skip if directory doesn't exist
501
+ if not os.path.isdir(module_dir):
502
+ continue
503
+
504
+ # Try to extend the corresponding karrio namespace
505
+ try:
506
+ target_module_name = f"karrio.{module_name}"
507
+ target_module = importlib.import_module(target_module_name)
508
+
509
+ # Extend the module's __path__ to include our plugin directory
510
+ if hasattr(target_module, "__path__"):
511
+ extended_path = pkgutil.extend_path(target_module.__path__, target_module.__name__)
512
+ if module_dir not in extended_path:
513
+ extended_path.append(module_dir)
514
+ target_module.__path__ = extended_path
515
+ except ImportError as e:
516
+ logger.error(f"Could not import {target_module_name}: {e}")
517
+ continue
518
+
519
+ # Mark plugin as loaded
520
+ loaded_plugins.append(plugin_name)
521
+
522
+ # To prevent recursion, only refresh references if we're not being called from references
523
+ # This check uses the stack frame inspection to see if we're being called from import_extensions
524
+ calling_module = ''
525
+ frame = inspect.currentframe()
526
+ if frame and frame.f_back:
527
+ calling_module = frame.f_back.f_globals.get('__name__', '')
528
+ if 'karrio.references' not in calling_module:
529
+ try:
530
+ import karrio.references
531
+ if hasattr(karrio.references, "import_extensions"):
532
+ karrio.references.import_extensions()
533
+ logger.info("Refreshed karrio.references providers")
534
+ except (ImportError, AttributeError) as e:
535
+ logger.error(f"Could not refresh karrio.references: {e}")
536
+
537
+ return loaded_plugins
538
+
539
+ def get_failed_plugin_modules() -> Dict[str, Any]:
540
+ """
541
+ Get information about plugin modules that failed to load.
542
+
543
+ Returns:
544
+ Dict containing information about failed plugin module loads
545
+ """
546
+ return FAILED_PLUGIN_MODULES
547
+
548
+ def discover_entrypoint_plugins() -> Dict[str, Dict[str, Any]]:
549
+ """
550
+ Discover plugins registered via setuptools entrypoints.
551
+
552
+ This function looks for plugins registered under the 'karrio.plugins'
553
+ entrypoint group. Each entrypoint should point to a module with a METADATA
554
+ object that defines the plugin's capabilities.
555
+
556
+ Returns:
557
+ Dict mapping plugin names to a dict containing the plugin module
558
+ """
559
+ global FAILED_PLUGIN_MODULES
560
+ entrypoint_plugins = {}
561
+
562
+ try:
563
+ # Find all entry points in the karrio.plugins group
564
+ entry_points = importlib_metadata.entry_points()
565
+
566
+ # Handle different entry_points behavior in different versions of importlib_metadata
567
+ if hasattr(entry_points, 'select'): # Python 3.10+
568
+ plugin_entry_points = entry_points.select(group=ENTRYPOINT_GROUP)
569
+ elif hasattr(entry_points, 'get'): # Python 3.8, 3.9
570
+ plugin_entry_points = entry_points.get(ENTRYPOINT_GROUP, [])
571
+ else: # Older versions or different implementation
572
+ plugin_entry_points = [
573
+ ep for ep in entry_points
574
+ if getattr(ep, 'group', None) == ENTRYPOINT_GROUP
575
+ ]
576
+
577
+ for entry_point in plugin_entry_points:
578
+ plugin_name = entry_point.name
579
+
580
+ try:
581
+ # Load the plugin module
582
+ plugin_module = entry_point.load()
583
+
584
+ # Create a structured dict similar to discover_plugin_modules output
585
+ if plugin_name not in entrypoint_plugins:
586
+ entrypoint_plugins[plugin_name] = {
587
+ "entrypoint": {
588
+ plugin_name: plugin_module
589
+ }
590
+ }
591
+
592
+ except Exception as e:
593
+ # Track failed entrypoint loads
594
+ logger.error(f"Error loading entrypoint plugin {plugin_name}: {str(e)}")
595
+ key = f"entrypoint.{plugin_name}"
596
+ FAILED_PLUGIN_MODULES[key] = {
597
+ "plugin": plugin_name,
598
+ "module_type": "entrypoint",
599
+ "error": str(e),
600
+ "traceback": traceback.format_exc()
601
+ }
602
+
603
+ except Exception as e:
604
+ logger.error(f"Error discovering entrypoint plugins: {str(e)}")
605
+
606
+ return entrypoint_plugins
karrio/core/settings.py CHANGED
@@ -3,11 +3,12 @@
3
3
  import abc
4
4
  import attr
5
5
  import typing
6
+ import functools
6
7
 
7
8
 
8
9
  @attr.s(auto_attribs=True)
9
10
  class Settings(abc.ABC):
10
- """Unified API carrier Connection settings (Interface)"""
11
+ """Unified API carrier connection settings (Interface)"""
11
12
 
12
13
  carrier_id: str
13
14
  account_country_code: str = None
@@ -15,6 +16,7 @@ class Settings(abc.ABC):
15
16
  metadata: dict = {}
16
17
  config: dict = {}
17
18
  id: str = None
19
+ tracer = None # Will be set during Gateway initialization
18
20
 
19
21
  @property
20
22
  def carrier_name(self) -> typing.Optional[str]:
@@ -32,4 +34,39 @@ class Settings(abc.ABC):
32
34
  def connection_config(self):
33
35
  import karrio.lib as lib
34
36
 
35
- return lib.to_connection_config(self.config or {})
37
+ return lib.to_connection_config(
38
+ self.config or {},
39
+ option_type=lib.units.create_enum(
40
+ "ConnectionConfig",
41
+ dict(
42
+ label_type=lib.units.create_enum(
43
+ "LabelType", ["PDF", "ZPL"]
44
+ ),
45
+ )
46
+ ),
47
+ )
48
+
49
+ @property
50
+ def connection_cache(self):
51
+ import karrio.lib as lib
52
+
53
+ return getattr(self, "cache", None) or lib.Cache()
54
+
55
+ def trace(self, *args, **kwargs):
56
+ if self.tracer is None:
57
+ import karrio.lib as lib
58
+ self.tracer = lib.Tracer()
59
+
60
+ return self.tracer.with_metadata(
61
+ dict(
62
+ connection=dict(
63
+ id=self.id,
64
+ test_mode=self.test_mode,
65
+ carrier_id=self.carrier_id,
66
+ carrier_name=self.carrier_name,
67
+ )
68
+ )
69
+ )(*args, **kwargs)
70
+
71
+ def trace_as(self, format: str):
72
+ return functools.partial(self.trace, format=format)