pyarallel 0.1.2__tar.gz → 0.1.3__tar.gz
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.
- {pyarallel-0.1.2 → pyarallel-0.1.3}/PKG-INFO +51 -1
- {pyarallel-0.1.2 → pyarallel-0.1.3}/README.md +50 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/TASKS.md +35 -5
- {pyarallel-0.1.2 → pyarallel-0.1.3}/docs/development/roadmap.md +4 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/docs/index.md +40 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/pyarallel/__init__.py +2 -2
- {pyarallel-0.1.2 → pyarallel-0.1.3}/pyarallel/config.py +30 -48
- {pyarallel-0.1.2 → pyarallel-0.1.3}/pyarallel/config_manager.py +74 -48
- {pyarallel-0.1.2 → pyarallel-0.1.3}/pyarallel/core.py +209 -92
- {pyarallel-0.1.2 → pyarallel-0.1.3}/pyarallel/env_config.py +5 -4
- {pyarallel-0.1.2 → pyarallel-0.1.3}/pyproject.toml +1 -1
- {pyarallel-0.1.2 → pyarallel-0.1.3}/sandbox/sandbox_test.py +20 -29
- {pyarallel-0.1.2 → pyarallel-0.1.3}/setup.py +1 -1
- {pyarallel-0.1.2 → pyarallel-0.1.3}/tests/conftest.py +37 -25
- {pyarallel-0.1.2 → pyarallel-0.1.3}/tests/test_config_manager.py +11 -14
- {pyarallel-0.1.2 → pyarallel-0.1.3}/tests/test_decorator_config.py +48 -42
- {pyarallel-0.1.2 → pyarallel-0.1.3}/tests/test_env_config.py +15 -11
- pyarallel-0.1.3/tests/test_pyarallel.py +184 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/tests/test_runtime_config.py +46 -41
- pyarallel-0.1.2/tests/test_pyarallel.py +0 -99
- {pyarallel-0.1.2 → pyarallel-0.1.3}/.gitignore +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/.python-version +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/CONTRIBUTING.md +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/LICENSE.md +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/Makefile +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/docs/api-reference/configuration-api.md +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/docs/api-reference/decorators.md +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/docs/api-reference/rate-limiting.md +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/docs/development/CONTRIBUTING.md +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/docs/getting-started/installation.md +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/docs/getting-started/quickstart.md +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/docs/user-guide/advanced-features.md +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/docs/user-guide/best-practices.md +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/docs/user-guide/configuration.md +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/mkdocs.yml +0 -0
- {pyarallel-0.1.2 → pyarallel-0.1.3}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyarallel
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: A powerful parallel execution library for Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/oneryalcin/pyarallel
|
|
6
6
|
Project-URL: Repository, https://github.com/oneryalcin/pyarallel.git
|
|
@@ -98,6 +98,56 @@ def analyze_text(text: str) -> dict:
|
|
|
98
98
|
return text_analysis(text)
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
## Usage Examples
|
|
102
|
+
|
|
103
|
+
### Basic Function
|
|
104
|
+
```python
|
|
105
|
+
from pyarallel import parallel
|
|
106
|
+
|
|
107
|
+
@parallel
|
|
108
|
+
def process_item(x):
|
|
109
|
+
return x * 2
|
|
110
|
+
|
|
111
|
+
results = process_item([1, 2, 3]) # [2, 4, 6]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Instance Methods
|
|
115
|
+
```python
|
|
116
|
+
class DataProcessor:
|
|
117
|
+
def __init__(self, multiplier):
|
|
118
|
+
self.multiplier = multiplier
|
|
119
|
+
|
|
120
|
+
@parallel
|
|
121
|
+
def process(self, x):
|
|
122
|
+
return x * self.multiplier
|
|
123
|
+
|
|
124
|
+
processor = DataProcessor(3)
|
|
125
|
+
results = processor.process([1, 2, 3]) # [3, 6, 9]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Class Methods
|
|
129
|
+
```python
|
|
130
|
+
class StringFormatter:
|
|
131
|
+
@classmethod
|
|
132
|
+
@parallel
|
|
133
|
+
def format_all(cls, items):
|
|
134
|
+
return [f"Formatted-{item}" for item in items]
|
|
135
|
+
|
|
136
|
+
results = StringFormatter.format_all(['a', 'b', 'c'])
|
|
137
|
+
# ['Formatted-a', 'Formatted-b', 'Formatted-c']
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Static Methods
|
|
141
|
+
```python
|
|
142
|
+
class MathUtils:
|
|
143
|
+
@staticmethod
|
|
144
|
+
@parallel
|
|
145
|
+
def square_all(numbers):
|
|
146
|
+
return [n**2 for n in numbers]
|
|
147
|
+
|
|
148
|
+
results = MathUtils.square_all([1, 2, 3]) # [1, 4, 9]
|
|
149
|
+
```
|
|
150
|
+
|
|
101
151
|
## Advanced Usage
|
|
102
152
|
|
|
103
153
|
### Rate Limiting
|
|
@@ -59,6 +59,56 @@ def analyze_text(text: str) -> dict:
|
|
|
59
59
|
return text_analysis(text)
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
## Usage Examples
|
|
63
|
+
|
|
64
|
+
### Basic Function
|
|
65
|
+
```python
|
|
66
|
+
from pyarallel import parallel
|
|
67
|
+
|
|
68
|
+
@parallel
|
|
69
|
+
def process_item(x):
|
|
70
|
+
return x * 2
|
|
71
|
+
|
|
72
|
+
results = process_item([1, 2, 3]) # [2, 4, 6]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Instance Methods
|
|
76
|
+
```python
|
|
77
|
+
class DataProcessor:
|
|
78
|
+
def __init__(self, multiplier):
|
|
79
|
+
self.multiplier = multiplier
|
|
80
|
+
|
|
81
|
+
@parallel
|
|
82
|
+
def process(self, x):
|
|
83
|
+
return x * self.multiplier
|
|
84
|
+
|
|
85
|
+
processor = DataProcessor(3)
|
|
86
|
+
results = processor.process([1, 2, 3]) # [3, 6, 9]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Class Methods
|
|
90
|
+
```python
|
|
91
|
+
class StringFormatter:
|
|
92
|
+
@classmethod
|
|
93
|
+
@parallel
|
|
94
|
+
def format_all(cls, items):
|
|
95
|
+
return [f"Formatted-{item}" for item in items]
|
|
96
|
+
|
|
97
|
+
results = StringFormatter.format_all(['a', 'b', 'c'])
|
|
98
|
+
# ['Formatted-a', 'Formatted-b', 'Formatted-c']
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Static Methods
|
|
102
|
+
```python
|
|
103
|
+
class MathUtils:
|
|
104
|
+
@staticmethod
|
|
105
|
+
@parallel
|
|
106
|
+
def square_all(numbers):
|
|
107
|
+
return [n**2 for n in numbers]
|
|
108
|
+
|
|
109
|
+
results = MathUtils.square_all([1, 2, 3]) # [1, 4, 9]
|
|
110
|
+
```
|
|
111
|
+
|
|
62
112
|
## Advanced Usage
|
|
63
113
|
|
|
64
114
|
### Rate Limiting
|
|
@@ -72,11 +72,11 @@ def test_runtime_warnings():
|
|
|
72
72
|
"""Warnings for problematic configs"""
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
## 5. Documentation [
|
|
76
|
-
- [
|
|
77
|
-
- [
|
|
78
|
-
- [
|
|
79
|
-
- [
|
|
75
|
+
## 5. Documentation [✅]
|
|
76
|
+
- [✅] Add configuration section to README
|
|
77
|
+
- [✅] Document all environment variables
|
|
78
|
+
- [✅] Add configuration examples
|
|
79
|
+
- [✅] Document best practices
|
|
80
80
|
|
|
81
81
|
## Configuration Schema
|
|
82
82
|
```python
|
|
@@ -121,3 +121,33 @@ PYARALLEL_SENTRY_DSN=https://...
|
|
|
121
121
|
- 🚧 In progress
|
|
122
122
|
- ❌ Blocked
|
|
123
123
|
- [ ] Not started
|
|
124
|
+
|
|
125
|
+
## Feature: Enhanced Decorator Ergonomics
|
|
126
|
+
|
|
127
|
+
Implement first-class support for decorating instance, static, and class methods with `@parallel`.
|
|
128
|
+
|
|
129
|
+
- [✅] **1. Analyze Core Decorator Logic**
|
|
130
|
+
- [✅] Examine `@parallel` implementation in `pyarallel/core.py`.
|
|
131
|
+
- [✅] Understand current argument inspection and `executor.submit` usage.
|
|
132
|
+
- [✅] **2. Modify Argument Handling**
|
|
133
|
+
- [✅] Update the `wrapper` function to detect method type (instance, static, class).
|
|
134
|
+
- [✅] Correctly identify the iterable/item argument based on method type (using `inspect` or signature analysis).
|
|
135
|
+
- [✅] **3. Adapt Executor Submission**
|
|
136
|
+
- [✅] Ensure `self`/`cls` are passed correctly to `executor.submit` for instance/class methods.
|
|
137
|
+
- [✅] Preserve other arguments (`*args`, `**kwargs`).
|
|
138
|
+
- [✅] **4. Implement Comprehensive Tests**
|
|
139
|
+
- [✅] Add tests for instance methods (`test_instance_method_parallel`).
|
|
140
|
+
- [✅] Add tests for static methods (`test_static_method_parallel`).
|
|
141
|
+
- [✅] Add tests for class methods (`test_class_method_parallel`).
|
|
142
|
+
- [✅] Include tests for single item and list inputs.
|
|
143
|
+
- [✅] Test methods with additional arguments.
|
|
144
|
+
- [✅] Ensure no regressions for regular functions.
|
|
145
|
+
- [✅] **5. Update Documentation**
|
|
146
|
+
- [✅] Update `README.md` and `docs/index.md` with clear examples for each method type.
|
|
147
|
+
- [✅] **6. Code Review and Refinement**
|
|
148
|
+
- [✅] Review implementation, tests, and documentation.
|
|
149
|
+
- [✅] Ensure robustness, adherence to standards, and clarity.
|
|
150
|
+
- [✅] **7. Add Type Hints**
|
|
151
|
+
- [✅] Add type hints to the `@parallel` decorator and inner `wrapper` in `pyarallel/core.py` for better IDE support.
|
|
152
|
+
|
|
153
|
+
## Future Enhancements / Ideas
|
|
@@ -56,6 +56,10 @@ Some of the features we're planning to add in the future:
|
|
|
56
56
|
- Resource quotas per task
|
|
57
57
|
|
|
58
58
|
### Developer Experience
|
|
59
|
+
- **Enhanced Decorator Ergonomics**
|
|
60
|
+
- First-class, documented support for `@parallel` on instance methods, static methods, and class methods.
|
|
61
|
+
- Automatic handling of `self` and `cls` arguments.
|
|
62
|
+
- Clear examples and best practices for OOP usage.
|
|
59
63
|
- **CLI Tools**
|
|
60
64
|
- Task monitoring dashboard
|
|
61
65
|
- Performance profiling
|
|
@@ -36,6 +36,46 @@ urls = ["http://api1.com", "http://api2.com"]
|
|
|
36
36
|
results = fetch_url(urls)
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
## Advanced Usage
|
|
40
|
+
|
|
41
|
+
### Method Support
|
|
42
|
+
|
|
43
|
+
The `@parallel` decorator works seamlessly with:
|
|
44
|
+
- Regular functions
|
|
45
|
+
- Instance methods (preserves `self`)
|
|
46
|
+
- Class methods (preserves `cls`)
|
|
47
|
+
- Static methods
|
|
48
|
+
|
|
49
|
+
#### Instance Method Example
|
|
50
|
+
```python
|
|
51
|
+
class DataTransformer:
|
|
52
|
+
def __init__(self, base):
|
|
53
|
+
self.base = base
|
|
54
|
+
|
|
55
|
+
@parallel
|
|
56
|
+
def transform(self, items):
|
|
57
|
+
return [self.base + x for x in items]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### Class Method Example
|
|
61
|
+
```python
|
|
62
|
+
class Logger:
|
|
63
|
+
log_prefix = "APP"
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
@parallel
|
|
67
|
+
def log_all(cls, messages):
|
|
68
|
+
return [f"{cls.log_prefix}: {msg}" for msg in messages]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Argument Handling
|
|
72
|
+
|
|
73
|
+
The decorator intelligently handles:
|
|
74
|
+
- Positional arguments
|
|
75
|
+
- Keyword arguments
|
|
76
|
+
- Mixed argument types
|
|
77
|
+
- Both single items and iterables
|
|
78
|
+
|
|
39
79
|
## Installation
|
|
40
80
|
|
|
41
81
|
```bash
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .core import
|
|
1
|
+
from .core import RateLimit, parallel
|
|
2
2
|
|
|
3
3
|
__version__ = "0.1.0"
|
|
4
4
|
__all__ = ["parallel", "RateLimit"]
|
|
@@ -7,4 +7,4 @@ __doc__ = f"""
|
|
|
7
7
|
pyarallel v{__version__}
|
|
8
8
|
|
|
9
9
|
Simple parallel processing framework for Python.
|
|
10
|
-
"""
|
|
10
|
+
"""
|
|
@@ -7,49 +7,39 @@ with support for loading configurations from different sources and thread-safe o
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any, Dict, Optional, Union
|
|
9
9
|
|
|
10
|
-
from pydantic import BaseModel,
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class ExecutionConfig(BaseModel):
|
|
14
14
|
"""Execution configuration settings."""
|
|
15
|
+
|
|
15
16
|
max_workers: int = Field(
|
|
16
|
-
default=4,
|
|
17
|
-
description="Maximum number of worker processes/threads",
|
|
18
|
-
ge=1
|
|
17
|
+
default=4, description="Maximum number of worker processes/threads", ge=1
|
|
19
18
|
)
|
|
20
19
|
timeout: float = Field(
|
|
21
20
|
default=30.0,
|
|
22
21
|
description="Default timeout for parallel operations in seconds",
|
|
23
|
-
ge=0
|
|
22
|
+
ge=0,
|
|
24
23
|
)
|
|
25
24
|
default_max_workers: int = Field(
|
|
26
|
-
default=4,
|
|
27
|
-
description="Default number of workers for parallel operations",
|
|
28
|
-
ge=1
|
|
25
|
+
default=4, description="Default number of workers for parallel operations", ge=1
|
|
29
26
|
)
|
|
30
27
|
default_executor_type: str = Field(
|
|
31
|
-
default="thread",
|
|
32
|
-
description="Default executor type (thread or process)"
|
|
28
|
+
default="thread", description="Default executor type (thread or process)"
|
|
33
29
|
)
|
|
34
30
|
default_batch_size: int = Field(
|
|
35
|
-
default=10,
|
|
36
|
-
description="Default batch size for parallel operations",
|
|
37
|
-
ge=1
|
|
31
|
+
default=10, description="Default batch size for parallel operations", ge=1
|
|
38
32
|
)
|
|
39
|
-
|
|
33
|
+
|
|
40
34
|
model_config = ConfigDict(extra="allow") # Allow dynamic fields
|
|
41
35
|
|
|
36
|
+
|
|
42
37
|
class RateLimitingConfig(BaseModel):
|
|
43
38
|
"""Rate limiting configuration settings."""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
)
|
|
49
|
-
interval: str = Field(
|
|
50
|
-
default="minute",
|
|
51
|
-
description="Rate limit interval"
|
|
52
|
-
)
|
|
39
|
+
|
|
40
|
+
rate: int = Field(default=1000, description="Rate limit per interval", ge=0)
|
|
41
|
+
interval: str = Field(default="minute", description="Rate limit interval")
|
|
42
|
+
|
|
53
43
|
|
|
54
44
|
class PyarallelConfig(BaseModel):
|
|
55
45
|
"""Base configuration class for pyarallel.
|
|
@@ -60,57 +50,44 @@ class PyarallelConfig(BaseModel):
|
|
|
60
50
|
|
|
61
51
|
# Execution settings
|
|
62
52
|
max_workers: int = Field(
|
|
63
|
-
default=4,
|
|
64
|
-
description="Maximum number of worker processes/threads",
|
|
65
|
-
ge=1
|
|
53
|
+
default=4, description="Maximum number of worker processes/threads", ge=1
|
|
66
54
|
)
|
|
67
55
|
timeout: float = Field(
|
|
68
56
|
default=30.0,
|
|
69
57
|
description="Default timeout for parallel operations in seconds",
|
|
70
|
-
ge=0
|
|
58
|
+
ge=0,
|
|
71
59
|
)
|
|
72
60
|
execution: Optional[ExecutionConfig] = Field(
|
|
73
|
-
default=None,
|
|
74
|
-
description="Nested execution settings"
|
|
61
|
+
default=None, description="Nested execution settings"
|
|
75
62
|
)
|
|
76
63
|
|
|
77
64
|
# Rate limiting settings
|
|
78
65
|
rate_limiting: Optional[RateLimitingConfig] = Field(
|
|
79
|
-
default=None,
|
|
80
|
-
description="Rate limiting settings"
|
|
66
|
+
default=None, description="Rate limiting settings"
|
|
81
67
|
)
|
|
82
68
|
|
|
83
69
|
# Error handling settings
|
|
84
70
|
error_handling: Dict[str, Any] = Field(
|
|
85
71
|
default_factory=lambda: {"retry_count": 3},
|
|
86
|
-
description="Error handling settings"
|
|
72
|
+
description="Error handling settings",
|
|
87
73
|
)
|
|
88
74
|
|
|
89
75
|
# Monitoring settings
|
|
90
76
|
monitoring: Dict[str, Any] = Field(
|
|
91
|
-
default_factory=lambda: {"enabled": False},
|
|
92
|
-
description="Monitoring settings"
|
|
77
|
+
default_factory=lambda: {"enabled": False}, description="Monitoring settings"
|
|
93
78
|
)
|
|
94
79
|
|
|
95
80
|
# Resource management
|
|
96
81
|
memory_limit: Optional[int] = Field(
|
|
97
|
-
default=None,
|
|
98
|
-
description="Memory limit per worker in bytes, None for no limit"
|
|
82
|
+
default=None, description="Memory limit per worker in bytes, None for no limit"
|
|
99
83
|
)
|
|
100
84
|
cpu_affinity: bool = Field(
|
|
101
|
-
default=False,
|
|
102
|
-
description="Enable CPU affinity for workers"
|
|
85
|
+
default=False, description="Enable CPU affinity for workers"
|
|
103
86
|
)
|
|
104
87
|
|
|
105
88
|
# Logging and debugging
|
|
106
|
-
debug: bool = Field(
|
|
107
|
-
|
|
108
|
-
description="Enable debug mode"
|
|
109
|
-
)
|
|
110
|
-
log_level: str = Field(
|
|
111
|
-
default="INFO",
|
|
112
|
-
description="Logging level"
|
|
113
|
-
)
|
|
89
|
+
debug: bool = Field(default=False, description="Enable debug mode")
|
|
90
|
+
log_level: str = Field(default="INFO", description="Logging level")
|
|
114
91
|
|
|
115
92
|
@classmethod
|
|
116
93
|
def from_dict(cls, config_dict: Dict[str, Any]) -> "PyarallelConfig":
|
|
@@ -148,18 +125,23 @@ class PyarallelConfig(BaseModel):
|
|
|
148
125
|
# Load based on file extension
|
|
149
126
|
if config_path.suffix == ".json":
|
|
150
127
|
import json
|
|
128
|
+
|
|
151
129
|
with open(config_path) as f:
|
|
152
130
|
config_dict = json.load(f)
|
|
153
131
|
elif config_path.suffix in (".yml", ".yaml"):
|
|
154
132
|
import yaml
|
|
133
|
+
|
|
155
134
|
with open(config_path) as f:
|
|
156
135
|
config_dict = yaml.safe_load(f)
|
|
157
136
|
elif config_path.suffix == ".toml":
|
|
158
137
|
import toml
|
|
138
|
+
|
|
159
139
|
with open(config_path) as f:
|
|
160
140
|
config_dict = toml.load(f)
|
|
161
141
|
else:
|
|
162
|
-
raise ValueError(
|
|
142
|
+
raise ValueError(
|
|
143
|
+
f"Unsupported configuration file format: {config_path.suffix}"
|
|
144
|
+
)
|
|
163
145
|
|
|
164
146
|
return cls.from_dict(config_dict)
|
|
165
147
|
|
|
@@ -169,4 +151,4 @@ class PyarallelConfig(BaseModel):
|
|
|
169
151
|
Returns:
|
|
170
152
|
Dictionary representation of the configuration
|
|
171
153
|
"""
|
|
172
|
-
return self.model_dump()
|
|
154
|
+
return self.model_dump()
|
|
@@ -13,7 +13,7 @@ from .env_config import load_env_vars
|
|
|
13
13
|
|
|
14
14
|
logger = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
|
-
T = TypeVar(
|
|
16
|
+
T = TypeVar("T", bound="ConfigManager")
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class ConfigManager:
|
|
@@ -22,6 +22,7 @@ class ConfigManager:
|
|
|
22
22
|
This class ensures thread-safe access to configuration settings and provides
|
|
23
23
|
methods for updating and retrieving configuration values.
|
|
24
24
|
"""
|
|
25
|
+
|
|
25
26
|
_instance: Optional[T] = None
|
|
26
27
|
_lock: RLock = RLock()
|
|
27
28
|
_config: Optional[PyarallelConfig] = None
|
|
@@ -34,18 +35,23 @@ class ConfigManager:
|
|
|
34
35
|
# Initialize with default config
|
|
35
36
|
config_dict = PyarallelConfig().model_dump()
|
|
36
37
|
logger.debug(f"Default config: {config_dict}")
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
# Load and apply environment variables
|
|
39
40
|
env_config = load_env_vars()
|
|
40
41
|
logger.debug(f"Loaded environment config: {env_config}")
|
|
41
|
-
|
|
42
|
+
|
|
42
43
|
# Update config with environment variables
|
|
43
44
|
if env_config:
|
|
44
45
|
logger.debug("Updating config with environment variables")
|
|
45
|
-
config_dict = {
|
|
46
|
+
config_dict = {
|
|
47
|
+
**config_dict,
|
|
48
|
+
**env_config,
|
|
49
|
+
} # Use dictionary unpacking for proper update
|
|
46
50
|
logger.debug(f"Updated config: {config_dict}")
|
|
47
|
-
|
|
48
|
-
cls._instance._config = PyarallelConfig(
|
|
51
|
+
|
|
52
|
+
cls._instance._config = PyarallelConfig(
|
|
53
|
+
**config_dict
|
|
54
|
+
) # Use direct instantiation
|
|
49
55
|
logger.debug(f"Final config: {cls._instance._config}")
|
|
50
56
|
return cls._instance
|
|
51
57
|
|
|
@@ -79,18 +85,20 @@ class ConfigManager:
|
|
|
79
85
|
current_config = self._config.model_dump()
|
|
80
86
|
logger.debug(f"Current config before update: {current_config}")
|
|
81
87
|
logger.debug(f"Incoming updates: {updates}")
|
|
82
|
-
|
|
88
|
+
|
|
83
89
|
# Validate max_workers before merging
|
|
84
90
|
if "max_workers" in updates and updates["max_workers"] < 1:
|
|
85
91
|
updates["max_workers"] = 1
|
|
86
|
-
|
|
92
|
+
|
|
87
93
|
# Use deep merge for nested updates
|
|
88
94
|
merged_config = self._deep_merge(current_config, updates)
|
|
89
95
|
logger.debug(f"Merged config: {merged_config}")
|
|
90
96
|
self._config = PyarallelConfig.from_dict(merged_config)
|
|
91
97
|
logger.debug(f"Final config after update: {self._config}")
|
|
92
98
|
|
|
93
|
-
def _deep_merge(
|
|
99
|
+
def _deep_merge(
|
|
100
|
+
self, base: Dict[str, Any], updates: Dict[str, Any]
|
|
101
|
+
) -> Dict[str, Any]:
|
|
94
102
|
"""Recursively merge two configuration dictionaries with special handling for execution settings.
|
|
95
103
|
|
|
96
104
|
This method implements a specialized merge strategy for configuration dictionaries that:
|
|
@@ -123,13 +131,13 @@ class ConfigManager:
|
|
|
123
131
|
logger.debug(f"Deep merging - Base: {base}")
|
|
124
132
|
logger.debug(f"Deep merging - Updates: {updates}")
|
|
125
133
|
result = base.copy()
|
|
126
|
-
|
|
134
|
+
|
|
127
135
|
# Initialize critical nested structures
|
|
128
136
|
self._ensure_critical_structures(result)
|
|
129
|
-
|
|
137
|
+
|
|
130
138
|
for key, value in updates.items():
|
|
131
139
|
logger.debug(f"Processing key: {key}, value: {value}")
|
|
132
|
-
|
|
140
|
+
|
|
133
141
|
if self._is_execution_update(key, value):
|
|
134
142
|
self._handle_execution_settings(result, value)
|
|
135
143
|
elif self._is_nested_dict_update(key, value, result):
|
|
@@ -137,11 +145,11 @@ class ConfigManager:
|
|
|
137
145
|
else:
|
|
138
146
|
result[key] = value
|
|
139
147
|
logger.debug(f"Direct value assignment for key: {key}")
|
|
140
|
-
|
|
148
|
+
|
|
141
149
|
# Re-ensure critical structures after each update
|
|
142
150
|
self._ensure_critical_structures(result)
|
|
143
151
|
logger.debug(f"Current result after processing {key}: {result}")
|
|
144
|
-
|
|
152
|
+
|
|
145
153
|
return result
|
|
146
154
|
|
|
147
155
|
def _ensure_critical_structures(self, config: Dict[str, Any]) -> None:
|
|
@@ -151,15 +159,17 @@ class ConfigManager:
|
|
|
151
159
|
config: The configuration dictionary to initialize
|
|
152
160
|
"""
|
|
153
161
|
critical_structures = {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
162
|
+
"execution": {},
|
|
163
|
+
"rate_limiting": {},
|
|
164
|
+
"error_handling": {"retry_count": 3},
|
|
165
|
+
"monitoring": {"enabled": False},
|
|
158
166
|
}
|
|
159
167
|
for structure, default_value in critical_structures.items():
|
|
160
168
|
if structure not in config or config[structure] is None:
|
|
161
169
|
config[structure] = default_value.copy()
|
|
162
|
-
logger.debug(
|
|
170
|
+
logger.debug(
|
|
171
|
+
f"Initialized {structure} structure with defaults: {default_value}"
|
|
172
|
+
)
|
|
163
173
|
|
|
164
174
|
def _is_execution_update(self, key: str, value: Any) -> bool:
|
|
165
175
|
"""Check if the update is for execution settings.
|
|
@@ -173,7 +183,9 @@ class ConfigManager:
|
|
|
173
183
|
"""
|
|
174
184
|
return key == "execution" and isinstance(value, dict)
|
|
175
185
|
|
|
176
|
-
def _is_nested_dict_update(
|
|
186
|
+
def _is_nested_dict_update(
|
|
187
|
+
self, key: str, value: Any, base: Dict[str, Any]
|
|
188
|
+
) -> bool:
|
|
177
189
|
"""Check if the update is for a nested dictionary.
|
|
178
190
|
|
|
179
191
|
Args:
|
|
@@ -184,11 +196,11 @@ class ConfigManager:
|
|
|
184
196
|
Returns:
|
|
185
197
|
bool: True if this is a nested dictionary update
|
|
186
198
|
"""
|
|
187
|
-
return
|
|
188
|
-
isinstance(base[key], dict) and
|
|
189
|
-
isinstance(value, dict))
|
|
199
|
+
return key in base and isinstance(base[key], dict) and isinstance(value, dict)
|
|
190
200
|
|
|
191
|
-
def _handle_execution_settings(
|
|
201
|
+
def _handle_execution_settings(
|
|
202
|
+
self, config: Dict[str, Any], execution_settings: Dict[str, Any]
|
|
203
|
+
) -> None:
|
|
192
204
|
"""Handle special case of execution settings update.
|
|
193
205
|
|
|
194
206
|
This method maintains both top-level and nested execution settings
|
|
@@ -201,19 +213,23 @@ class ConfigManager:
|
|
|
201
213
|
# Update top-level settings for backward compatibility
|
|
202
214
|
if "max_workers" in execution_settings:
|
|
203
215
|
config["max_workers"] = execution_settings["max_workers"]
|
|
204
|
-
logger.debug(
|
|
216
|
+
logger.debug(
|
|
217
|
+
f"Set top-level max_workers: {execution_settings['max_workers']}"
|
|
218
|
+
)
|
|
205
219
|
if "timeout" in execution_settings:
|
|
206
220
|
config["timeout"] = execution_settings["timeout"]
|
|
207
221
|
logger.debug(f"Set top-level timeout: {execution_settings['timeout']}")
|
|
208
|
-
|
|
222
|
+
|
|
209
223
|
# Initialize execution settings if None
|
|
210
224
|
if config["execution"] is None:
|
|
211
225
|
config["execution"] = {}
|
|
212
226
|
logger.debug("Initialized empty execution settings")
|
|
213
|
-
|
|
227
|
+
|
|
214
228
|
# Merge with existing execution settings
|
|
215
229
|
config["execution"] = {**config["execution"], **execution_settings}
|
|
216
|
-
logger.debug(
|
|
230
|
+
logger.debug(
|
|
231
|
+
f"Merged execution settings in nested structure: {config['execution']}"
|
|
232
|
+
)
|
|
217
233
|
|
|
218
234
|
def reset(self) -> None:
|
|
219
235
|
"""Reset the configuration to default values.
|
|
@@ -233,56 +249,66 @@ class ConfigManager:
|
|
|
233
249
|
value: Value to set
|
|
234
250
|
"""
|
|
235
251
|
with self._lock:
|
|
236
|
-
parts = key.split(
|
|
252
|
+
parts = key.split(".")
|
|
237
253
|
current = self._config.model_dump()
|
|
238
254
|
target = current
|
|
239
255
|
logger.debug(f"Setting config value - Key: {key}, Value: {value}")
|
|
240
256
|
logger.debug(f"Current config state: {current}")
|
|
241
|
-
|
|
257
|
+
|
|
242
258
|
# Handle execution settings at top level
|
|
243
|
-
if parts[0] ==
|
|
244
|
-
if parts[1] in [
|
|
259
|
+
if parts[0] == "execution" and len(parts) == 2:
|
|
260
|
+
if parts[1] in ["max_workers", "timeout"]:
|
|
245
261
|
target[parts[1]] = value
|
|
246
|
-
logger.debug(
|
|
262
|
+
logger.debug(
|
|
263
|
+
f"Setting top-level execution parameter: {parts[1]} = {value}"
|
|
264
|
+
)
|
|
247
265
|
else:
|
|
248
266
|
# Create execution dictionary if needed
|
|
249
|
-
if
|
|
250
|
-
target[
|
|
251
|
-
target[
|
|
252
|
-
logger.debug(
|
|
267
|
+
if "execution" not in target:
|
|
268
|
+
target["execution"] = {}
|
|
269
|
+
target["execution"][parts[1]] = value
|
|
270
|
+
logger.debug(
|
|
271
|
+
f"Setting nested execution parameter: {parts[1]} = {value}"
|
|
272
|
+
)
|
|
253
273
|
else:
|
|
254
274
|
# Initialize nested configuration objects if needed
|
|
255
|
-
if parts[0] ==
|
|
256
|
-
target[
|
|
257
|
-
logger.debug(
|
|
275
|
+
if parts[0] == "rate_limiting" and target["rate_limiting"] is None:
|
|
276
|
+
target["rate_limiting"] = {"rate": 1000, "interval": "minute"}
|
|
277
|
+
logger.debug(
|
|
278
|
+
f"Initialized rate_limiting config: {target['rate_limiting']}"
|
|
279
|
+
)
|
|
258
280
|
|
|
259
281
|
# Navigate to the nested location
|
|
260
282
|
for part in parts[:-1]:
|
|
261
283
|
if part not in target:
|
|
262
284
|
target[part] = {}
|
|
263
285
|
target = target[part]
|
|
264
|
-
logger.debug(
|
|
286
|
+
logger.debug(
|
|
287
|
+
f"Navigating to nested key: {part}, Current target: {target}"
|
|
288
|
+
)
|
|
265
289
|
|
|
266
290
|
# Set the value
|
|
267
291
|
target[parts[-1]] = value
|
|
268
292
|
logger.debug(f"Set final value at {parts[-1]}: {value}")
|
|
269
|
-
|
|
293
|
+
|
|
270
294
|
logger.debug(f"Updated config state: {current}")
|
|
271
295
|
self._config = PyarallelConfig.from_dict(current)
|
|
272
296
|
logger.debug(f"Final config after update: {self._config}")
|
|
273
297
|
|
|
274
298
|
def get(self, key: str, default: Any = None) -> Any:
|
|
275
|
-
parts = key.split(
|
|
299
|
+
parts = key.split(".")
|
|
276
300
|
current = self._config.model_dump()
|
|
277
301
|
logger.debug(f"Getting config value - Key: {key}")
|
|
278
302
|
logger.debug(f"Current config state: {current}")
|
|
279
303
|
|
|
280
304
|
# Handle execution settings specially
|
|
281
|
-
if parts[0] ==
|
|
282
|
-
if current[
|
|
305
|
+
if parts[0] == "execution":
|
|
306
|
+
if current["execution"] is None:
|
|
283
307
|
# Check if the value exists at top level
|
|
284
|
-
if len(parts) == 2 and parts[1] in [
|
|
285
|
-
logger.debug(
|
|
308
|
+
if len(parts) == 2 and parts[1] in ["max_workers", "timeout"]:
|
|
309
|
+
logger.debug(
|
|
310
|
+
f"Retrieving top-level {parts[1]} value: {current[parts[1]]}"
|
|
311
|
+
)
|
|
286
312
|
return current[parts[1]]
|
|
287
313
|
logger.debug("Execution config is None, returning default")
|
|
288
314
|
return default
|
|
@@ -339,4 +365,4 @@ class ConfigManager:
|
|
|
339
365
|
"""
|
|
340
366
|
with self._lock:
|
|
341
367
|
updates = {"monitoring": kwargs}
|
|
342
|
-
self.update_config(updates)
|
|
368
|
+
self.update_config(updates)
|