pyarallel 0.1.0__tar.gz → 0.1.1__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.
@@ -33,6 +33,7 @@ ENV/
33
33
  *.swo
34
34
 
35
35
  # Test
36
+ .coverage.*
36
37
  .coverage
37
38
  htmlcov/
38
39
  .pytest_cache/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyarallel
3
- Version: 0.1.0
3
+ Version: 0.1.1
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
@@ -280,4 +280,61 @@ Contributions are welcome! Please check out our [Contributing Guide](CONTRIBUTIN
280
280
 
281
281
  ## License
282
282
 
283
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
283
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
284
+
285
+ ## Configuration
286
+
287
+ Pyarallel provides a flexible configuration system that allows you to customize its behavior globally or per-function:
288
+
289
+ ### Basic Configuration
290
+
291
+ ```python
292
+ from pyarallel import ConfigManager
293
+
294
+ # Get the global configuration manager
295
+ config = ConfigManager.get_instance()
296
+
297
+ # Update configuration
298
+ config.update_config({
299
+ "max_workers": 8,
300
+ "timeout": 60.0,
301
+ "debug": True
302
+ })
303
+ ```
304
+
305
+ ### Configuration Options
306
+
307
+ - **Execution Settings**
308
+ - `max_workers`: Maximum number of worker processes/threads (default: 4)
309
+ - `timeout`: Default timeout for parallel operations in seconds (default: 30.0)
310
+
311
+ - **Resource Management**
312
+ - `memory_limit`: Memory limit per worker in bytes (default: None)
313
+ - `cpu_affinity`: Enable CPU affinity for workers (default: False)
314
+
315
+ - **Logging and Debugging**
316
+ - `debug`: Enable debug mode (default: False)
317
+ - `log_level`: Logging level (default: "INFO")
318
+
319
+ ### Environment Variables
320
+
321
+ You can configure Pyarallel using environment variables with the `PYARALLEL_` prefix:
322
+
323
+ ```bash
324
+ PYARALLEL_MAX_WORKERS=4
325
+ PYARALLEL_TIMEOUT=60.0
326
+ PYARALLEL_DEBUG=true
327
+ ```
328
+
329
+ ### Configuration Files
330
+
331
+ Load configuration from JSON, YAML, or TOML files:
332
+
333
+ ```python
334
+ from pyarallel import PyarallelConfig
335
+
336
+ # Load from file
337
+ config = PyarallelConfig.from_file("pyarallel.yaml")
338
+
339
+ # Convert to dictionary
340
+ config_dict = config.to_dict()
@@ -241,4 +241,61 @@ Contributions are welcome! Please check out our [Contributing Guide](CONTRIBUTIN
241
241
 
242
242
  ## License
243
243
 
244
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
244
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
245
+
246
+ ## Configuration
247
+
248
+ Pyarallel provides a flexible configuration system that allows you to customize its behavior globally or per-function:
249
+
250
+ ### Basic Configuration
251
+
252
+ ```python
253
+ from pyarallel import ConfigManager
254
+
255
+ # Get the global configuration manager
256
+ config = ConfigManager.get_instance()
257
+
258
+ # Update configuration
259
+ config.update_config({
260
+ "max_workers": 8,
261
+ "timeout": 60.0,
262
+ "debug": True
263
+ })
264
+ ```
265
+
266
+ ### Configuration Options
267
+
268
+ - **Execution Settings**
269
+ - `max_workers`: Maximum number of worker processes/threads (default: 4)
270
+ - `timeout`: Default timeout for parallel operations in seconds (default: 30.0)
271
+
272
+ - **Resource Management**
273
+ - `memory_limit`: Memory limit per worker in bytes (default: None)
274
+ - `cpu_affinity`: Enable CPU affinity for workers (default: False)
275
+
276
+ - **Logging and Debugging**
277
+ - `debug`: Enable debug mode (default: False)
278
+ - `log_level`: Logging level (default: "INFO")
279
+
280
+ ### Environment Variables
281
+
282
+ You can configure Pyarallel using environment variables with the `PYARALLEL_` prefix:
283
+
284
+ ```bash
285
+ PYARALLEL_MAX_WORKERS=4
286
+ PYARALLEL_TIMEOUT=60.0
287
+ PYARALLEL_DEBUG=true
288
+ ```
289
+
290
+ ### Configuration Files
291
+
292
+ Load configuration from JSON, YAML, or TOML files:
293
+
294
+ ```python
295
+ from pyarallel import PyarallelConfig
296
+
297
+ # Load from file
298
+ config = PyarallelConfig.from_file("pyarallel.yaml")
299
+
300
+ # Convert to dictionary
301
+ config_dict = config.to_dict()
@@ -0,0 +1,123 @@
1
+ # Configuration System Implementation Tasks
2
+
3
+ ## 1. Core Configuration System [✅]
4
+ - [✅] Create `config.py` with dataclass-based configuration
5
+ - [✅] Implement singleton config manager
6
+ - [✅] Add type validation for config values
7
+ - [✅] Add merge strategy for partial updates
8
+
9
+ **Test Cases:**
10
+ ```python
11
+ def test_config_defaults():
12
+ """Default values are set correctly"""
13
+
14
+ def test_config_validation():
15
+ """Invalid values raise proper exceptions"""
16
+
17
+ def test_partial_update():
18
+ """Partial config updates don't affect other values"""
19
+ ```
20
+
21
+ ## 2. Environment Variables Support [✅]
22
+ - [] Add env var parsing in config manager
23
+ - [] Implement type coercion (str -> proper type)
24
+ - [] Add prefix support (PYARALLEL_*)
25
+ - [] Support complex values (lists, dicts via JSON)
26
+
27
+ **Test Cases:**
28
+ ```python
29
+ def test_env_var_loading():
30
+ """Config loads from environment variables"""
31
+
32
+ def test_env_var_types():
33
+ """Environment variables are properly typed"""
34
+
35
+ def test_env_var_prefix():
36
+ """Only PYARALLEL_* vars are loaded"""
37
+ ```
38
+
39
+ ## 3. Runtime Configuration API [✅]
40
+ - [] Add global `set()` method
41
+ - [] Add category-specific setters
42
+ - [] Add value getters with dot notation
43
+ - [] Implement config validation hooks
44
+
45
+ **Test Cases:**
46
+ ```python
47
+ def test_global_set():
48
+ """Global config can be set"""
49
+
50
+ def test_category_set():
51
+ """Category-specific settings work"""
52
+
53
+ def test_dot_notation():
54
+ """Dot notation access works for nested config"""
55
+ ```
56
+
57
+ ## 4. Decorator Integration [✅]
58
+ - [] Update parallel decorator to use config
59
+ - [] Add config override in decorator
60
+ - [] Implement inheritance rules
61
+ - [] Add runtime config warnings
62
+
63
+ **Test Cases:**
64
+ ```python
65
+ def test_decorator_defaults():
66
+ """Decorator uses global defaults"""
67
+
68
+ def test_decorator_override():
69
+ """Decorator args override global config"""
70
+
71
+ def test_runtime_warnings():
72
+ """Warnings for problematic configs"""
73
+ ```
74
+
75
+ ## 5. Documentation [ ]
76
+ - [ ] Add configuration section to README
77
+ - [ ] Document all environment variables
78
+ - [ ] Add configuration examples
79
+ - [ ] Document best practices
80
+
81
+ ## Configuration Schema
82
+ ```python
83
+ {
84
+ "execution": {
85
+ "default_max_workers": int,
86
+ "default_executor_type": str,
87
+ "default_batch_size": Optional[int],
88
+ "prewarm_pools": bool
89
+ },
90
+ "rate_limiting": {
91
+ "default_rate": Optional[float],
92
+ "default_interval": str,
93
+ "burst_tolerance": float
94
+ },
95
+ "error_handling": {
96
+ "max_retries": int,
97
+ "retry_backoff": float,
98
+ "fail_fast": bool
99
+ },
100
+ "monitoring": {
101
+ "enable_logging": bool,
102
+ "log_level": str,
103
+ "sentry_dsn": Optional[str],
104
+ "metrics_enabled": bool
105
+ }
106
+ }
107
+ ```
108
+
109
+ ## Environment Variables
110
+ ```
111
+ PYARALLEL_MAX_WORKERS=4
112
+ PYARALLEL_EXECUTOR_TYPE=thread
113
+ PYARALLEL_BATCH_SIZE=10
114
+ PYARALLEL_RATE_LIMIT=100/minute
115
+ PYARALLEL_FAIL_FAST=true
116
+ PYARALLEL_SENTRY_DSN=https://...
117
+ ```
118
+
119
+ ## Progress Tracking
120
+ - ✅ Task completed
121
+ - 🚧 In progress
122
+ - ❌ Blocked
123
+ - [ ] Not started
@@ -0,0 +1,110 @@
1
+ """Configuration module for pyarallel.
2
+
3
+ This module provides a configuration system using Pydantic for schema validation,
4
+ with support for loading configurations from different sources and thread-safe operations.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Any, Dict, Optional, Union
9
+
10
+ from pydantic import BaseModel, Field
11
+ from pydantic_core import ValidationError
12
+
13
+
14
+ class PyarallelConfig(BaseModel):
15
+ """Base configuration class for pyarallel.
16
+
17
+ This class defines the configuration schema and provides validation
18
+ using Pydantic. It includes default values and type hints for all settings.
19
+ """
20
+
21
+ # Execution settings
22
+ max_workers: int = Field(
23
+ default=4,
24
+ description="Maximum number of worker processes/threads",
25
+ ge=1
26
+ )
27
+ timeout: float = Field(
28
+ default=30.0,
29
+ description="Default timeout for parallel operations in seconds",
30
+ ge=0
31
+ )
32
+
33
+ # Resource management
34
+ memory_limit: Optional[int] = Field(
35
+ default=None,
36
+ description="Memory limit per worker in bytes, None for no limit"
37
+ )
38
+ cpu_affinity: bool = Field(
39
+ default=False,
40
+ description="Enable CPU affinity for workers"
41
+ )
42
+
43
+ # Logging and debugging
44
+ debug: bool = Field(
45
+ default=False,
46
+ description="Enable debug mode"
47
+ )
48
+ log_level: str = Field(
49
+ default="INFO",
50
+ description="Logging level"
51
+ )
52
+
53
+ @classmethod
54
+ def from_dict(cls, config_dict: Dict[str, Any]) -> "PyarallelConfig":
55
+ """Create a configuration instance from a dictionary.
56
+
57
+ Args:
58
+ config_dict: Dictionary containing configuration values
59
+
60
+ Returns:
61
+ PyarallelConfig instance
62
+
63
+ Raises:
64
+ ValidationError: If the configuration is invalid
65
+ """
66
+ return cls(**config_dict)
67
+
68
+ @classmethod
69
+ def from_file(cls, config_path: Union[str, Path]) -> "PyarallelConfig":
70
+ """Load configuration from a file (JSON, YAML, or TOML).
71
+
72
+ Args:
73
+ config_path: Path to the configuration file
74
+
75
+ Returns:
76
+ PyarallelConfig instance
77
+
78
+ Raises:
79
+ FileNotFoundError: If the configuration file doesn't exist
80
+ ValidationError: If the configuration is invalid
81
+ """
82
+ config_path = Path(config_path)
83
+ if not config_path.exists():
84
+ raise FileNotFoundError(f"Configuration file not found: {config_path}")
85
+
86
+ # Load based on file extension
87
+ if config_path.suffix == ".json":
88
+ import json
89
+ with open(config_path) as f:
90
+ config_dict = json.load(f)
91
+ elif config_path.suffix in (".yml", ".yaml"):
92
+ import yaml
93
+ with open(config_path) as f:
94
+ config_dict = yaml.safe_load(f)
95
+ elif config_path.suffix == ".toml":
96
+ import toml
97
+ with open(config_path) as f:
98
+ config_dict = toml.load(f)
99
+ else:
100
+ raise ValueError(f"Unsupported configuration file format: {config_path.suffix}")
101
+
102
+ return cls.from_dict(config_dict)
103
+
104
+ def to_dict(self) -> Dict[str, Any]:
105
+ """Convert configuration to a dictionary.
106
+
107
+ Returns:
108
+ Dictionary representation of the configuration
109
+ """
110
+ return self.model_dump()
@@ -0,0 +1,90 @@
1
+ """Configuration manager module for pyarallel.
2
+
3
+ This module provides a thread-safe singleton configuration manager that handles
4
+ all configuration operations and maintains the global configuration state.
5
+ """
6
+
7
+ from threading import Lock
8
+ from typing import Any, Dict, Optional, Type, TypeVar
9
+
10
+ from .config import PyarallelConfig
11
+
12
+ T = TypeVar('T', bound='ConfigManager')
13
+
14
+
15
+ class ConfigManager:
16
+ """Singleton configuration manager for pyarallel.
17
+
18
+ This class ensures thread-safe access to configuration settings and provides
19
+ methods for updating and retrieving configuration values.
20
+ """
21
+ _instance: Optional[T] = None
22
+ _lock: Lock = Lock()
23
+ _config: Optional[PyarallelConfig] = None
24
+
25
+ def __new__(cls: Type[T]) -> T:
26
+ if not cls._instance:
27
+ with cls._lock:
28
+ if not cls._instance:
29
+ cls._instance = super().__new__(cls)
30
+ cls._instance._config = PyarallelConfig()
31
+ return cls._instance
32
+
33
+ @classmethod
34
+ def get_instance(cls: Type[T]) -> T:
35
+ """Get the singleton instance of the configuration manager.
36
+
37
+ Returns:
38
+ ConfigManager: The singleton instance
39
+ """
40
+ return cls()
41
+
42
+ def get_config(self) -> PyarallelConfig:
43
+ """Get the current configuration.
44
+
45
+ Returns:
46
+ PyarallelConfig: The current configuration
47
+ """
48
+ return self._config
49
+
50
+ def update_config(self, updates: Dict[str, Any]) -> None:
51
+ """Update the configuration with new values.
52
+
53
+ This method implements a merge strategy that allows partial updates
54
+ while preserving existing values.
55
+
56
+ Args:
57
+ updates: Dictionary containing the configuration updates
58
+ """
59
+ with self._lock:
60
+ current_config = self._config.model_dump()
61
+ # Handle nested execution structure
62
+ if "execution" in updates:
63
+ for key, value in updates["execution"].items():
64
+ updates[key] = value
65
+ del updates["execution"]
66
+
67
+ # Validate max_workers before merging
68
+ if "max_workers" in updates and updates["max_workers"] < 1:
69
+ updates["max_workers"] = 1
70
+
71
+ merged_config = self._deep_merge(current_config, updates)
72
+ self._config = PyarallelConfig.from_dict(merged_config)
73
+
74
+ def _deep_merge(self, base: Dict[str, Any], updates: Dict[str, Any]) -> Dict[str, Any]:
75
+ """Recursively merge two dictionaries.
76
+
77
+ Args:
78
+ base: The base dictionary
79
+ updates: The dictionary with updates
80
+
81
+ Returns:
82
+ Dict[str, Any]: The merged dictionary
83
+ """
84
+ result = base.copy()
85
+ for key, value in updates.items():
86
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
87
+ result[key] = self._deep_merge(result[key], value)
88
+ else:
89
+ result[key] = value
90
+ return result
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyarallel"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  description = "A powerful parallel execution library for Python"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="pyarallel",
8
- version="0.1.0",
8
+ version="0.1.1",
9
9
  author="Mehmet Oner Yalcin",
10
10
  author_email="oneryalcin@gmail.com",
11
11
  description="A powerful parallel execution library for Python",
@@ -0,0 +1,69 @@
1
+ """Test cases for the configuration manager module.
2
+
3
+ This module contains tests for the singleton pattern, thread safety,
4
+ and merge strategy of the configuration manager.
5
+ """
6
+
7
+ import threading
8
+ from concurrent.futures import ThreadPoolExecutor
9
+
10
+ import pytest
11
+
12
+ from pyarallel.config_manager import ConfigManager
13
+
14
+
15
+ def test_singleton_pattern():
16
+ """Test that ConfigManager maintains singleton pattern."""
17
+ config1 = ConfigManager()
18
+ config2 = ConfigManager()
19
+ assert config1 is config2
20
+
21
+
22
+ def test_thread_safety():
23
+ """Test thread-safe access to configuration."""
24
+ def update_config(i):
25
+ manager = ConfigManager()
26
+ manager.update_config({"max_workers": i})
27
+ return manager.get_config().max_workers
28
+
29
+ with ThreadPoolExecutor(max_workers=4) as executor:
30
+ results = list(executor.map(update_config, range(10)))
31
+
32
+ # Verify the final state is consistent
33
+ assert ConfigManager().get_config().max_workers == 9
34
+
35
+
36
+ def test_partial_update():
37
+ """Test that partial updates don't affect other values."""
38
+ manager = ConfigManager()
39
+
40
+ # Initial state
41
+ initial_config = manager.get_config()
42
+ initial_timeout = initial_config.timeout
43
+
44
+ # Update only max_workers
45
+ manager.update_config({"max_workers": 8})
46
+
47
+ # Verify timeout remains unchanged
48
+ updated_config = manager.get_config()
49
+ assert updated_config.timeout == initial_timeout
50
+ assert updated_config.max_workers == 8
51
+
52
+
53
+ def test_nested_merge():
54
+ """Test deep merging of nested configuration values."""
55
+ manager = ConfigManager()
56
+
57
+ # Update with nested structure
58
+ update = {
59
+ "execution": {
60
+ "max_workers": 8,
61
+ "timeout": 60.0
62
+ }
63
+ }
64
+
65
+ manager.update_config(update)
66
+ config = manager.get_config()
67
+
68
+ assert config.max_workers == 8
69
+ assert config.timeout == 60.0
@@ -0,0 +1,7 @@
1
+ version = 1
2
+ requires-python = ">=3.12"
3
+
4
+ [[package]]
5
+ name = "pyarallel"
6
+ version = "0.1.0"
7
+ source = { editable = "." }
pyarallel-0.1.0/hello.py DELETED
@@ -1,6 +0,0 @@
1
- def main():
2
- print("Hello from pyarallel!")
3
-
4
-
5
- if __name__ == "__main__":
6
- main()