integration-automation-patterns 0.1.0__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.
@@ -0,0 +1,15 @@
1
+ """Reference patterns for reliable enterprise integration and workflow automation."""
2
+
3
+ from .event_envelope import DeliveryStatus, EventEnvelope, RetryPolicy
4
+ from .sync_boundary import RecordAuthority, SyncBoundary, SyncConflict
5
+
6
+ __all__ = [
7
+ # Event handling
8
+ "DeliveryStatus",
9
+ "EventEnvelope",
10
+ "RetryPolicy",
11
+ # Sync boundary
12
+ "RecordAuthority",
13
+ "SyncBoundary",
14
+ "SyncConflict",
15
+ ]
@@ -0,0 +1,206 @@
1
+ """
2
+ event_envelope.py — Reliable Event Handling for Enterprise Integration
3
+
4
+ Enterprise integration fails most often at the transport layer — not because
5
+ the business logic is wrong, but because events are dropped, duplicated, or
6
+ processed out of order when systems restart, network partitions occur, or
7
+ downstream services are temporarily unavailable.
8
+
9
+ This module provides the structural primitives for building reliable,
10
+ replay-safe event handling in enterprise integration workflows. The patterns
11
+ apply regardless of the message broker (Kafka, SQS, Azure Service Bus,
12
+ GCP Pub/Sub, RabbitMQ, MQ Series) or the enterprise platforms being integrated
13
+ (CRM, ERP, ITSM, custom services).
14
+
15
+ Design goals:
16
+ - Every event carries enough context to be replayed safely
17
+ - Retry behavior is explicit and bounded, not implicit
18
+ - Delivery status is inspectable without querying the broker
19
+ - Deduplication is structural (idempotency key), not a side effect of storage
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from dataclasses import dataclass, field
25
+ from datetime import datetime, timezone
26
+ from enum import Enum
27
+ from typing import Any
28
+
29
+
30
+ class DeliveryStatus(Enum):
31
+ """
32
+ Tracks the lifecycle of one event through the integration pipeline.
33
+
34
+ Events move forward through this lifecycle. An event that has reached
35
+ FAILED should not be silently dropped — it should be routed to a dead
36
+ letter queue or an explicit remediation path.
37
+ """
38
+ PENDING = "pending" # Created, not yet dispatched
39
+ DISPATCHED = "dispatched" # Sent to broker or downstream system
40
+ ACKNOWLEDGED = "acknowledged" # Downstream confirmed receipt
41
+ PROCESSED = "processed" # Business logic completed successfully
42
+ RETRYING = "retrying" # Failed at least once, within retry budget
43
+ FAILED = "failed" # Retry budget exhausted; requires intervention
44
+ SKIPPED = "skipped" # Intentionally not processed (e.g., duplicate)
45
+
46
+
47
+ @dataclass(slots=True)
48
+ class RetryPolicy:
49
+ """
50
+ Defines retry behavior for a failed event delivery attempt.
51
+
52
+ Explicit retry policies prevent two common failure modes:
53
+ 1. Infinite retry loops that mask persistent downstream failures
54
+ 2. Retry storms that overwhelm recovering services
55
+
56
+ Attributes:
57
+ max_attempts: Total number of delivery attempts allowed (including
58
+ the first). After this many attempts, the event moves to FAILED.
59
+ backoff_seconds: Base wait time between retries in seconds.
60
+ Actual wait = backoff_seconds * (2 ** attempt_number) when
61
+ exponential = True.
62
+ exponential: If True, use exponential backoff. If False, use
63
+ fixed-interval retry.
64
+ max_backoff_seconds: Upper bound on wait time when using exponential
65
+ backoff. Prevents unbounded delays.
66
+ """
67
+ max_attempts: int = 3
68
+ backoff_seconds: int = 30
69
+ exponential: bool = True
70
+ max_backoff_seconds: int = 3600
71
+
72
+ def wait_seconds_for_attempt(self, attempt_number: int) -> int:
73
+ """
74
+ Calculate the wait time before the given attempt number.
75
+
76
+ Args:
77
+ attempt_number: Zero-indexed attempt count (0 = first retry,
78
+ not the original attempt).
79
+
80
+ Returns:
81
+ Seconds to wait before attempting delivery again.
82
+ """
83
+ if not self.exponential:
84
+ return self.backoff_seconds
85
+ delay = self.backoff_seconds * (2 ** attempt_number)
86
+ return min(delay, self.max_backoff_seconds)
87
+
88
+ def is_exhausted(self, attempt_number: int) -> bool:
89
+ """Return True if the attempt budget is exhausted."""
90
+ return attempt_number >= self.max_attempts
91
+
92
+
93
+ @dataclass
94
+ class EventEnvelope:
95
+ """
96
+ A transport container for one unit of work in an enterprise integration flow.
97
+
98
+ An EventEnvelope carries the event payload alongside the metadata needed
99
+ to route, deduplicate, retry, and audit it. Separating envelope metadata
100
+ from business payload means integration infrastructure can handle
101
+ reliability concerns without touching business logic.
102
+
103
+ Design principle: The envelope must contain everything needed to replay
104
+ the event safely, without requiring coordination with the original sender.
105
+
106
+ Attributes:
107
+ event_id: Globally unique identifier for this event. Used as the
108
+ idempotency key — processing the same event_id twice should
109
+ produce the same result as processing it once.
110
+ event_type: A namespaced string identifying what happened.
111
+ Convention: "<domain>.<entity>.<action>" (e.g., "enrollment.student.updated")
112
+ source_system: Identifier for the system that originated the event.
113
+ Used for routing, filtering, and audit.
114
+ payload: The business data associated with the event. Keep payloads
115
+ small — prefer IDs + change summary over full record snapshots.
116
+ Full records should be fetched from the system of record at
117
+ processing time, not embedded in the event.
118
+ correlation_id: Links this event to a parent workflow, request, or
119
+ transaction. Enables tracing across system boundaries.
120
+ schema_version: Semantic version of the payload schema. Consumers
121
+ should reject events with unexpected versions rather than
122
+ silently misinterpreting the payload.
123
+ created_at: Wall clock time when the event was created. Stored in
124
+ UTC. Do not use for business ordering — use a sequence or
125
+ logical clock for that.
126
+ status: Current delivery status. Updated as the event moves through
127
+ the pipeline.
128
+ attempt_count: Number of delivery attempts made. Starts at 0,
129
+ incremented before each attempt.
130
+ retry_policy: Retry behavior for failed delivery attempts.
131
+ tags: Arbitrary key-value metadata for routing, filtering, or
132
+ observability. Not used in business logic.
133
+ """
134
+ event_id: str
135
+ event_type: str
136
+ source_system: str
137
+ payload: dict[str, Any]
138
+ correlation_id: str = ""
139
+ schema_version: str = "1.0"
140
+ created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
141
+ status: DeliveryStatus = DeliveryStatus.PENDING
142
+ attempt_count: int = 0
143
+ retry_policy: RetryPolicy = field(default_factory=RetryPolicy)
144
+ tags: dict[str, str] = field(default_factory=dict)
145
+
146
+ def mark_dispatched(self) -> None:
147
+ """Record that this event has been sent to the broker or downstream system."""
148
+ self.attempt_count += 1
149
+ self.status = DeliveryStatus.DISPATCHED
150
+
151
+ def mark_acknowledged(self) -> None:
152
+ """Record that the downstream system confirmed receipt."""
153
+ self.status = DeliveryStatus.ACKNOWLEDGED
154
+
155
+ def mark_processed(self) -> None:
156
+ """Record that business processing completed successfully."""
157
+ self.status = DeliveryStatus.PROCESSED
158
+
159
+ def mark_failed(self) -> None:
160
+ """
161
+ Record a failed delivery attempt and advance status accordingly.
162
+
163
+ If the retry budget is not exhausted, status becomes RETRYING.
164
+ If the budget is exhausted, status becomes FAILED.
165
+ """
166
+ if self.retry_policy.is_exhausted(self.attempt_count):
167
+ self.status = DeliveryStatus.FAILED
168
+ else:
169
+ self.status = DeliveryStatus.RETRYING
170
+
171
+ def mark_skipped(self, reason: str = "") -> None:
172
+ """Record that this event was intentionally skipped (e.g., duplicate)."""
173
+ self.status = DeliveryStatus.SKIPPED
174
+ if reason:
175
+ self.tags["skip_reason"] = reason
176
+
177
+ def next_retry_wait_seconds(self) -> int:
178
+ """
179
+ Return the wait time before the next delivery attempt.
180
+
181
+ Returns 0 if the event is not in a retryable state.
182
+ """
183
+ if self.status != DeliveryStatus.RETRYING:
184
+ return 0
185
+ return self.retry_policy.wait_seconds_for_attempt(self.attempt_count)
186
+
187
+ def is_terminal(self) -> bool:
188
+ """Return True if the event has reached a state that requires no further processing."""
189
+ return self.status in {
190
+ DeliveryStatus.PROCESSED,
191
+ DeliveryStatus.FAILED,
192
+ DeliveryStatus.SKIPPED,
193
+ }
194
+
195
+ def to_audit_line(self) -> str:
196
+ """Format a structured log line for integration observability systems."""
197
+ return (
198
+ f"[INTEGRATION_EVENT] event_id={self.event_id} "
199
+ f"type={self.event_type} "
200
+ f"source={self.source_system} "
201
+ f"status={self.status.value} "
202
+ f"attempts={self.attempt_count} "
203
+ f"correlation={self.correlation_id or 'none'} "
204
+ f"schema={self.schema_version} "
205
+ f"created={self.created_at.isoformat()}"
206
+ )
@@ -0,0 +1,214 @@
1
+ """
2
+ sync_boundary.py — System-of-Record Synchronization Boundaries
3
+
4
+ Bi-directional synchronization between enterprise systems (CRM and ERP,
5
+ for example) fails in predictable ways. The most common failure is
6
+ ambiguity about which system is authoritative for a given field when
7
+ both systems have updated the same record simultaneously.
8
+
9
+ This module provides the structural primitives for making synchronization
10
+ boundaries explicit. It does not implement a full sync engine — it
11
+ provides the objects needed to define authority, detect conflicts, and
12
+ route resolution decisions to the right system.
13
+
14
+ The pattern applies wherever two or more enterprise platforms must
15
+ maintain consistent state for overlapping records: CRM + ERP,
16
+ CRM + SIS (Student Information System), ERP + ITSM, and similar pairings.
17
+ Platform-agnostic — the primitives work regardless of vendor.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from dataclasses import dataclass, field
23
+ from datetime import datetime, timezone
24
+ from enum import Enum
25
+ from typing import Any
26
+
27
+
28
+ class RecordAuthority(Enum):
29
+ """
30
+ Defines which system is authoritative for a given field or record type.
31
+
32
+ In bi-directional sync, a field must have exactly one authoritative system.
33
+ Changes in the authoritative system propagate outward; changes in
34
+ non-authoritative systems are either rejected, queued for review, or
35
+ overwritten on next sync.
36
+
37
+ SHARED indicates that authority is determined per-field (see SyncBoundary)
38
+ rather than per-record. Use this sparingly — field-level authority
39
+ increases complexity.
40
+ """
41
+ SYSTEM_A = "system_a" # Source system A is authoritative
42
+ SYSTEM_B = "system_b" # Source system B is authoritative
43
+ SHARED = "shared" # Field-level authority defined separately
44
+ MANUAL = "manual" # Human review required before sync
45
+
46
+
47
+ @dataclass(slots=True)
48
+ class SyncConflict:
49
+ """
50
+ Represents a detected conflict between two systems' versions of a field.
51
+
52
+ A conflict occurs when both systems have modified the same field since
53
+ the last successful sync. The sync boundary policy determines how to
54
+ resolve it: prefer one system, defer to manual review, or apply a
55
+ merge function.
56
+
57
+ Attributes:
58
+ field_name: The name of the field where the conflict was detected.
59
+ value_a: The value in system A at detection time.
60
+ value_b: The value in system B at detection time.
61
+ last_sync_value: The agreed-upon value from the last successful sync.
62
+ If None, this is the first sync for this record.
63
+ detected_at: Wall-clock time when the conflict was detected.
64
+ record_id: Identifier of the record where the conflict exists.
65
+ system_a_modified_at: When system A last modified this field.
66
+ None if not available.
67
+ system_b_modified_at: When system B last modified this field.
68
+ None if not available.
69
+ """
70
+ field_name: str
71
+ value_a: Any
72
+ value_b: Any
73
+ record_id: str
74
+ last_sync_value: Any = None
75
+ detected_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
76
+ system_a_modified_at: datetime | None = None
77
+ system_b_modified_at: datetime | None = None
78
+
79
+ def last_writer(self) -> RecordAuthority | None:
80
+ """
81
+ Return which system made the most recent modification, based on
82
+ available modification timestamps.
83
+
84
+ Returns None if timestamps are unavailable for one or both systems.
85
+ """
86
+ if self.system_a_modified_at is None or self.system_b_modified_at is None:
87
+ return None
88
+ if self.system_a_modified_at > self.system_b_modified_at:
89
+ return RecordAuthority.SYSTEM_A
90
+ if self.system_b_modified_at > self.system_a_modified_at:
91
+ return RecordAuthority.SYSTEM_B
92
+ return None # Same timestamp — cannot determine last writer
93
+
94
+
95
+ @dataclass
96
+ class SyncBoundary:
97
+ """
98
+ Defines the synchronization contract between two systems for one record type.
99
+
100
+ A SyncBoundary specifies:
101
+ 1. Which fields exist in both systems
102
+ 2. Which system is authoritative for each field
103
+ 3. Which fields should never be synced (exclusions)
104
+
105
+ Keeping this as an explicit data object — rather than encoding it in
106
+ sync scripts — makes the contract readable, testable, and auditable.
107
+
108
+ Usage::
109
+
110
+ boundary = SyncBoundary(
111
+ record_type="contact",
112
+ system_a_id="crm",
113
+ system_b_id="erp",
114
+ field_authority={
115
+ "email": RecordAuthority.SYSTEM_A,
116
+ "billing_address": RecordAuthority.SYSTEM_B,
117
+ "preferred_name": RecordAuthority.SYSTEM_A,
118
+ "account_status": RecordAuthority.SYSTEM_B,
119
+ },
120
+ excluded_fields={"internal_notes", "crm_lead_score"},
121
+ )
122
+
123
+ # Check if a field is synced and which system wins
124
+ authority = boundary.authority_for("email") # RecordAuthority.SYSTEM_A
125
+ is_synced = boundary.is_synced("internal_notes") # False
126
+
127
+ Attributes:
128
+ record_type: A name for the type of record this boundary governs.
129
+ system_a_id: Identifier for the first system (e.g., "crm", "salesforce", "hubspot").
130
+ system_b_id: Identifier for the second system (e.g., "erp", "peoplesoft", "sap").
131
+ field_authority: Maps field names to which system is authoritative.
132
+ excluded_fields: Fields that are never synchronized, even if present
133
+ in both systems.
134
+ allow_system_b_override: If True, system B changes to SYSTEM_A-authoritative
135
+ fields are queued for manual review rather than silently discarded.
136
+ Default False (system A changes simply overwrite system B).
137
+ """
138
+ record_type: str
139
+ system_a_id: str
140
+ system_b_id: str
141
+ field_authority: dict[str, RecordAuthority] = field(default_factory=dict)
142
+ excluded_fields: set[str] = field(default_factory=set)
143
+ allow_system_b_override: bool = False
144
+
145
+ def authority_for(self, field_name: str) -> RecordAuthority | None:
146
+ """
147
+ Return the authoritative system for the given field.
148
+
149
+ Returns None if the field is not in the sync boundary (either
150
+ excluded or not mapped).
151
+ """
152
+ if field_name in self.excluded_fields:
153
+ return None
154
+ return self.field_authority.get(field_name)
155
+
156
+ def is_synced(self, field_name: str) -> bool:
157
+ """Return True if this field is part of the synchronization boundary."""
158
+ return field_name not in self.excluded_fields and field_name in self.field_authority
159
+
160
+ def synced_fields(self) -> list[str]:
161
+ """Return the list of field names that are actively synchronized."""
162
+ return [f for f in self.field_authority if f not in self.excluded_fields]
163
+
164
+ def fields_owned_by(self, authority: RecordAuthority) -> list[str]:
165
+ """Return all fields where the given system is authoritative."""
166
+ return [
167
+ f for f, auth in self.field_authority.items()
168
+ if auth == authority and f not in self.excluded_fields
169
+ ]
170
+
171
+ def detect_conflict(
172
+ self,
173
+ field_name: str,
174
+ value_a: Any,
175
+ value_b: Any,
176
+ record_id: str,
177
+ last_sync_value: Any = None,
178
+ system_a_modified_at: datetime | None = None,
179
+ system_b_modified_at: datetime | None = None,
180
+ ) -> SyncConflict | None:
181
+ """
182
+ Detect whether a conflict exists for the given field.
183
+
184
+ A conflict exists when both systems have a value different from the
185
+ last sync value, indicating both have modified the field since the
186
+ last sync. If only one system differs from the last sync value,
187
+ that system made the authoritative change and no conflict exists.
188
+
189
+ Returns None if no conflict is detected or if the field is not synced.
190
+ """
191
+ if not self.is_synced(field_name):
192
+ return None
193
+
194
+ # Both systems agree — no conflict
195
+ if value_a == value_b:
196
+ return None
197
+
198
+ # If we have a last sync value, check whether both systems diverged
199
+ if last_sync_value is not None:
200
+ a_changed = value_a != last_sync_value
201
+ b_changed = value_b != last_sync_value
202
+ if not (a_changed and b_changed):
203
+ # Only one system changed — not a conflict
204
+ return None
205
+
206
+ return SyncConflict(
207
+ field_name=field_name,
208
+ value_a=value_a,
209
+ value_b=value_b,
210
+ record_id=record_id,
211
+ last_sync_value=last_sync_value,
212
+ system_a_modified_at=system_a_modified_at,
213
+ system_b_modified_at=system_b_modified_at,
214
+ )
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: integration-automation-patterns
3
+ Version: 0.1.0
4
+ Summary: Reference patterns for reliable enterprise integration, workflow automation, and system-of-record synchronization.
5
+ Author: Ashutosh Rana
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ashutoshrana/integration-automation-patterns
8
+ Project-URL: Issues, https://github.com/ashutoshrana/integration-automation-patterns/issues
9
+ Project-URL: Changelog, https://github.com/ashutoshrana/integration-automation-patterns/blob/main/CHANGELOG.md
10
+ Keywords: enterprise-integration,event-driven,workflow-automation,crm,erp,idempotency,system-of-record,integration-patterns,enterprise-architecture
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: System :: Distributed Computing
20
+ Classifier: Topic :: Office/Business
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: test
25
+ Requires-Dist: pytest>=7.0; extra == "test"
26
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
27
+ Provides-Extra: lint
28
+ Requires-Dist: ruff>=0.4.0; extra == "lint"
29
+ Provides-Extra: typecheck
30
+ Requires-Dist: mypy>=1.0; extra == "typecheck"
31
+ Provides-Extra: dev
32
+ Requires-Dist: integration-automation-patterns[lint,test,typecheck]; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # integration-automation-patterns
36
+
37
+ [![CI](https://github.com/ashutoshrana/integration-automation-patterns/actions/workflows/ci.yml/badge.svg)](https://github.com/ashutoshrana/integration-automation-patterns/actions/workflows/ci.yml)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
39
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
40
+ [![PyPI](https://img.shields.io/pypi/v/integration-automation-patterns.svg)](https://pypi.org/project/integration-automation-patterns/)
41
+
42
+ Practical patterns for enterprise integration, workflow orchestration, and system-of-record synchronization in complex operating environments.
43
+
44
+ ## Why this repo exists
45
+
46
+ Enterprise modernization usually breaks down at the integration layer:
47
+ - brittle handoffs between systems
48
+ - inconsistent event handling
49
+ - weak retry and idempotency models
50
+ - workflow logic scattered across tools
51
+ - poor visibility into operational state
52
+
53
+ This repository is a public-safe reference for patterns that help teams build more reliable integration and automation systems. The patterns are platform-agnostic and cloud-agnostic — applicable across any combination of CRM, ERP, ITSM, and custom services, on any cloud environment (AWS, GCP, Azure, OCI) or on-premises.
54
+
55
+ ## Scope
56
+
57
+ This repo focuses on:
58
+ - event-driven integration patterns with explicit retry and idempotency models
59
+ - system-of-record synchronization with authority boundaries
60
+ - workflow orchestration and escalation boundaries
61
+ - observability for automation flows
62
+ - public-safe architecture notes for enterprise operations
63
+
64
+ The patterns do not assume any specific vendor, broker, or cloud platform.
65
+
66
+ ## Modules
67
+
68
+ - `event_envelope.py`
69
+ Reliable event transport with explicit delivery status, bounded retry policy,
70
+ and structured audit logging. Works with any message broker (Kafka, SQS,
71
+ Azure Service Bus, GCP Pub/Sub, RabbitMQ, IBM MQ, and others).
72
+
73
+ - `sync_boundary.py`
74
+ System-of-record synchronization contracts for bi-directional integration
75
+ between enterprise platforms. Explicit field-level authority assignment,
76
+ conflict detection, and exclusion management. Platform-agnostic.
77
+
78
+ ## Repository structure
79
+
80
+ - `src/integration_automation_patterns/`
81
+ - `event_envelope.py` — event transport with retry and audit
82
+ - `sync_boundary.py` — bi-directional sync authority boundaries
83
+ - `docs/architecture.md`
84
+ - `docs/implementation-note-01.md`
85
+ - `docs/adr/`
86
+ - `examples/event-flow.yaml`
87
+ - `CITATION.cff`
88
+ - `CONTRIBUTING.md`
89
+ - `GOVERNANCE.md`
90
+
91
+ ## Near-term roadmap
92
+
93
+ - add integration reliability ADRs
94
+ - add examples for retry-safe event handling across broker types
95
+ - document action logging and audit boundaries
96
+ - add workflow orchestration boundary patterns
97
+
98
+ ## Published notes
99
+
100
+ - implementation note: [`docs/implementation-note-01.md`](./docs/implementation-note-01.md)
101
+
102
+ ## Intended audience
103
+
104
+ - enterprise architects
105
+ - integration engineers
106
+ - workflow and automation operators
107
+ - platform teams responsible for system-of-record reliability across CRM, ERP, and service platforms
108
+
109
+ ## Citing this work
110
+
111
+ If you use these patterns in your work, see `CITATION.cff` or use GitHub's "Cite this repository" button above.
@@ -0,0 +1,8 @@
1
+ integration_automation_patterns/__init__.py,sha256=3Ovz1M_BrF1oeuR30kHC8F7pJFvAow8ubVP9OlkJH4M,410
2
+ integration_automation_patterns/event_envelope.py,sha256=lLsyIzjj-6e3-xUFI8G9RaWMpiNAexQQi7olYFdL-Es,8780
3
+ integration_automation_patterns/sync_boundary.py,sha256=BxNDRvr1yBtp5q--Q0t5lUYSIAS3qeT0C1ejDev8BC4,8713
4
+ integration_automation_patterns-0.1.0.dist-info/licenses/LICENSE,sha256=L6npPxlrwrbxlLHOCdxrcGGhlRpL0YR7OevMVApNuG4,1070
5
+ integration_automation_patterns-0.1.0.dist-info/METADATA,sha256=Ut7bzl14hF0C1-UooS22dlaz1OP1VskpoHq222hr-nw,4861
6
+ integration_automation_patterns-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
7
+ integration_automation_patterns-0.1.0.dist-info/top_level.txt,sha256=FLqJ0YhyPI2Y0duuD1lFIfuInngldNmeeVnVKUnfqqQ,32
8
+ integration_automation_patterns-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ashutosh Rana
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
+ integration_automation_patterns