ai-lls-lib 1.0.0__py3-none-any.whl → 1.2.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,163 @@
1
+ """Stripe webhook event processing."""
2
+
3
+ import json
4
+ import logging
5
+ from typing import Dict, Any
6
+
7
+ try:
8
+ import stripe
9
+ except ImportError:
10
+ stripe = None
11
+
12
+ from .credit_manager import CreditManager
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class WebhookProcessor:
18
+ """Process Stripe webhook events."""
19
+
20
+ def __init__(self, webhook_secret: str, credit_manager: CreditManager):
21
+ """Initialize with webhook secret and credit manager."""
22
+ self.webhook_secret = webhook_secret
23
+ self.credit_manager = credit_manager
24
+
25
+ def verify_and_parse(self, payload: str, signature: str) -> Dict[str, Any]:
26
+ """Verify webhook signature and parse event."""
27
+ if not stripe:
28
+ raise ImportError("stripe package not installed")
29
+
30
+ try:
31
+ event = stripe.Webhook.construct_event(
32
+ payload, signature, self.webhook_secret
33
+ )
34
+ return event
35
+ except ValueError as e:
36
+ logger.error(f"Invalid webhook payload: {e}")
37
+ raise
38
+ except stripe.error.SignatureVerificationError as e:
39
+ logger.error(f"Invalid webhook signature: {e}")
40
+ raise
41
+
42
+ def process_event(self, event: Dict[str, Any]) -> Dict[str, Any]:
43
+ """
44
+ Process a verified webhook event.
45
+ Returns response data.
46
+ """
47
+ event_type = event.get("type")
48
+ event_data = event.get("data", {}).get("object", {})
49
+
50
+ logger.info(f"Processing webhook event: {event_type}")
51
+
52
+ if event_type == "checkout.session.completed":
53
+ return self._handle_checkout_completed(event_data)
54
+
55
+ elif event_type == "customer.subscription.created":
56
+ return self._handle_subscription_created(event_data)
57
+
58
+ elif event_type == "customer.subscription.updated":
59
+ return self._handle_subscription_updated(event_data)
60
+
61
+ elif event_type == "customer.subscription.deleted":
62
+ return self._handle_subscription_deleted(event_data)
63
+
64
+ elif event_type == "invoice.payment_succeeded":
65
+ return self._handle_invoice_paid(event_data)
66
+
67
+ elif event_type == "invoice.payment_failed":
68
+ return self._handle_invoice_failed(event_data)
69
+
70
+ else:
71
+ logger.info(f"Unhandled event type: {event_type}")
72
+ return {"message": f"Event {event_type} received but not processed"}
73
+
74
+ def _handle_checkout_completed(self, session: Dict[str, Any]) -> Dict[str, Any]:
75
+ """Handle successful checkout session for credit purchase."""
76
+ metadata = session.get("metadata", {})
77
+ user_id = metadata.get("user_id")
78
+
79
+ if not user_id:
80
+ logger.error("No user_id in checkout session metadata")
81
+ return {"error": "Missing user_id"}
82
+
83
+ # Get line items to determine credits purchased
84
+ if session.get("mode") == "payment":
85
+ # One-time payment for credits
86
+ # In production, fetch line items from Stripe to get price metadata
87
+ # For now, extract from session metadata if available
88
+ credits = int(metadata.get("credits", 0))
89
+
90
+ if credits > 0:
91
+ new_balance = self.credit_manager.add_credits(user_id, credits)
92
+ logger.info(f"Added {credits} credits to user {user_id}, new balance: {new_balance}")
93
+ return {"credits_added": credits, "new_balance": new_balance}
94
+
95
+ return {"message": "Checkout processed"}
96
+
97
+ def _handle_subscription_created(self, subscription: Dict[str, Any]) -> Dict[str, Any]:
98
+ """Handle new subscription creation."""
99
+ metadata = subscription.get("metadata", {})
100
+ user_id = metadata.get("user_id")
101
+ customer_id = subscription.get("customer")
102
+ subscription_id = subscription.get("id")
103
+ status = subscription.get("status")
104
+
105
+ if user_id:
106
+ self.credit_manager.set_subscription_state(
107
+ user_id=user_id,
108
+ status=status,
109
+ stripe_customer_id=customer_id,
110
+ stripe_subscription_id=subscription_id
111
+ )
112
+ logger.info(f"Created subscription {subscription_id} for user {user_id}")
113
+
114
+ return {"subscription_id": subscription_id, "status": status}
115
+
116
+ def _handle_subscription_updated(self, subscription: Dict[str, Any]) -> Dict[str, Any]:
117
+ """Handle subscription updates (pause/resume/etc)."""
118
+ metadata = subscription.get("metadata", {})
119
+ user_id = metadata.get("user_id")
120
+ subscription_id = subscription.get("id")
121
+ status = subscription.get("status")
122
+
123
+ if user_id:
124
+ self.credit_manager.set_subscription_state(
125
+ user_id=user_id,
126
+ status=status,
127
+ stripe_subscription_id=subscription_id
128
+ )
129
+ logger.info(f"Updated subscription {subscription_id} status to {status}")
130
+
131
+ return {"subscription_id": subscription_id, "status": status}
132
+
133
+ def _handle_subscription_deleted(self, subscription: Dict[str, Any]) -> Dict[str, Any]:
134
+ """Handle subscription cancellation."""
135
+ metadata = subscription.get("metadata", {})
136
+ user_id = metadata.get("user_id")
137
+ subscription_id = subscription.get("id")
138
+
139
+ if user_id:
140
+ self.credit_manager.set_subscription_state(
141
+ user_id=user_id,
142
+ status="cancelled",
143
+ stripe_subscription_id=subscription_id
144
+ )
145
+ logger.info(f"Cancelled subscription {subscription_id} for user {user_id}")
146
+
147
+ return {"subscription_id": subscription_id, "status": "cancelled"}
148
+
149
+ def _handle_invoice_paid(self, invoice: Dict[str, Any]) -> Dict[str, Any]:
150
+ """Handle successful subscription payment."""
151
+ # For monthly subscriptions, could grant monthly credit allotment here
152
+ # For now, just log the payment
153
+ customer_id = invoice.get("customer")
154
+ amount = invoice.get("amount_paid", 0) / 100.0
155
+ logger.info(f"Invoice paid: ${amount} from customer {customer_id}")
156
+ return {"amount_paid": amount}
157
+
158
+ def _handle_invoice_failed(self, invoice: Dict[str, Any]) -> Dict[str, Any]:
159
+ """Handle failed subscription payment."""
160
+ customer_id = invoice.get("customer")
161
+ logger.warning(f"Invoice payment failed for customer {customer_id}")
162
+ # Could pause subscription or send notification here
163
+ return {"status": "payment_failed"}
@@ -0,0 +1,7 @@
1
+ """
2
+ Verification providers for phone number checking
3
+ """
4
+ from .base import VerificationProvider
5
+ from .stub import StubProvider
6
+
7
+ __all__ = ["VerificationProvider", "StubProvider"]
@@ -0,0 +1,28 @@
1
+ """
2
+ Base protocol for verification providers
3
+ """
4
+ from typing import Protocol, Tuple
5
+ from ..core.models import LineType
6
+
7
+
8
+ class VerificationProvider(Protocol):
9
+ """
10
+ Protocol for phone verification providers.
11
+ All providers must implement this interface.
12
+ """
13
+
14
+ def verify_phone(self, phone: str) -> Tuple[LineType, bool]:
15
+ """
16
+ Verify a phone number's line type and DNC status.
17
+
18
+ Args:
19
+ phone: E.164 formatted phone number
20
+
21
+ Returns:
22
+ Tuple of (line_type, is_on_dnc_list)
23
+
24
+ Raises:
25
+ ValueError: If phone format is invalid
26
+ Exception: For provider-specific errors
27
+ """
28
+ ...
@@ -0,0 +1,87 @@
1
+ """
2
+ External API provider for production phone verification
3
+ """
4
+ import os
5
+ from typing import Tuple, Optional
6
+ import httpx
7
+ from aws_lambda_powertools import Logger
8
+ from ..core.models import LineType
9
+
10
+ logger = Logger()
11
+
12
+
13
+ class ExternalAPIProvider:
14
+ """
15
+ Production provider that calls external verification APIs.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ phone_api_key: Optional[str] = None,
21
+ dnc_api_key: Optional[str] = None,
22
+ timeout: float = 10.0
23
+ ):
24
+ """
25
+ Initialize external API provider.
26
+
27
+ Args:
28
+ phone_api_key: API key for phone line type verification
29
+ dnc_api_key: API key for DNC list checking
30
+ timeout: HTTP request timeout in seconds
31
+ """
32
+ self.phone_api_key = phone_api_key or os.environ.get("PHONE_VERIFY_API_KEY", "")
33
+ self.dnc_api_key = dnc_api_key or os.environ.get("DNC_API_KEY", "")
34
+ self.http_client = httpx.Client(timeout=timeout)
35
+
36
+ def verify_phone(self, phone: str) -> Tuple[LineType, bool]:
37
+ """
38
+ Verify phone using external APIs.
39
+
40
+ Args:
41
+ phone: E.164 formatted phone number
42
+
43
+ Returns:
44
+ Tuple of (line_type, is_on_dnc_list)
45
+
46
+ Raises:
47
+ httpx.HTTPError: For API communication errors
48
+ ValueError: For invalid responses
49
+ """
50
+ line_type = self._check_line_type(phone)
51
+ is_dnc = self._check_dnc(phone)
52
+ return line_type, is_dnc
53
+
54
+ def _check_line_type(self, phone: str) -> LineType:
55
+ """
56
+ Check line type via external API.
57
+
58
+ TODO: Implement actual API call
59
+ - Use self.phone_api_key for authentication
60
+ - Parse API response
61
+ - Map to LineType enum
62
+ """
63
+ logger.info(f"External line type check for {phone[:6]}***")
64
+
65
+ # Placeholder implementation
66
+ # In production, this would make an actual API call
67
+ raise NotImplementedError("External line type API not yet configured")
68
+
69
+ def _check_dnc(self, phone: str) -> bool:
70
+ """
71
+ Check DNC status via external API.
72
+
73
+ TODO: Implement actual API call
74
+ - Use self.dnc_api_key for authentication
75
+ - Parse API response
76
+ - Return boolean status
77
+ """
78
+ logger.info(f"External DNC check for {phone[:6]}***")
79
+
80
+ # Placeholder implementation
81
+ # In production, this would make an actual API call
82
+ raise NotImplementedError("External DNC API not yet configured")
83
+
84
+ def __del__(self):
85
+ """Cleanup HTTP client"""
86
+ if hasattr(self, 'http_client'):
87
+ self.http_client.close()
@@ -0,0 +1,48 @@
1
+ """
2
+ Stub provider for development and testing
3
+ """
4
+ from typing import Tuple
5
+ from aws_lambda_powertools import Logger
6
+ from ..core.models import LineType
7
+
8
+ logger = Logger()
9
+
10
+
11
+ class StubProvider:
12
+ """
13
+ Stub implementation for development and testing.
14
+ Uses deterministic rules based on phone number digits.
15
+ """
16
+
17
+ def verify_phone(self, phone: str) -> Tuple[LineType, bool]:
18
+ """
19
+ Verify using stub logic based on last digit.
20
+
21
+ Line type:
22
+ - Ends in 2 or 0: LANDLINE
23
+ - Otherwise: MOBILE
24
+
25
+ DNC status:
26
+ - Ends in 1 or 0: on DNC list
27
+ - Otherwise: not on DNC
28
+
29
+ Args:
30
+ phone: E.164 formatted phone number
31
+
32
+ Returns:
33
+ Tuple of (line_type, is_on_dnc_list)
34
+ """
35
+ logger.info(f"Stub verification for {phone[:6]}***")
36
+
37
+ last_digit = phone[-1] if phone else '5'
38
+
39
+ # Determine line type
40
+ if last_digit in ['2', '0']:
41
+ line_type = LineType.LANDLINE
42
+ else:
43
+ line_type = LineType.MOBILE
44
+
45
+ # Determine DNC status
46
+ is_dnc = last_digit in ['1', '0']
47
+
48
+ return line_type, is_dnc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ai-lls-lib
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: Landline Scrubber core library - phone verification and DNC checking
5
5
  Author: LandlineScrubber Team
6
6
  Requires-Python: >=3.12,<4.0
@@ -14,12 +14,28 @@ Requires-Dist: httpx (>=0.25.0,<0.26.0)
14
14
  Requires-Dist: phonenumbers (>=8.13.0,<9.0.0)
15
15
  Requires-Dist: pydantic (>=2.5.0,<3.0.0)
16
16
  Requires-Dist: rich (>=14.0,<15.0)
17
+ Requires-Dist: stripe (>=12.5.1,<13.0.0)
17
18
  Description-Content-Type: text/markdown
18
19
 
19
20
  # AI LLS Library
20
21
 
21
22
  Core business logic library and CLI tools for Landline Scrubber - phone verification and DNC checking.
22
23
 
24
+ ## Version 2.1.0 - Streaming & Provider Architecture
25
+
26
+ New features:
27
+ - **Streaming support** for large CSV files to reduce memory usage
28
+ - **Provider architecture** for clean separation of verification logic
29
+ - **Contract tests** ensuring all providers behave consistently
30
+
31
+ ## Version 2.0.0 - Breaking Changes
32
+
33
+ This is a greenfield rewrite with no backwards compatibility:
34
+ - All file-based CSV processing replaced with text-based methods
35
+ - Removed `_sync` suffix from all methods (everything is sync)
36
+ - `process_csv_sync(file_path)` → `process_csv(csv_text)`
37
+ - `generate_results_csv(...)` now returns CSV string instead of writing to file
38
+
23
39
  ## Features
24
40
 
25
41
  - Phone number normalization (E.164 format)
@@ -50,7 +66,7 @@ from ai_lls_lib import PhoneVerifier, DynamoDBCache
50
66
  cache = DynamoDBCache(table_name="phone-cache")
51
67
  verifier = PhoneVerifier(cache)
52
68
 
53
- result = verifier.verify_sync("+15551234567")
69
+ result = verifier.verify("+15551234567")
54
70
  print(f"Line type: {result.line_type}")
55
71
  print(f"DNC: {result.dnc}")
56
72
  print(f"From cache: {result.cached}")
@@ -65,12 +81,49 @@ cache = DynamoDBCache(table_name="phone-cache")
65
81
  verifier = PhoneVerifier(cache)
66
82
  processor = BulkProcessor(verifier)
67
83
 
68
- results = processor.process_csv_sync("/path/to/phones.csv")
69
- processor.generate_results_csv(
70
- original_path="/path/to/phones.csv",
71
- results=results,
72
- output_path="/path/to/results.csv"
73
- )
84
+ # Process CSV text content
85
+ csv_text = "name,phone\nJohn,+15551234567\nJane,+15551234568"
86
+ results = processor.process_csv(csv_text)
87
+
88
+ # Generate results CSV
89
+ results_csv = processor.generate_results_csv(csv_text, results)
90
+ print(results_csv) # CSV string with added line_type, dnc, cached columns
91
+ ```
92
+
93
+ ### Streaming Large Files
94
+
95
+ For memory-efficient processing of large CSV files:
96
+
97
+ ```python
98
+ from ai_lls_lib import BulkProcessor, PhoneVerifier, DynamoDBCache
99
+
100
+ cache = DynamoDBCache(table_name="phone-cache")
101
+ verifier = PhoneVerifier(cache)
102
+ processor = BulkProcessor(verifier)
103
+
104
+ # Process CSV as a stream, yielding batches
105
+ csv_lines = open('large_file.csv').readlines()
106
+ for batch in processor.process_csv_stream(csv_lines, batch_size=100):
107
+ print(f"Processed batch of {len(batch)} phones")
108
+ # Each batch is a list of PhoneVerification objects
109
+ ```
110
+
111
+ ### Custom Verification Providers
112
+
113
+ Use different verification providers based on your needs:
114
+
115
+ ```python
116
+ from ai_lls_lib import PhoneVerifier, DynamoDBCache
117
+ from ai_lls_lib.providers import StubProvider
118
+
119
+ # Use stub provider for testing
120
+ cache = DynamoDBCache(table_name="phone-cache")
121
+ provider = StubProvider() # Deterministic testing provider
122
+ verifier = PhoneVerifier(cache, provider=provider)
123
+
124
+ # When external APIs are ready, switch to:
125
+ # from ai_lls_lib.providers.external import ExternalAPIProvider
126
+ # provider = ExternalAPIProvider(phone_api_key="...", dnc_api_key="...")
74
127
  ```
75
128
 
76
129
  ## CLI Usage
@@ -0,0 +1,33 @@
1
+ ai_lls_lib/__init__.py,sha256=vX28RZKgWt_4PDkQ_H69AwCCp7Jt9nHwhxt4RhKOotU,584
2
+ ai_lls_lib/auth/__init__.py,sha256=c6zomHSB6y9Seakf84ciGsD3XgWarIty9xty6P8fxVw,194
3
+ ai_lls_lib/auth/context_parser.py,sha256=8I0vGbtykNLWqm8ldedxXjE-E3nqsCy113JgeyuiJoM,2222
4
+ ai_lls_lib/cli/__init__.py,sha256=m9qjZTW1jpENwXAUeuRrlP0b66BWRcqSO28MSjvOyCs,74
5
+ ai_lls_lib/cli/__main__.py,sha256=8tUdq4GJwzIiTC1pvCsTkwmq8iNujdFTbI45_wm3d7A,716
6
+ ai_lls_lib/cli/aws_client.py,sha256=YcCWCpTNOW9JPLxSNLRy5-F5HPKguJJk7dPNrPqhJv0,3952
7
+ ai_lls_lib/cli/commands/__init__.py,sha256=_kROrYuR_p2i110c0OvNeArfEFQbn15zR1c3pdeZOoo,28
8
+ ai_lls_lib/cli/commands/admin.py,sha256=bNBJi2fZBP0J40JQP6HP7NYadNmI214iII1TeLhooyE,6687
9
+ ai_lls_lib/cli/commands/cache.py,sha256=vWt0vy4L9CEgUEWUzfdehU6u43PE8vUvHx7xxg4e5tw,5680
10
+ ai_lls_lib/cli/commands/stripe.py,sha256=x5QtirbCPiBrjdMRVTR0aeQN2gy0_p6jcGzELObFfzg,12006
11
+ ai_lls_lib/cli/commands/test_stack.py,sha256=rNq4mhRXX7Ixo67kSoEPWlxqgXCzM9e2PR96qTAG7pE,7378
12
+ ai_lls_lib/cli/commands/verify.py,sha256=V5ucjmjCUxqN8_AeEJWWgrmYin8BNV3h4WbZ-iX3loU,4238
13
+ ai_lls_lib/cli/env_loader.py,sha256=YTCB6QDyknOuedPbQsW4bezB5SgHzJMGjhpSPArHFVc,3762
14
+ ai_lls_lib/core/__init__.py,sha256=QUaeQHIyvknkgMxIbfXRo1a5jSQpiJkB84dId5ubuEk,36
15
+ ai_lls_lib/core/cache.py,sha256=MubgyAF3y7BBF9am39Ni98NgikZ9UBZUG-KtbE3XWX4,3711
16
+ ai_lls_lib/core/models.py,sha256=ABRYaeMCWahQh4WdbkCxt3AO0-EvPWZwlL-JITQSnEM,2381
17
+ ai_lls_lib/core/processor.py,sha256=6752IPDQ-Mz5i_CU7aBM0UjvV7IjyZFl35LKVPkHMpc,9974
18
+ ai_lls_lib/core/verifier.py,sha256=6uB_jawWoIqsNYAadTKr0lSolIWygg2gK6ykf8lrul0,2716
19
+ ai_lls_lib/payment/__init__.py,sha256=xhUWgfLnk3syXXQItswmDXdfXUyJTXTQAA0zIUuCVII,295
20
+ ai_lls_lib/payment/credit_manager.py,sha256=TDrdW7BYjhtlg8MX_EJEnMNct1SU_6Tdb_k19_e8kpo,6343
21
+ ai_lls_lib/payment/models.py,sha256=JjSmWKwpuFF85Jzmabj6y7UyolJlJlsh5CmOWRg21B8,3339
22
+ ai_lls_lib/payment/stripe_manager.py,sha256=-L329stuu-6V_QKzRdkm9qnoi74SbWzST0fuf2WAJXA,17111
23
+ ai_lls_lib/payment/webhook_processor.py,sha256=UVngeAGcou5ieRAp-49pqdWh0wsJQVwpiRoLmyl5Adc,6448
24
+ ai_lls_lib/providers/__init__.py,sha256=AEv3ARenWDwDo5PLCoszP2fQ70RgSHkrSLSUz7xHJDk,179
25
+ ai_lls_lib/providers/base.py,sha256=344XYOg7bxDQMWJ6Lle8U7NOHpabnCp0XYbZpeWpPAk,681
26
+ ai_lls_lib/providers/external.py,sha256=-Hhnlm8lQWRcWr5vG0dmD3sca2rohURrx0usOR2y1MM,2623
27
+ ai_lls_lib/providers/stub.py,sha256=847Tmw522B3HQ2j38BH1sdcZQy--RdtDcXsrIrFKNBQ,1150
28
+ ai_lls_lib/testing/__init__.py,sha256=RUxRYBzzPCPS15Umb6bUrE6rL5BQXBQf4SJM2E3ffrg,39
29
+ ai_lls_lib/testing/fixtures.py,sha256=_n6bbr95LnQf9Dvu1qKs2HsvHEA7AAbe59B75qxE10w,3310
30
+ ai_lls_lib-1.2.0.dist-info/METADATA,sha256=o76nenDApQ5pITFYnlmX7IS-maEi39JgU75iUyI9udk,7248
31
+ ai_lls_lib-1.2.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
+ ai_lls_lib-1.2.0.dist-info/entry_points.txt,sha256=Pi0V_HBViEKGFbNQKatl5lhhnHHBXlxaom-5gH9gXZ0,55
33
+ ai_lls_lib-1.2.0.dist-info/RECORD,,
@@ -1,20 +0,0 @@
1
- ai_lls_lib/__init__.py,sha256=XfIG376ImlT_OH0R75BNpoqcxsx0fIp6cSFpQw_XhL8,584
2
- ai_lls_lib/cli/__init__.py,sha256=m9qjZTW1jpENwXAUeuRrlP0b66BWRcqSO28MSjvOyCs,74
3
- ai_lls_lib/cli/__main__.py,sha256=DH9x08k6GWBSqVWspHgWTxGM7NkTBZ2OS2KrVW-XQaA,671
4
- ai_lls_lib/cli/aws_client.py,sha256=YcCWCpTNOW9JPLxSNLRy5-F5HPKguJJk7dPNrPqhJv0,3952
5
- ai_lls_lib/cli/commands/__init__.py,sha256=_kROrYuR_p2i110c0OvNeArfEFQbn15zR1c3pdeZOoo,28
6
- ai_lls_lib/cli/commands/admin.py,sha256=bNBJi2fZBP0J40JQP6HP7NYadNmI214iII1TeLhooyE,6687
7
- ai_lls_lib/cli/commands/cache.py,sha256=vWt0vy4L9CEgUEWUzfdehU6u43PE8vUvHx7xxg4e5tw,5680
8
- ai_lls_lib/cli/commands/test_stack.py,sha256=rNq4mhRXX7Ixo67kSoEPWlxqgXCzM9e2PR96qTAG7pE,7378
9
- ai_lls_lib/cli/commands/verify.py,sha256=tKHkSghrtnqkjAQGvTs-5uDUgstQO0HMW04EddRPgJU,4253
10
- ai_lls_lib/core/__init__.py,sha256=QUaeQHIyvknkgMxIbfXRo1a5jSQpiJkB84dId5ubuEk,36
11
- ai_lls_lib/core/cache.py,sha256=MubgyAF3y7BBF9am39Ni98NgikZ9UBZUG-KtbE3XWX4,3711
12
- ai_lls_lib/core/models.py,sha256=ABRYaeMCWahQh4WdbkCxt3AO0-EvPWZwlL-JITQSnEM,2381
13
- ai_lls_lib/core/processor.py,sha256=s49fVlZt6lnfWNgboiHsC6ruman4oIXzgVqzuwExKBQ,4916
14
- ai_lls_lib/core/verifier.py,sha256=fglRDoFgrQyX4UuYknZLYX4t9HlpTS2HMVsfszSNXQ4,3257
15
- ai_lls_lib/testing/__init__.py,sha256=RUxRYBzzPCPS15Umb6bUrE6rL5BQXBQf4SJM2E3ffrg,39
16
- ai_lls_lib/testing/fixtures.py,sha256=_n6bbr95LnQf9Dvu1qKs2HsvHEA7AAbe59B75qxE10w,3310
17
- ai_lls_lib-1.0.0.dist-info/METADATA,sha256=q4xSigc2_m2Jmv45SfXFfH9dBbFOVDcfIBa0Uzaw48w,5344
18
- ai_lls_lib-1.0.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
19
- ai_lls_lib-1.0.0.dist-info/entry_points.txt,sha256=Pi0V_HBViEKGFbNQKatl5lhhnHHBXlxaom-5gH9gXZ0,55
20
- ai_lls_lib-1.0.0.dist-info/RECORD,,