puffinflow 2.dev0__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.
- puffinflow/__init__.py +132 -0
- puffinflow/core/__init__.py +110 -0
- puffinflow/core/agent/__init__.py +320 -0
- puffinflow/core/agent/base.py +1635 -0
- puffinflow/core/agent/checkpoint.py +50 -0
- puffinflow/core/agent/context.py +521 -0
- puffinflow/core/agent/decorators/__init__.py +90 -0
- puffinflow/core/agent/decorators/builder.py +454 -0
- puffinflow/core/agent/decorators/flexible.py +714 -0
- puffinflow/core/agent/decorators/inspection.py +144 -0
- puffinflow/core/agent/dependencies.py +57 -0
- puffinflow/core/agent/scheduling/__init__.py +21 -0
- puffinflow/core/agent/scheduling/builder.py +160 -0
- puffinflow/core/agent/scheduling/exceptions.py +35 -0
- puffinflow/core/agent/scheduling/inputs.py +137 -0
- puffinflow/core/agent/scheduling/parser.py +209 -0
- puffinflow/core/agent/scheduling/scheduler.py +413 -0
- puffinflow/core/agent/state.py +141 -0
- puffinflow/core/config.py +62 -0
- puffinflow/core/coordination/__init__.py +137 -0
- puffinflow/core/coordination/agent_group.py +359 -0
- puffinflow/core/coordination/agent_pool.py +629 -0
- puffinflow/core/coordination/agent_team.py +577 -0
- puffinflow/core/coordination/coordinator.py +720 -0
- puffinflow/core/coordination/deadlock.py +1759 -0
- puffinflow/core/coordination/fluent_api.py +421 -0
- puffinflow/core/coordination/primitives.py +478 -0
- puffinflow/core/coordination/rate_limiter.py +520 -0
- puffinflow/core/observability/__init__.py +47 -0
- puffinflow/core/observability/agent.py +139 -0
- puffinflow/core/observability/alerting.py +73 -0
- puffinflow/core/observability/config.py +127 -0
- puffinflow/core/observability/context.py +88 -0
- puffinflow/core/observability/core.py +147 -0
- puffinflow/core/observability/decorators.py +105 -0
- puffinflow/core/observability/events.py +71 -0
- puffinflow/core/observability/interfaces.py +196 -0
- puffinflow/core/observability/metrics.py +137 -0
- puffinflow/core/observability/tracing.py +209 -0
- puffinflow/core/reliability/__init__.py +27 -0
- puffinflow/core/reliability/bulkhead.py +96 -0
- puffinflow/core/reliability/circuit_breaker.py +149 -0
- puffinflow/core/reliability/leak_detector.py +122 -0
- puffinflow/core/resources/__init__.py +77 -0
- puffinflow/core/resources/allocation.py +790 -0
- puffinflow/core/resources/pool.py +645 -0
- puffinflow/core/resources/quotas.py +567 -0
- puffinflow/core/resources/requirements.py +217 -0
- puffinflow/version.py +21 -0
- puffinflow-2.dev0.dist-info/METADATA +334 -0
- puffinflow-2.dev0.dist-info/RECORD +55 -0
- puffinflow-2.dev0.dist-info/WHEEL +5 -0
- puffinflow-2.dev0.dist-info/entry_points.txt +3 -0
- puffinflow-2.dev0.dist-info/licenses/LICENSE +21 -0
- puffinflow-2.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Resource requirements and types for PuffinFlow resource management.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from enum import Flag
|
|
8
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..agent.state import Priority
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ResourceType(Flag):
|
|
17
|
+
"""Resource type flags for specifying required resources."""
|
|
18
|
+
|
|
19
|
+
NONE = 0
|
|
20
|
+
CPU = 1
|
|
21
|
+
MEMORY = 2
|
|
22
|
+
IO = 4
|
|
23
|
+
NETWORK = 8
|
|
24
|
+
GPU = 16
|
|
25
|
+
|
|
26
|
+
# Convenience combination for all resource types
|
|
27
|
+
ALL = CPU | MEMORY | IO | NETWORK | GPU
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ResourceRequirements:
|
|
32
|
+
"""
|
|
33
|
+
Specifies resource requirements for agent states.
|
|
34
|
+
|
|
35
|
+
This class defines the computational resources needed by an agent state,
|
|
36
|
+
including CPU, memory, I/O, network, and GPU resources, along with
|
|
37
|
+
priority and timeout specifications.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
cpu_units: float = 1.0
|
|
41
|
+
memory_mb: float = 100.0
|
|
42
|
+
io_weight: float = 1.0
|
|
43
|
+
network_weight: float = 1.0
|
|
44
|
+
gpu_units: float = 0.0
|
|
45
|
+
priority_boost: int = 0
|
|
46
|
+
timeout: Optional[float] = None
|
|
47
|
+
resource_types: ResourceType = ResourceType.ALL
|
|
48
|
+
|
|
49
|
+
def __post_init__(self) -> None:
|
|
50
|
+
"""Ensure resource_types is always a valid ResourceType enum."""
|
|
51
|
+
try:
|
|
52
|
+
# Validate that it supports bitwise operations
|
|
53
|
+
try:
|
|
54
|
+
test_result = self.resource_types & ResourceType.CPU
|
|
55
|
+
logger.debug(
|
|
56
|
+
f"Bitwise test successful: {self.resource_types} & CPU = {test_result}"
|
|
57
|
+
)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.warning(f"Bitwise operation failed: {e}")
|
|
60
|
+
self._auto_determine_resource_types()
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
logger.error(f"Error in ResourceRequirements.__post_init__: {e}")
|
|
64
|
+
# Fallback to a safe default
|
|
65
|
+
object.__setattr__(self, "resource_types", ResourceType.ALL)
|
|
66
|
+
|
|
67
|
+
def _auto_determine_resource_types(self) -> None:
|
|
68
|
+
"""Auto-determine resource_types from individual resource amounts."""
|
|
69
|
+
resource_types = ResourceType.NONE
|
|
70
|
+
|
|
71
|
+
if getattr(self, "cpu_units", 0) > 0:
|
|
72
|
+
resource_types |= ResourceType.CPU
|
|
73
|
+
if getattr(self, "memory_mb", 0) > 0:
|
|
74
|
+
resource_types |= ResourceType.MEMORY
|
|
75
|
+
if getattr(self, "io_weight", 0) > 0:
|
|
76
|
+
resource_types |= ResourceType.IO
|
|
77
|
+
if getattr(self, "network_weight", 0) > 0:
|
|
78
|
+
resource_types |= ResourceType.NETWORK
|
|
79
|
+
if getattr(self, "gpu_units", 0) > 0:
|
|
80
|
+
resource_types |= ResourceType.GPU
|
|
81
|
+
|
|
82
|
+
# If no specific resources are requested, default to ALL
|
|
83
|
+
if resource_types == ResourceType.NONE:
|
|
84
|
+
resource_types = ResourceType.ALL
|
|
85
|
+
|
|
86
|
+
# Use object.__setattr__ for dataclass
|
|
87
|
+
object.__setattr__(self, "resource_types", resource_types)
|
|
88
|
+
logger.info(f"Auto-determined resource_types: {resource_types}")
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def priority(self) -> "Priority":
|
|
92
|
+
"""Get priority level based on priority_boost."""
|
|
93
|
+
from ..agent.state import Priority
|
|
94
|
+
|
|
95
|
+
if self.priority_boost <= 0:
|
|
96
|
+
return Priority.LOW
|
|
97
|
+
elif self.priority_boost == 1:
|
|
98
|
+
return Priority.NORMAL
|
|
99
|
+
elif self.priority_boost == 2:
|
|
100
|
+
return Priority.HIGH
|
|
101
|
+
else: # priority_boost >= 3
|
|
102
|
+
return Priority.CRITICAL
|
|
103
|
+
|
|
104
|
+
@priority.setter
|
|
105
|
+
def priority(self, value: Union["Priority", int]) -> None:
|
|
106
|
+
"""Set priority level, updating priority_boost accordingly."""
|
|
107
|
+
from ..agent.state import Priority
|
|
108
|
+
|
|
109
|
+
if isinstance(value, Priority):
|
|
110
|
+
self.priority_boost = value.value
|
|
111
|
+
elif isinstance(value, int):
|
|
112
|
+
self.priority_boost = value
|
|
113
|
+
else:
|
|
114
|
+
raise TypeError(f"Priority must be Priority enum or int, got {type(value)}")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Resource attribute mapping for get_resource_amount function
|
|
118
|
+
RESOURCE_ATTRIBUTE_MAPPING = {
|
|
119
|
+
ResourceType.CPU: "cpu_units",
|
|
120
|
+
ResourceType.MEMORY: "memory_mb",
|
|
121
|
+
ResourceType.IO: "io_weight",
|
|
122
|
+
ResourceType.NETWORK: "network_weight",
|
|
123
|
+
ResourceType.GPU: "gpu_units",
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def safe_check_resource_type(
|
|
128
|
+
requirements: ResourceRequirements, resource_type: ResourceType
|
|
129
|
+
) -> bool:
|
|
130
|
+
"""
|
|
131
|
+
Safely check if a resource type is requested in requirements.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
requirements: The ResourceRequirements object
|
|
135
|
+
resource_type: The ResourceType to check
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
True if the resource type is requested, False otherwise
|
|
139
|
+
"""
|
|
140
|
+
try:
|
|
141
|
+
# First try the normal bitwise operation
|
|
142
|
+
return bool(requirements.resource_types & resource_type)
|
|
143
|
+
except TypeError as e:
|
|
144
|
+
logger.warning(
|
|
145
|
+
f"Bitwise operation failed: {e}. Falling back to value comparison."
|
|
146
|
+
)
|
|
147
|
+
try:
|
|
148
|
+
# Fallback to value-based comparison
|
|
149
|
+
return bool(requirements.resource_types.value & resource_type.value)
|
|
150
|
+
except Exception as e2:
|
|
151
|
+
logger.error(
|
|
152
|
+
f"Fallback comparison also failed: {e2}. Assuming resource is requested."
|
|
153
|
+
)
|
|
154
|
+
# If all else fails, check if the individual resource amount is > 0
|
|
155
|
+
# Direct attribute access to avoid circular dependency
|
|
156
|
+
if resource_type in RESOURCE_ATTRIBUTE_MAPPING:
|
|
157
|
+
attr_name = RESOURCE_ATTRIBUTE_MAPPING[resource_type]
|
|
158
|
+
return getattr(requirements, attr_name, 0.0) > 0
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_resource_amount(
|
|
163
|
+
requirements: ResourceRequirements, resource_type: ResourceType
|
|
164
|
+
) -> float:
|
|
165
|
+
"""
|
|
166
|
+
Get the amount of a specific resource type from requirements.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
requirements: The ResourceRequirements object
|
|
170
|
+
resource_type: The ResourceType to get the amount for
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
The amount of the specified resource type, or 0.0 if the resource type is not enabled
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
ValueError: If resource_type is not a single resource type
|
|
177
|
+
"""
|
|
178
|
+
if resource_type == ResourceType.NONE:
|
|
179
|
+
return 0.0
|
|
180
|
+
|
|
181
|
+
# Check if the resource type is enabled in the requirements
|
|
182
|
+
if not safe_check_resource_type(requirements, resource_type):
|
|
183
|
+
return 0.0
|
|
184
|
+
|
|
185
|
+
if resource_type == ResourceType.ALL:
|
|
186
|
+
# Return sum of all resource amounts
|
|
187
|
+
total = 0.0
|
|
188
|
+
for rt, attr in RESOURCE_ATTRIBUTE_MAPPING.items():
|
|
189
|
+
if safe_check_resource_type(requirements, rt):
|
|
190
|
+
total += getattr(requirements, attr, 0.0)
|
|
191
|
+
return total
|
|
192
|
+
|
|
193
|
+
# Check if it's a single resource type (power of 2, excluding NONE)
|
|
194
|
+
if (
|
|
195
|
+
resource_type.value > 0
|
|
196
|
+
and (resource_type.value & (resource_type.value - 1)) == 0
|
|
197
|
+
and resource_type in RESOURCE_ATTRIBUTE_MAPPING
|
|
198
|
+
):
|
|
199
|
+
attr_name = RESOURCE_ATTRIBUTE_MAPPING[resource_type]
|
|
200
|
+
return getattr(requirements, attr_name, 0.0)
|
|
201
|
+
|
|
202
|
+
# Handle combined resource types by summing individual types
|
|
203
|
+
total = 0.0
|
|
204
|
+
for rt, attr in RESOURCE_ATTRIBUTE_MAPPING.items():
|
|
205
|
+
try:
|
|
206
|
+
if (resource_type.value & rt.value) != 0 and safe_check_resource_type(
|
|
207
|
+
requirements, rt
|
|
208
|
+
):
|
|
209
|
+
total += getattr(requirements, attr, 0.0)
|
|
210
|
+
except TypeError:
|
|
211
|
+
# Fallback if 'in' operation fails
|
|
212
|
+
if (resource_type.value & rt.value) != 0 and safe_check_resource_type(
|
|
213
|
+
requirements, rt
|
|
214
|
+
):
|
|
215
|
+
total += getattr(requirements, attr, 0.0)
|
|
216
|
+
|
|
217
|
+
return total
|
puffinflow/version.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '2.dev0'
|
|
21
|
+
__version_tuple__ = version_tuple = (2, 'dev0')
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: puffinflow
|
|
3
|
+
Version: 2.dev0
|
|
4
|
+
Summary: A powerful Python workflow orchestration framework with advanced resource management and observability
|
|
5
|
+
Author-email: Mohamed Ahmed <mohamed.ahmed.4894@gmail.com>
|
|
6
|
+
Maintainer-email: Mohamed Ahmed <mohamed.ahmed.4894@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/m-ahmed-elbeskeri/puffinflow-main
|
|
9
|
+
Project-URL: Documentation, https://puffinflow.readthedocs.io
|
|
10
|
+
Project-URL: Repository, https://github.com/m-ahmed-elbeskeri/puffinflow-main.git
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/m-ahmed-elbeskeri/puffinflow-main/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/m-ahmed-elbeskeri/puffinflow-main/blob/main/CHANGELOG.md
|
|
13
|
+
Project-URL: Funding, https://github.com/sponsors/m-ahmed-elbeskeri
|
|
14
|
+
Keywords: workflow,orchestration,async,state-management,resource-allocation,task-execution,distributed-systems,monitoring,observability,tracing,metrics,coordination
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
27
|
+
Classifier: Topic :: System :: Monitoring
|
|
28
|
+
Classifier: Topic :: System :: Systems Administration
|
|
29
|
+
Classifier: Framework :: AsyncIO
|
|
30
|
+
Classifier: Typing :: Typed
|
|
31
|
+
Requires-Python: >=3.9
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Requires-Dist: pydantic<3.0.0,>=2.0.0
|
|
35
|
+
Requires-Dist: pydantic-settings<3.0.0,>=2.0.0
|
|
36
|
+
Requires-Dist: structlog>=23.1.0
|
|
37
|
+
Requires-Dist: typing-extensions>=4.8.0; python_version < "3.11"
|
|
38
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
39
|
+
Requires-Dist: prometheus-client>=0.19.0
|
|
40
|
+
Requires-Dist: psutil>=5.9.0
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
45
|
+
Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
|
|
46
|
+
Requires-Dist: pytest-benchmark>=4.0.0; extra == "dev"
|
|
47
|
+
Requires-Dist: pytest-timeout>=2.2.0; extra == "dev"
|
|
48
|
+
Requires-Dist: pytest-xdist>=3.5.0; extra == "dev"
|
|
49
|
+
Requires-Dist: black>=23.12.0; extra == "dev"
|
|
50
|
+
Requires-Dist: ruff>=0.1.8; extra == "dev"
|
|
51
|
+
Requires-Dist: mypy>=1.8.0; extra == "dev"
|
|
52
|
+
Requires-Dist: types-psutil>=5.9.0; extra == "dev"
|
|
53
|
+
Requires-Dist: pre-commit>=3.6.0; extra == "dev"
|
|
54
|
+
Requires-Dist: tox>=4.11.0; extra == "dev"
|
|
55
|
+
Requires-Dist: coverage[toml]>=7.3.0; extra == "dev"
|
|
56
|
+
Provides-Extra: docs
|
|
57
|
+
Requires-Dist: sphinx>=7.1.0; extra == "docs"
|
|
58
|
+
Requires-Dist: sphinx-rtd-theme>=2.0.0; extra == "docs"
|
|
59
|
+
Requires-Dist: sphinx-autodoc-typehints>=1.25.0; extra == "docs"
|
|
60
|
+
Requires-Dist: myst-parser>=2.0.0; extra == "docs"
|
|
61
|
+
Requires-Dist: sphinxcontrib-asyncio>=0.3.0; extra == "docs"
|
|
62
|
+
Provides-Extra: cli
|
|
63
|
+
Requires-Dist: typer[all]>=0.9.0; extra == "cli"
|
|
64
|
+
Requires-Dist: rich>=13.7.0; extra == "cli"
|
|
65
|
+
Requires-Dist: click>=8.1.0; extra == "cli"
|
|
66
|
+
Provides-Extra: observability
|
|
67
|
+
Requires-Dist: prometheus-client>=0.19.0; extra == "observability"
|
|
68
|
+
Requires-Dist: psutil>=5.9.0; extra == "observability"
|
|
69
|
+
Requires-Dist: opentelemetry-api<2.0.0,>=1.23.0; extra == "observability"
|
|
70
|
+
Requires-Dist: opentelemetry-sdk<2.0.0,>=1.23.0; extra == "observability"
|
|
71
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc<2.0.0,>=1.23.0; extra == "observability"
|
|
72
|
+
Requires-Dist: opentelemetry-exporter-jaeger-thrift<2.0.0,>=1.21.0; extra == "observability"
|
|
73
|
+
Requires-Dist: opentelemetry-instrumentation-asyncio<1.0.0,>=0.44b0; extra == "observability"
|
|
74
|
+
Requires-Dist: opentelemetry-instrumentation-logging<1.0.0,>=0.44b0; extra == "observability"
|
|
75
|
+
Requires-Dist: aiohttp>=3.9.0; extra == "observability"
|
|
76
|
+
Requires-Dist: httpx>=0.26.0; extra == "observability"
|
|
77
|
+
Requires-Dist: aiosmtplib>=3.0.0; extra == "observability"
|
|
78
|
+
Requires-Dist: deprecated>=1.2.6; extra == "observability"
|
|
79
|
+
Provides-Extra: monitoring
|
|
80
|
+
Requires-Dist: prometheus-client>=0.19.0; extra == "monitoring"
|
|
81
|
+
Requires-Dist: opentelemetry-api<2.0.0,>=1.23.0; extra == "monitoring"
|
|
82
|
+
Requires-Dist: opentelemetry-sdk<2.0.0,>=1.23.0; extra == "monitoring"
|
|
83
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc<2.0.0,>=1.23.0; extra == "monitoring"
|
|
84
|
+
Requires-Dist: opentelemetry-instrumentation-asyncio<1.0.0,>=0.44b0; extra == "monitoring"
|
|
85
|
+
Provides-Extra: integrations
|
|
86
|
+
Requires-Dist: fastapi>=0.108.0; extra == "integrations"
|
|
87
|
+
Requires-Dist: celery>=5.3.0; extra == "integrations"
|
|
88
|
+
Requires-Dist: kubernetes>=28.0.0; extra == "integrations"
|
|
89
|
+
Requires-Dist: redis>=5.0.0; extra == "integrations"
|
|
90
|
+
Requires-Dist: httpx>=0.26.0; extra == "integrations"
|
|
91
|
+
Requires-Dist: psutil>=5.9.0; extra == "integrations"
|
|
92
|
+
Provides-Extra: performance
|
|
93
|
+
Requires-Dist: pytest-benchmark>=4.0.0; extra == "performance"
|
|
94
|
+
Requires-Dist: memory-profiler>=0.61.0; extra == "performance"
|
|
95
|
+
Requires-Dist: line-profiler>=4.1.0; extra == "performance"
|
|
96
|
+
Requires-Dist: py-spy>=0.3.14; extra == "performance"
|
|
97
|
+
Provides-Extra: security
|
|
98
|
+
Requires-Dist: bandit>=1.7.5; extra == "security"
|
|
99
|
+
Requires-Dist: safety>=2.3.0; extra == "security"
|
|
100
|
+
Requires-Dist: semgrep>=1.45.0; extra == "security"
|
|
101
|
+
Provides-Extra: all
|
|
102
|
+
Requires-Dist: typer[all]>=0.9.0; extra == "all"
|
|
103
|
+
Requires-Dist: rich>=13.7.0; extra == "all"
|
|
104
|
+
Requires-Dist: click>=8.1.0; extra == "all"
|
|
105
|
+
Requires-Dist: prometheus-client>=0.19.0; extra == "all"
|
|
106
|
+
Requires-Dist: psutil>=5.9.0; extra == "all"
|
|
107
|
+
Requires-Dist: opentelemetry-api<2.0.0,>=1.23.0; extra == "all"
|
|
108
|
+
Requires-Dist: opentelemetry-sdk<2.0.0,>=1.23.0; extra == "all"
|
|
109
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc<2.0.0,>=1.23.0; extra == "all"
|
|
110
|
+
Requires-Dist: opentelemetry-exporter-jaeger-thrift<2.0.0,>=1.21.0; extra == "all"
|
|
111
|
+
Requires-Dist: opentelemetry-instrumentation-asyncio<1.0.0,>=0.44b0; extra == "all"
|
|
112
|
+
Requires-Dist: opentelemetry-instrumentation-logging<1.0.0,>=0.44b0; extra == "all"
|
|
113
|
+
Requires-Dist: aiohttp>=3.9.0; extra == "all"
|
|
114
|
+
Requires-Dist: httpx>=0.26.0; extra == "all"
|
|
115
|
+
Requires-Dist: aiosmtplib>=3.0.0; extra == "all"
|
|
116
|
+
Requires-Dist: fastapi>=0.108.0; extra == "all"
|
|
117
|
+
Requires-Dist: celery>=5.3.0; extra == "all"
|
|
118
|
+
Requires-Dist: kubernetes>=28.0.0; extra == "all"
|
|
119
|
+
Requires-Dist: redis>=5.0.0; extra == "all"
|
|
120
|
+
Dynamic: license-file
|
|
121
|
+
|
|
122
|
+
# 🐧 PuffinFlow
|
|
123
|
+
|
|
124
|
+
[](https://badge.fury.io/py/puffinflow)
|
|
125
|
+
[](https://pypi.org/project/puffinflow/)
|
|
126
|
+
[](https://opensource.org/licenses/MIT)
|
|
127
|
+
|
|
128
|
+
**PuffinFlow is a powerful Python framework for developers who need to rapidly prototype LLM workflows and seamlessly transition them to production-ready systems.**
|
|
129
|
+
|
|
130
|
+
Perfect for AI engineers, data scientists, and backend developers who want to focus on workflow logic rather than infrastructure complexity.
|
|
131
|
+
|
|
132
|
+
## Get started
|
|
133
|
+
|
|
134
|
+
Install PuffinFlow:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install puffinflow
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Then, create an agent using the state decorator:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from puffinflow import Agent, state
|
|
144
|
+
|
|
145
|
+
class DataProcessor(Agent):
|
|
146
|
+
@state(cpu=2.0, memory=1024.0)
|
|
147
|
+
async def fetch_data(self, context):
|
|
148
|
+
"""Fetch data from external source."""
|
|
149
|
+
data = await get_external_data()
|
|
150
|
+
context.set_variable("raw_data", data)
|
|
151
|
+
return "validate_data" if data else "error"
|
|
152
|
+
|
|
153
|
+
@state(cpu=1.0, memory=512.0)
|
|
154
|
+
async def validate_data(self, context):
|
|
155
|
+
"""Validate the fetched data."""
|
|
156
|
+
data = context.get_variable("raw_data")
|
|
157
|
+
if self.is_valid(data):
|
|
158
|
+
return "process_data"
|
|
159
|
+
return "error"
|
|
160
|
+
|
|
161
|
+
@state(cpu=4.0, memory=2048.0)
|
|
162
|
+
async def process_data(self, context):
|
|
163
|
+
"""Process the validated data."""
|
|
164
|
+
data = context.get_variable("raw_data")
|
|
165
|
+
result = await self.transform_data(data)
|
|
166
|
+
context.set_output("processed_data", result)
|
|
167
|
+
return "complete"
|
|
168
|
+
|
|
169
|
+
# Run the agent
|
|
170
|
+
agent = DataProcessor("data-processor")
|
|
171
|
+
result = await agent.run()
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
For more information, see the [Quickstart](https://puffinflow.readthedocs.io/en/latest/guides/quickstart.html). Or, to learn how to build complex multi-agent workflows with coordination and observability, see the [Advanced Examples](./examples/).
|
|
175
|
+
|
|
176
|
+
## Core benefits
|
|
177
|
+
|
|
178
|
+
PuffinFlow bridges the gap between quick prototyping and production deployment. Start building your LLM workflow in minutes, then scale to production without rewriting code:
|
|
179
|
+
|
|
180
|
+
**Prototype to Production**: Begin with simple agents and seamlessly add resource management, observability, and coordination as your needs grow.
|
|
181
|
+
|
|
182
|
+
**Intelligent resource management**: Automatically allocate and manage CPU, memory, and other resources based on state requirements with built-in quotas and limits.
|
|
183
|
+
|
|
184
|
+
**Zero-config observability**: Comprehensive monitoring with OpenTelemetry integration, custom metrics, distributed tracing, and real-time alerting that works out of the box.
|
|
185
|
+
|
|
186
|
+
**Built-in reliability**: Circuit breakers, bulkheads, and leak detection ensure robust operation under various failure conditions without additional configuration.
|
|
187
|
+
|
|
188
|
+
**Agent coordination**: Scale from single agents to complex multi-agent workflows with teams, pools, and orchestrators using the same simple API.
|
|
189
|
+
|
|
190
|
+
**Production performance**: Achieve 567,000+ operations/second with sub-millisecond latency, designed for real-world production workloads.
|
|
191
|
+
|
|
192
|
+
## PuffinFlow's ecosystem
|
|
193
|
+
|
|
194
|
+
While PuffinFlow can be used standalone, it integrates with popular Python frameworks and tools:
|
|
195
|
+
|
|
196
|
+
**FastAPI & Django** — Seamlessly integrate PuffinFlow agents into web applications with built-in async support and resource management.
|
|
197
|
+
|
|
198
|
+
**Celery & Redis** — Enhance existing task queues with stateful workflows, advanced coordination, and comprehensive monitoring.
|
|
199
|
+
|
|
200
|
+
**OpenTelemetry** — Full observability stack with distributed tracing, metrics collection, and integration with monitoring platforms like Prometheus and Jaeger.
|
|
201
|
+
|
|
202
|
+
**Kubernetes** — Production-ready deployment with container orchestration, automatic scaling, and cloud-native observability.
|
|
203
|
+
|
|
204
|
+
## Additional resources
|
|
205
|
+
|
|
206
|
+
- **[Documentation](https://puffinflow.readthedocs.io/)**: Complete guides and API reference
|
|
207
|
+
- **[Examples](./examples/)**: Ready-to-run code examples for common patterns
|
|
208
|
+
- **[Advanced Guides](./docs/source/guides/)**: Deep dives into resource management, coordination, and observability
|
|
209
|
+
- **[Benchmarks](./benchmarks/)**: Performance metrics and optimization guides
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Real-World Examples
|
|
214
|
+
|
|
215
|
+
### 🔥 Image Processing Pipeline
|
|
216
|
+
```python
|
|
217
|
+
class ImageProcessor(Agent):
|
|
218
|
+
@state(cpu=2.0, memory=1024.0)
|
|
219
|
+
async def resize_image(self, context):
|
|
220
|
+
image_url = context.get_variable("image_url")
|
|
221
|
+
resized = await resize_image(image_url, size=(800, 600))
|
|
222
|
+
context.set_variable("resized_image", resized)
|
|
223
|
+
return "add_watermark"
|
|
224
|
+
|
|
225
|
+
@state(cpu=1.0, memory=512.0)
|
|
226
|
+
async def add_watermark(self, context):
|
|
227
|
+
image = context.get_variable("resized_image")
|
|
228
|
+
watermarked = await add_watermark(image)
|
|
229
|
+
context.set_variable("final_image", watermarked)
|
|
230
|
+
return "upload_to_storage"
|
|
231
|
+
|
|
232
|
+
@state(cpu=1.0, memory=256.0)
|
|
233
|
+
async def upload_to_storage(self, context):
|
|
234
|
+
image = context.get_variable("final_image")
|
|
235
|
+
url = await upload_to_s3(image)
|
|
236
|
+
context.set_output("result_url", url)
|
|
237
|
+
return "complete"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### 🤖 ML Model Training
|
|
241
|
+
```python
|
|
242
|
+
class MLTrainer(Agent):
|
|
243
|
+
@state(cpu=8.0, memory=4096.0)
|
|
244
|
+
async def train_model(self, context):
|
|
245
|
+
dataset = context.get_variable("dataset")
|
|
246
|
+
model = await train_neural_network(dataset)
|
|
247
|
+
context.set_variable("model", model)
|
|
248
|
+
context.set_output("accuracy", model.accuracy)
|
|
249
|
+
|
|
250
|
+
if model.accuracy > 0.9:
|
|
251
|
+
return "deploy_model"
|
|
252
|
+
return "retrain_with_more_data"
|
|
253
|
+
|
|
254
|
+
@state(cpu=2.0, memory=1024.0)
|
|
255
|
+
async def deploy_model(self, context):
|
|
256
|
+
model = context.get_variable("model")
|
|
257
|
+
await deploy_to_production(model)
|
|
258
|
+
context.set_output("deployment_status", "success")
|
|
259
|
+
return "complete"
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 🔄 Multi-Agent Coordination
|
|
263
|
+
```python
|
|
264
|
+
from puffinflow import create_team, AgentTeam
|
|
265
|
+
|
|
266
|
+
# Coordinate multiple agents
|
|
267
|
+
email_team = create_team([
|
|
268
|
+
EmailValidator("validator"),
|
|
269
|
+
EmailProcessor("processor"),
|
|
270
|
+
EmailTracker("tracker")
|
|
271
|
+
])
|
|
272
|
+
|
|
273
|
+
# Execute with built-in coordination
|
|
274
|
+
result = await email_team.execute_parallel()
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 🎯 Use Cases
|
|
280
|
+
|
|
281
|
+
**📊 Data Pipelines** — Build resilient ETL workflows with automatic retries, resource management, and comprehensive monitoring.
|
|
282
|
+
|
|
283
|
+
**🤖 ML Workflows** — Orchestrate training pipelines, model deployment, and inference workflows with checkpointing and observability.
|
|
284
|
+
|
|
285
|
+
**🌐 Microservices** — Coordinate distributed services with circuit breakers, bulkheads, and intelligent load balancing.
|
|
286
|
+
|
|
287
|
+
**⚡ Event Processing** — Handle high-throughput event streams with backpressure control and automatic scaling.
|
|
288
|
+
|
|
289
|
+
## 📊 Performance
|
|
290
|
+
|
|
291
|
+
PuffinFlow is built for production workloads with excellent performance characteristics:
|
|
292
|
+
|
|
293
|
+
### Core Performance Metrics
|
|
294
|
+
- **567,000+ operations/second** for basic agent operations
|
|
295
|
+
- **27,000+ operations/second** for complex data processing
|
|
296
|
+
- **1,100+ operations/second** for CPU-intensive tasks
|
|
297
|
+
- **Sub-millisecond** state transition latency (0.00-1.97ms range)
|
|
298
|
+
|
|
299
|
+
### Benchmark Results (Latest)
|
|
300
|
+
| Operation Type | Avg Latency | Throughput | Use Case |
|
|
301
|
+
|---|---|---|---|
|
|
302
|
+
| Agent State Transitions | 0.00ms | 567,526 ops/s | Basic workflow steps |
|
|
303
|
+
| Data Processing | 0.04ms | 27,974 ops/s | ETL operations |
|
|
304
|
+
| Resource Management | 0.01ms | 104,719 ops/s | Memory/CPU allocation |
|
|
305
|
+
| Async Coordination | 1.23ms | 811 ops/s | Multi-agent workflows |
|
|
306
|
+
| CPU-Intensive Tasks | 0.91ms | 1,100 ops/s | ML training steps |
|
|
307
|
+
|
|
308
|
+
*Benchmarks run on: Linux WSL2, 16 cores, 3.68GB RAM, Python 3.12*
|
|
309
|
+
|
|
310
|
+
[View detailed benchmarks →](./benchmarks/)
|
|
311
|
+
|
|
312
|
+
## 🤝 Community & Support
|
|
313
|
+
|
|
314
|
+
- **[🐛 Issues](https://github.com/m-ahmed-elbeskeri/puffinflow/issues)** — Bug reports and feature requests
|
|
315
|
+
- **[💬 Discussions](https://github.com/m-ahmed-elbeskeri/puffinflow/discussions)** — Community Q&A
|
|
316
|
+
- **[📧 Email](mailto:mohamed.ahmed.4894@gmail.com)** — Direct contact for support
|
|
317
|
+
|
|
318
|
+
## Acknowledgements
|
|
319
|
+
|
|
320
|
+
PuffinFlow is inspired by workflow orchestration principles and builds upon the Python async ecosystem. The framework emphasizes practical workflow management with production-ready features. PuffinFlow is built by Mohamed Ahmed, designed for developers who need reliable, observable, and scalable workflow orchestration.
|
|
321
|
+
|
|
322
|
+
## 📜 License
|
|
323
|
+
|
|
324
|
+
PuffinFlow is released under the [MIT License](LICENSE). Free for commercial and personal use.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
<div align="center">
|
|
329
|
+
|
|
330
|
+
**Ready to build production-ready workflows?**
|
|
331
|
+
|
|
332
|
+
[Get Started →](https://puffinflow.readthedocs.io/en/latest/guides/quickstart.html) | [View Examples →](./examples/) | [Join Community →](https://github.com/m-ahmed-elbeskeri/puffinflow/discussions)
|
|
333
|
+
|
|
334
|
+
</div>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
puffinflow/__init__.py,sha256=UnuscpJdtuwzB9i9m8zbZ-oQWLJWgM-4_TQeXGCy-b0,2623
|
|
2
|
+
puffinflow/version.py,sha256=h4IuJsX5bQcAuFjGgJgLghhjV7tMYP_AObu5mYfUkFo,514
|
|
3
|
+
puffinflow/core/__init__.py,sha256=ky3V0ulDLGExVSBGtCOSw91ulX3TohSDs1czkYIZHb0,3584
|
|
4
|
+
puffinflow/core/config.py,sha256=00ks86HgALGfBGWKoHarEHd8st1RKn-B3D_L5ilShq8,1952
|
|
5
|
+
puffinflow/core/agent/__init__.py,sha256=AFrdpn1UV6fUh19dRg5IDN555nwk8CVXDf8O1usMRMI,8522
|
|
6
|
+
puffinflow/core/agent/base.py,sha256=uXlEkoRcOYf-xwydu9uWOkv0NSUDZUAcSw_alRNPo-c,62386
|
|
7
|
+
puffinflow/core/agent/checkpoint.py,sha256=E3OZMogJ0omMtZI_coeTjRvu8TmqS8DmUMGpqGUclnY,1492
|
|
8
|
+
puffinflow/core/agent/context.py,sha256=KJnrcePY5HZMQCYTD8wrlKF_uJx6gOorUYMDBZoNcfw,18477
|
|
9
|
+
puffinflow/core/agent/dependencies.py,sha256=6LgR98bFs8BFt6CIRwA_auKm_7deOkGCam6pd3dxJYQ,1978
|
|
10
|
+
puffinflow/core/agent/state.py,sha256=2b7nFTxTM-oEKfNkYhThHF-IKvBc0aNTKnd6hehRK0I,3517
|
|
11
|
+
puffinflow/core/agent/decorators/__init__.py,sha256=zyL2L9TFGukFB3KwaQZNY6DW_PrwssAdIYsW4aZHN9Q,2073
|
|
12
|
+
puffinflow/core/agent/decorators/builder.py,sha256=XZ7D7tYRIVO-qZdY0vS1KXdv6q85Qr4zj23zASfm7Eg,14656
|
|
13
|
+
puffinflow/core/agent/decorators/flexible.py,sha256=nGzMU3la6qnu9ePCXNTm7fu5XL0na_CrHZvQXiFctls,25921
|
|
14
|
+
puffinflow/core/agent/decorators/inspection.py,sha256=4oxEjcanc_wz8bW9O2eBlEACCMviag9r0aqxQD5mRUI,4844
|
|
15
|
+
puffinflow/core/agent/scheduling/__init__.py,sha256=JFJgqhrMa0aid_xHgZkCgoJbPEHL58CVsTugELxZmp4,627
|
|
16
|
+
puffinflow/core/agent/scheduling/builder.py,sha256=gNYDNaqYezb-8NbEbXlFEt_7zFZ_K76MODGZF4_EMeA,5077
|
|
17
|
+
puffinflow/core/agent/scheduling/exceptions.py,sha256=3IAWd3QAJ7uh20xBbmla7QGNVbNXYT2V-BzKbzVbE64,1013
|
|
18
|
+
puffinflow/core/agent/scheduling/inputs.py,sha256=o_CHJchV504fQs3yO1POgMIcRAgeU2-er31M5ba5KpQ,4490
|
|
19
|
+
puffinflow/core/agent/scheduling/parser.py,sha256=wovziX-KqdvhozncJOEw0OfEqIaDsPLvaNdTLsfqXV4,7373
|
|
20
|
+
puffinflow/core/agent/scheduling/scheduler.py,sha256=y-2erkDoguy4Yl87tXG1TaVw6ZhQvrNadrxugiYUWMA,13771
|
|
21
|
+
puffinflow/core/coordination/__init__.py,sha256=pGYDH0QJt9z3YUivhTSKmNCE3QC1XugCoGs1J2OE12A,2921
|
|
22
|
+
puffinflow/core/coordination/agent_group.py,sha256=OtEzLBkGRbV6z9gBbrN6kD58hA6vqYhJ55L5U54iCpU,11962
|
|
23
|
+
puffinflow/core/coordination/agent_pool.py,sha256=x6ldfzvcymIUXavbmLMlfC71_J2h2J36H7_goifpSEA,22772
|
|
24
|
+
puffinflow/core/coordination/agent_team.py,sha256=gQAVTuELq0W3lcwPMKAcyCl5AAySFCE8YUn7WxB-BFk,19516
|
|
25
|
+
puffinflow/core/coordination/coordinator.py,sha256=s2OCb6D1Jv49qghD--lEHupr6fgqTCrSiW7o4WoCf-k,26788
|
|
26
|
+
puffinflow/core/coordination/deadlock.py,sha256=QfOBJzzYoSx7_AIE2Pd_-IUcel6WGnr_n0u-z2Yr43c,64950
|
|
27
|
+
puffinflow/core/coordination/fluent_api.py,sha256=kY8hzY0Mp8qcvO7O7soHTdnEVbMiw09lW8PvGdL2suc,14896
|
|
28
|
+
puffinflow/core/coordination/primitives.py,sha256=nIEJkAp4qRpiQWMUYAfQDu988rcc7q_fidoDWYbMmvk,16634
|
|
29
|
+
puffinflow/core/coordination/rate_limiter.py,sha256=0-xHAl8n2-BdrqRGwRX18dG73faXhqSyrzMuB5FTPaE,17692
|
|
30
|
+
puffinflow/core/observability/__init__.py,sha256=7ldAw323pmhmbv9P4ctYlpETHhniMohv-GD93gbhcSY,1045
|
|
31
|
+
puffinflow/core/observability/agent.py,sha256=bUjGbQmfYaSfFLwQ6zRKG2VceXKxAtQliRG7pGBpH7w,5087
|
|
32
|
+
puffinflow/core/observability/alerting.py,sha256=YtFp5y9LhEHkzkihMPWTb7WFUUf6bvpbMefKmMD4H34,2123
|
|
33
|
+
puffinflow/core/observability/config.py,sha256=EScIvDHJuKiyR0FH11eLXpyND_oyAzG-Kgkx1zJXCEw,4296
|
|
34
|
+
puffinflow/core/observability/context.py,sha256=vRxwyRDDzuW59Bn7RteSltTf-tgaJevwme5qsj-Amks,2932
|
|
35
|
+
puffinflow/core/observability/core.py,sha256=LKo_d8O3G-Nh1RZsjWIN9th7x6bLYQ--icoPWfa19SM,4654
|
|
36
|
+
puffinflow/core/observability/decorators.py,sha256=XOiQgsEfc3HDA8FETR1vq1M_xICs0qLZ41TEBOwOPhU,3820
|
|
37
|
+
puffinflow/core/observability/events.py,sha256=4SWO3DjTjRT4yp3SiDQSPqr5TkDeMxryRiL8ljSkE2A,2525
|
|
38
|
+
puffinflow/core/observability/interfaces.py,sha256=LPofmDp4wUw6-Ls8MgI2SnaQj-FgwCV8GXptw405ep0,4897
|
|
39
|
+
puffinflow/core/observability/metrics.py,sha256=tJnCuREaKwL1wXnpF2qjoAmmJKk4S58pVNR9_QSd620,5139
|
|
40
|
+
puffinflow/core/observability/tracing.py,sha256=xWyTy0uA_CDElORkrdfmBTsDP5EUZmNsehkLjCvz6sY,7403
|
|
41
|
+
puffinflow/core/reliability/__init__.py,sha256=N48Hn2JcJQgM3JzMpBU9bdTWFihH7PU7ZXLCpFx_zvk,684
|
|
42
|
+
puffinflow/core/reliability/bulkhead.py,sha256=JE7G6W2V3gggeZ9pipO_W4h_y4YhYOH23a-uvHAAsbs,2895
|
|
43
|
+
puffinflow/core/reliability/circuit_breaker.py,sha256=jWiWowX08ORoJ7OSgBZuN0BLIgLhthYe_lA2vWUp1rU,4735
|
|
44
|
+
puffinflow/core/reliability/leak_detector.py,sha256=z31u2lvO82EfE6eyzAhFOsFaA6MPctsIhKKT0kEtwuA,4089
|
|
45
|
+
puffinflow/core/resources/__init__.py,sha256=wpq1n83Hjr1Ua1DsSlvaZcZbwLvYmhlrb-phBQ038gU,1722
|
|
46
|
+
puffinflow/core/resources/allocation.py,sha256=mZGBLvk6bmOOU8X_I-U2_6rAS1SrFCYztzx513seZ1E,28851
|
|
47
|
+
puffinflow/core/resources/pool.py,sha256=7KkC66c2Y04yUsq6-1O4dl8MGOdrCUzFyGof9kv61uo,24394
|
|
48
|
+
puffinflow/core/resources/quotas.py,sha256=1377rf00LqIVZuuwq7jxat8aFLwZ8i5WgZSD8766-CA,18159
|
|
49
|
+
puffinflow/core/resources/requirements.py,sha256=7J07JVIqa1sL9v-xCC-aGQjS2Vtk9Fwliajp8fOJNRE,7428
|
|
50
|
+
puffinflow-2.dev0.dist-info/licenses/LICENSE,sha256=_iTSa1NhJHVNdoCwmNMrS3nVUTwnEcflmb8zcplrm_g,1067
|
|
51
|
+
puffinflow-2.dev0.dist-info/METADATA,sha256=v1v_K-GCrqfo06io3jZF3cXPJyj8nNGLUoeOA_3NEIU,15172
|
|
52
|
+
puffinflow-2.dev0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
53
|
+
puffinflow-2.dev0.dist-info/entry_points.txt,sha256=kFo-w_DMoIAb-mlBdkE4Rp1sBNQ-rosnI0yFLdTfYaI,113
|
|
54
|
+
puffinflow-2.dev0.dist-info/top_level.txt,sha256=76A_vroRA-4hO5-lugaSXM8BSKuWS5f4uIlqPKohiZs,11
|
|
55
|
+
puffinflow-2.dev0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 PuffinFlow
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
puffinflow
|