ai-lls-lib 1.4.0rc2__py3-none-any.whl → 1.4.0rc4__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.
- ai_lls_lib/__init__.py +1 -1
- ai_lls_lib/auth/__init__.py +4 -4
- ai_lls_lib/auth/context_parser.py +68 -68
- ai_lls_lib/cli/__init__.py +3 -3
- ai_lls_lib/cli/__main__.py +30 -30
- ai_lls_lib/cli/aws_client.py +115 -115
- ai_lls_lib/cli/commands/__init__.py +3 -3
- ai_lls_lib/cli/commands/admin.py +174 -174
- ai_lls_lib/cli/commands/cache.py +142 -142
- ai_lls_lib/cli/commands/stripe.py +377 -377
- ai_lls_lib/cli/commands/test_stack.py +216 -216
- ai_lls_lib/cli/commands/verify.py +111 -111
- ai_lls_lib/cli/env_loader.py +122 -122
- ai_lls_lib/core/__init__.py +3 -3
- ai_lls_lib/core/cache.py +106 -106
- ai_lls_lib/core/models.py +77 -77
- ai_lls_lib/core/processor.py +295 -295
- ai_lls_lib/core/verifier.py +84 -84
- ai_lls_lib/payment/__init__.py +13 -13
- ai_lls_lib/payment/credit_manager.py +186 -193
- ai_lls_lib/payment/models.py +102 -102
- ai_lls_lib/payment/stripe_manager.py +487 -487
- ai_lls_lib/payment/webhook_processor.py +215 -215
- ai_lls_lib/providers/__init__.py +7 -7
- ai_lls_lib/providers/base.py +28 -28
- ai_lls_lib/providers/external.py +87 -87
- ai_lls_lib/providers/stub.py +48 -48
- ai_lls_lib/testing/__init__.py +3 -3
- ai_lls_lib/testing/fixtures.py +104 -104
- {ai_lls_lib-1.4.0rc2.dist-info → ai_lls_lib-1.4.0rc4.dist-info}/METADATA +1 -1
- ai_lls_lib-1.4.0rc4.dist-info/RECORD +33 -0
- ai_lls_lib-1.4.0rc2.dist-info/RECORD +0 -33
- {ai_lls_lib-1.4.0rc2.dist-info → ai_lls_lib-1.4.0rc4.dist-info}/WHEEL +0 -0
- {ai_lls_lib-1.4.0rc2.dist-info → ai_lls_lib-1.4.0rc4.dist-info}/entry_points.txt +0 -0
@@ -1,216 +1,216 @@
|
|
1
|
-
"""
|
2
|
-
Test stack management commands
|
3
|
-
"""
|
4
|
-
import click
|
5
|
-
import subprocess
|
6
|
-
import os
|
7
|
-
import time
|
8
|
-
from ai_lls_lib.cli.aws_client import AWSClient
|
9
|
-
|
10
|
-
@click.group(name="test-stack")
|
11
|
-
def test_stack_group():
|
12
|
-
"""Test stack management"""
|
13
|
-
pass
|
14
|
-
|
15
|
-
@test_stack_group.command(name="deploy")
|
16
|
-
@click.option("--stack-name", default="ai-lls-lib-test", help="Test stack name")
|
17
|
-
@click.option("--profile", help="AWS profile")
|
18
|
-
@click.option("--region", help="AWS region")
|
19
|
-
def deploy_test_stack(stack_name, profile, region):
|
20
|
-
"""Deploy the test stack"""
|
21
|
-
template_path = os.path.join(
|
22
|
-
os.path.dirname(__file__),
|
23
|
-
"..", "..", "..", "..", "template.yaml"
|
24
|
-
)
|
25
|
-
|
26
|
-
if not os.path.exists(template_path):
|
27
|
-
click.echo(f"Test stack template not found at {template_path}")
|
28
|
-
click.echo("Creating minimal test stack template...")
|
29
|
-
|
30
|
-
# Create a minimal test stack
|
31
|
-
template_content = """AWSTemplateFormatVersion: '2010-09-09'
|
32
|
-
Description: Minimal test stack for ai-lls-lib integration testing
|
33
|
-
|
34
|
-
Resources:
|
35
|
-
TestPhoneCache:
|
36
|
-
Type: AWS::DynamoDB::Table
|
37
|
-
Properties:
|
38
|
-
TableName: !Sub "${AWS::StackName}-phone-cache"
|
39
|
-
BillingMode: PAY_PER_REQUEST
|
40
|
-
AttributeDefinitions:
|
41
|
-
- AttributeName: phone_number
|
42
|
-
AttributeType: S
|
43
|
-
KeySchema:
|
44
|
-
- AttributeName: phone_number
|
45
|
-
KeyType: HASH
|
46
|
-
TimeToLiveSpecification:
|
47
|
-
AttributeName: ttl
|
48
|
-
Enabled: true
|
49
|
-
|
50
|
-
TestUploadBucket:
|
51
|
-
Type: AWS::S3::Bucket
|
52
|
-
Properties:
|
53
|
-
BucketName: !Sub "${AWS::StackName}-uploads-${AWS::AccountId}"
|
54
|
-
LifecycleConfiguration:
|
55
|
-
Rules:
|
56
|
-
- Id: DeleteOldTestFiles
|
57
|
-
Status: Enabled
|
58
|
-
ExpirationInDays: 1
|
59
|
-
|
60
|
-
TestQueue:
|
61
|
-
Type: AWS::SQS::Queue
|
62
|
-
Properties:
|
63
|
-
QueueName: !Sub "${AWS::StackName}-test-queue"
|
64
|
-
MessageRetentionPeriod: 3600 # 1 hour for test
|
65
|
-
|
66
|
-
Outputs:
|
67
|
-
CacheTableName:
|
68
|
-
Value: !Ref TestPhoneCache
|
69
|
-
BucketName:
|
70
|
-
Value: !Ref TestUploadBucket
|
71
|
-
QueueUrl:
|
72
|
-
Value: !Ref TestQueue
|
73
|
-
"""
|
74
|
-
|
75
|
-
os.makedirs(os.path.dirname(template_path), exist_ok=True)
|
76
|
-
with open(template_path, 'w') as f:
|
77
|
-
f.write(template_content)
|
78
|
-
click.echo(f"Created {template_path}")
|
79
|
-
|
80
|
-
# Deploy using AWS CLI
|
81
|
-
cmd = [
|
82
|
-
"aws", "cloudformation", "deploy",
|
83
|
-
"--template-file", template_path,
|
84
|
-
"--stack-name", stack_name,
|
85
|
-
"--capabilities", "CAPABILITY_IAM"
|
86
|
-
]
|
87
|
-
|
88
|
-
if profile:
|
89
|
-
cmd.extend(["--profile", profile])
|
90
|
-
if region:
|
91
|
-
cmd.extend(["--region", region])
|
92
|
-
|
93
|
-
click.echo(f"Deploying test stack '{stack_name}'...")
|
94
|
-
try:
|
95
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
96
|
-
if result.returncode == 0:
|
97
|
-
click.echo("Test stack deployed successfully!")
|
98
|
-
|
99
|
-
# Show outputs
|
100
|
-
aws = AWSClient(region=region, profile=profile)
|
101
|
-
outputs = aws.get_stack_outputs(stack_name)
|
102
|
-
if outputs:
|
103
|
-
click.echo("\nStack Outputs:")
|
104
|
-
for key, value in outputs.items():
|
105
|
-
click.echo(f" {key}: {value}")
|
106
|
-
else:
|
107
|
-
click.echo(f"Deployment failed: {result.stderr}", err=True)
|
108
|
-
except Exception as e:
|
109
|
-
click.echo(f"Error deploying test stack: {e}", err=True)
|
110
|
-
|
111
|
-
@test_stack_group.command(name="delete")
|
112
|
-
@click.option("--stack-name", default="ai-lls-lib-test", help="Test stack name")
|
113
|
-
@click.option("--profile", help="AWS profile")
|
114
|
-
@click.option("--region", help="AWS region")
|
115
|
-
@click.confirmation_option(prompt="Delete test stack and all resources?")
|
116
|
-
def delete_test_stack(stack_name, profile, region):
|
117
|
-
"""Delete the test stack"""
|
118
|
-
aws = AWSClient(region=region, profile=profile)
|
119
|
-
|
120
|
-
try:
|
121
|
-
# Empty S3 bucket first
|
122
|
-
outputs = aws.get_stack_outputs(stack_name)
|
123
|
-
if 'BucketName' in outputs:
|
124
|
-
bucket_name = outputs['BucketName']
|
125
|
-
click.echo(f"Emptying bucket {bucket_name}...")
|
126
|
-
|
127
|
-
# List and delete all objects
|
128
|
-
try:
|
129
|
-
objects = aws.s3.list_objects_v2(Bucket=bucket_name)
|
130
|
-
if 'Contents' in objects:
|
131
|
-
for obj in objects['Contents']:
|
132
|
-
aws.s3.delete_object(Bucket=bucket_name, Key=obj['Key'])
|
133
|
-
except:
|
134
|
-
pass # Bucket might not exist
|
135
|
-
|
136
|
-
# Delete stack
|
137
|
-
click.echo(f"Deleting stack '{stack_name}'...")
|
138
|
-
aws.cloudformation.delete_stack(StackName=stack_name)
|
139
|
-
|
140
|
-
# Wait for deletion
|
141
|
-
click.echo("Waiting for stack deletion...")
|
142
|
-
waiter = aws.cloudformation.get_waiter('stack_delete_complete')
|
143
|
-
waiter.wait(StackName=stack_name)
|
144
|
-
|
145
|
-
click.echo("Test stack deleted successfully!")
|
146
|
-
|
147
|
-
except Exception as e:
|
148
|
-
click.echo(f"Error deleting test stack: {e}", err=True)
|
149
|
-
|
150
|
-
@test_stack_group.command(name="status")
|
151
|
-
@click.option("--stack-name", default="ai-lls-lib-test", help="Test stack name")
|
152
|
-
@click.option("--profile", help="AWS profile")
|
153
|
-
@click.option("--region", help="AWS region")
|
154
|
-
def test_stack_status(stack_name, profile, region):
|
155
|
-
"""Show test stack status"""
|
156
|
-
aws = AWSClient(region=region, profile=profile)
|
157
|
-
|
158
|
-
try:
|
159
|
-
response = aws.cloudformation.describe_stacks(StackName=stack_name)
|
160
|
-
stack = response['Stacks'][0]
|
161
|
-
|
162
|
-
click.echo(f"\nTest Stack: {stack_name}")
|
163
|
-
click.echo("=" * 50)
|
164
|
-
click.echo(f"Status: {stack['StackStatus']}")
|
165
|
-
click.echo(f"Created: {stack.get('CreationTime', 'N/A')}")
|
166
|
-
|
167
|
-
if 'Outputs' in stack:
|
168
|
-
click.echo("\nOutputs:")
|
169
|
-
for output in stack['Outputs']:
|
170
|
-
click.echo(f" {output['OutputKey']}: {output['OutputValue']}")
|
171
|
-
|
172
|
-
except aws.cloudformation.exceptions.ClientError as e:
|
173
|
-
if 'does not exist' in str(e):
|
174
|
-
click.echo(f"Test stack '{stack_name}' does not exist")
|
175
|
-
else:
|
176
|
-
click.echo(f"Error: {e}", err=True)
|
177
|
-
|
178
|
-
@test_stack_group.command(name="test")
|
179
|
-
@click.option("--stack-name", default="ai-lls-lib-test", help="Test stack name")
|
180
|
-
@click.option("--profile", help="AWS profile")
|
181
|
-
@click.option("--region", help="AWS region")
|
182
|
-
def run_integration_tests(stack_name, profile, region):
|
183
|
-
"""Run integration tests against test stack"""
|
184
|
-
aws = AWSClient(region=region, profile=profile)
|
185
|
-
|
186
|
-
try:
|
187
|
-
# Check stack exists
|
188
|
-
outputs = aws.get_stack_outputs(stack_name)
|
189
|
-
if not outputs:
|
190
|
-
click.echo(f"Test stack '{stack_name}' not found. Deploy it first.")
|
191
|
-
return
|
192
|
-
|
193
|
-
# Set environment variables for tests
|
194
|
-
os.environ['TEST_STACK_NAME'] = stack_name
|
195
|
-
os.environ['TEST_CACHE_TABLE'] = outputs.get('CacheTableName', '')
|
196
|
-
os.environ['TEST_BUCKET'] = outputs.get('BucketName', '')
|
197
|
-
os.environ['TEST_QUEUE_URL'] = outputs.get('QueueUrl', '')
|
198
|
-
|
199
|
-
if profile:
|
200
|
-
os.environ['AWS_PROFILE'] = profile
|
201
|
-
if region:
|
202
|
-
os.environ['AWS_REGION'] = region
|
203
|
-
|
204
|
-
click.echo(f"Running integration tests against '{stack_name}'...")
|
205
|
-
|
206
|
-
# Run pytest
|
207
|
-
cmd = ["pytest", "tests/integration", "-v"]
|
208
|
-
result = subprocess.run(cmd, cwd=os.path.dirname(template_path))
|
209
|
-
|
210
|
-
if result.returncode == 0:
|
211
|
-
click.echo("\nIntegration tests passed!")
|
212
|
-
else:
|
213
|
-
click.echo("\nSome tests failed", err=True)
|
214
|
-
|
215
|
-
except Exception as e:
|
216
|
-
click.echo(f"Error running tests: {e}", err=True)
|
1
|
+
"""
|
2
|
+
Test stack management commands
|
3
|
+
"""
|
4
|
+
import click
|
5
|
+
import subprocess
|
6
|
+
import os
|
7
|
+
import time
|
8
|
+
from ai_lls_lib.cli.aws_client import AWSClient
|
9
|
+
|
10
|
+
@click.group(name="test-stack")
|
11
|
+
def test_stack_group():
|
12
|
+
"""Test stack management"""
|
13
|
+
pass
|
14
|
+
|
15
|
+
@test_stack_group.command(name="deploy")
|
16
|
+
@click.option("--stack-name", default="ai-lls-lib-test", help="Test stack name")
|
17
|
+
@click.option("--profile", help="AWS profile")
|
18
|
+
@click.option("--region", help="AWS region")
|
19
|
+
def deploy_test_stack(stack_name, profile, region):
|
20
|
+
"""Deploy the test stack"""
|
21
|
+
template_path = os.path.join(
|
22
|
+
os.path.dirname(__file__),
|
23
|
+
"..", "..", "..", "..", "template.yaml"
|
24
|
+
)
|
25
|
+
|
26
|
+
if not os.path.exists(template_path):
|
27
|
+
click.echo(f"Test stack template not found at {template_path}")
|
28
|
+
click.echo("Creating minimal test stack template...")
|
29
|
+
|
30
|
+
# Create a minimal test stack
|
31
|
+
template_content = """AWSTemplateFormatVersion: '2010-09-09'
|
32
|
+
Description: Minimal test stack for ai-lls-lib integration testing
|
33
|
+
|
34
|
+
Resources:
|
35
|
+
TestPhoneCache:
|
36
|
+
Type: AWS::DynamoDB::Table
|
37
|
+
Properties:
|
38
|
+
TableName: !Sub "${AWS::StackName}-phone-cache"
|
39
|
+
BillingMode: PAY_PER_REQUEST
|
40
|
+
AttributeDefinitions:
|
41
|
+
- AttributeName: phone_number
|
42
|
+
AttributeType: S
|
43
|
+
KeySchema:
|
44
|
+
- AttributeName: phone_number
|
45
|
+
KeyType: HASH
|
46
|
+
TimeToLiveSpecification:
|
47
|
+
AttributeName: ttl
|
48
|
+
Enabled: true
|
49
|
+
|
50
|
+
TestUploadBucket:
|
51
|
+
Type: AWS::S3::Bucket
|
52
|
+
Properties:
|
53
|
+
BucketName: !Sub "${AWS::StackName}-uploads-${AWS::AccountId}"
|
54
|
+
LifecycleConfiguration:
|
55
|
+
Rules:
|
56
|
+
- Id: DeleteOldTestFiles
|
57
|
+
Status: Enabled
|
58
|
+
ExpirationInDays: 1
|
59
|
+
|
60
|
+
TestQueue:
|
61
|
+
Type: AWS::SQS::Queue
|
62
|
+
Properties:
|
63
|
+
QueueName: !Sub "${AWS::StackName}-test-queue"
|
64
|
+
MessageRetentionPeriod: 3600 # 1 hour for test
|
65
|
+
|
66
|
+
Outputs:
|
67
|
+
CacheTableName:
|
68
|
+
Value: !Ref TestPhoneCache
|
69
|
+
BucketName:
|
70
|
+
Value: !Ref TestUploadBucket
|
71
|
+
QueueUrl:
|
72
|
+
Value: !Ref TestQueue
|
73
|
+
"""
|
74
|
+
|
75
|
+
os.makedirs(os.path.dirname(template_path), exist_ok=True)
|
76
|
+
with open(template_path, 'w') as f:
|
77
|
+
f.write(template_content)
|
78
|
+
click.echo(f"Created {template_path}")
|
79
|
+
|
80
|
+
# Deploy using AWS CLI
|
81
|
+
cmd = [
|
82
|
+
"aws", "cloudformation", "deploy",
|
83
|
+
"--template-file", template_path,
|
84
|
+
"--stack-name", stack_name,
|
85
|
+
"--capabilities", "CAPABILITY_IAM"
|
86
|
+
]
|
87
|
+
|
88
|
+
if profile:
|
89
|
+
cmd.extend(["--profile", profile])
|
90
|
+
if region:
|
91
|
+
cmd.extend(["--region", region])
|
92
|
+
|
93
|
+
click.echo(f"Deploying test stack '{stack_name}'...")
|
94
|
+
try:
|
95
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
96
|
+
if result.returncode == 0:
|
97
|
+
click.echo("Test stack deployed successfully!")
|
98
|
+
|
99
|
+
# Show outputs
|
100
|
+
aws = AWSClient(region=region, profile=profile)
|
101
|
+
outputs = aws.get_stack_outputs(stack_name)
|
102
|
+
if outputs:
|
103
|
+
click.echo("\nStack Outputs:")
|
104
|
+
for key, value in outputs.items():
|
105
|
+
click.echo(f" {key}: {value}")
|
106
|
+
else:
|
107
|
+
click.echo(f"Deployment failed: {result.stderr}", err=True)
|
108
|
+
except Exception as e:
|
109
|
+
click.echo(f"Error deploying test stack: {e}", err=True)
|
110
|
+
|
111
|
+
@test_stack_group.command(name="delete")
|
112
|
+
@click.option("--stack-name", default="ai-lls-lib-test", help="Test stack name")
|
113
|
+
@click.option("--profile", help="AWS profile")
|
114
|
+
@click.option("--region", help="AWS region")
|
115
|
+
@click.confirmation_option(prompt="Delete test stack and all resources?")
|
116
|
+
def delete_test_stack(stack_name, profile, region):
|
117
|
+
"""Delete the test stack"""
|
118
|
+
aws = AWSClient(region=region, profile=profile)
|
119
|
+
|
120
|
+
try:
|
121
|
+
# Empty S3 bucket first
|
122
|
+
outputs = aws.get_stack_outputs(stack_name)
|
123
|
+
if 'BucketName' in outputs:
|
124
|
+
bucket_name = outputs['BucketName']
|
125
|
+
click.echo(f"Emptying bucket {bucket_name}...")
|
126
|
+
|
127
|
+
# List and delete all objects
|
128
|
+
try:
|
129
|
+
objects = aws.s3.list_objects_v2(Bucket=bucket_name)
|
130
|
+
if 'Contents' in objects:
|
131
|
+
for obj in objects['Contents']:
|
132
|
+
aws.s3.delete_object(Bucket=bucket_name, Key=obj['Key'])
|
133
|
+
except:
|
134
|
+
pass # Bucket might not exist
|
135
|
+
|
136
|
+
# Delete stack
|
137
|
+
click.echo(f"Deleting stack '{stack_name}'...")
|
138
|
+
aws.cloudformation.delete_stack(StackName=stack_name)
|
139
|
+
|
140
|
+
# Wait for deletion
|
141
|
+
click.echo("Waiting for stack deletion...")
|
142
|
+
waiter = aws.cloudformation.get_waiter('stack_delete_complete')
|
143
|
+
waiter.wait(StackName=stack_name)
|
144
|
+
|
145
|
+
click.echo("Test stack deleted successfully!")
|
146
|
+
|
147
|
+
except Exception as e:
|
148
|
+
click.echo(f"Error deleting test stack: {e}", err=True)
|
149
|
+
|
150
|
+
@test_stack_group.command(name="status")
|
151
|
+
@click.option("--stack-name", default="ai-lls-lib-test", help="Test stack name")
|
152
|
+
@click.option("--profile", help="AWS profile")
|
153
|
+
@click.option("--region", help="AWS region")
|
154
|
+
def test_stack_status(stack_name, profile, region):
|
155
|
+
"""Show test stack status"""
|
156
|
+
aws = AWSClient(region=region, profile=profile)
|
157
|
+
|
158
|
+
try:
|
159
|
+
response = aws.cloudformation.describe_stacks(StackName=stack_name)
|
160
|
+
stack = response['Stacks'][0]
|
161
|
+
|
162
|
+
click.echo(f"\nTest Stack: {stack_name}")
|
163
|
+
click.echo("=" * 50)
|
164
|
+
click.echo(f"Status: {stack['StackStatus']}")
|
165
|
+
click.echo(f"Created: {stack.get('CreationTime', 'N/A')}")
|
166
|
+
|
167
|
+
if 'Outputs' in stack:
|
168
|
+
click.echo("\nOutputs:")
|
169
|
+
for output in stack['Outputs']:
|
170
|
+
click.echo(f" {output['OutputKey']}: {output['OutputValue']}")
|
171
|
+
|
172
|
+
except aws.cloudformation.exceptions.ClientError as e:
|
173
|
+
if 'does not exist' in str(e):
|
174
|
+
click.echo(f"Test stack '{stack_name}' does not exist")
|
175
|
+
else:
|
176
|
+
click.echo(f"Error: {e}", err=True)
|
177
|
+
|
178
|
+
@test_stack_group.command(name="test")
|
179
|
+
@click.option("--stack-name", default="ai-lls-lib-test", help="Test stack name")
|
180
|
+
@click.option("--profile", help="AWS profile")
|
181
|
+
@click.option("--region", help="AWS region")
|
182
|
+
def run_integration_tests(stack_name, profile, region):
|
183
|
+
"""Run integration tests against test stack"""
|
184
|
+
aws = AWSClient(region=region, profile=profile)
|
185
|
+
|
186
|
+
try:
|
187
|
+
# Check stack exists
|
188
|
+
outputs = aws.get_stack_outputs(stack_name)
|
189
|
+
if not outputs:
|
190
|
+
click.echo(f"Test stack '{stack_name}' not found. Deploy it first.")
|
191
|
+
return
|
192
|
+
|
193
|
+
# Set environment variables for tests
|
194
|
+
os.environ['TEST_STACK_NAME'] = stack_name
|
195
|
+
os.environ['TEST_CACHE_TABLE'] = outputs.get('CacheTableName', '')
|
196
|
+
os.environ['TEST_BUCKET'] = outputs.get('BucketName', '')
|
197
|
+
os.environ['TEST_QUEUE_URL'] = outputs.get('QueueUrl', '')
|
198
|
+
|
199
|
+
if profile:
|
200
|
+
os.environ['AWS_PROFILE'] = profile
|
201
|
+
if region:
|
202
|
+
os.environ['AWS_REGION'] = region
|
203
|
+
|
204
|
+
click.echo(f"Running integration tests against '{stack_name}'...")
|
205
|
+
|
206
|
+
# Run pytest
|
207
|
+
cmd = ["pytest", "tests/integration", "-v"]
|
208
|
+
result = subprocess.run(cmd, cwd=os.path.dirname(template_path))
|
209
|
+
|
210
|
+
if result.returncode == 0:
|
211
|
+
click.echo("\nIntegration tests passed!")
|
212
|
+
else:
|
213
|
+
click.echo("\nSome tests failed", err=True)
|
214
|
+
|
215
|
+
except Exception as e:
|
216
|
+
click.echo(f"Error running tests: {e}", err=True)
|
@@ -1,111 +1,111 @@
|
|
1
|
-
"""
|
2
|
-
Verification commands - direct phone verification bypassing API
|
3
|
-
"""
|
4
|
-
import click
|
5
|
-
import json
|
6
|
-
from datetime import datetime
|
7
|
-
from ai_lls_lib.core.verifier import PhoneVerifier
|
8
|
-
from ai_lls_lib.core.cache import DynamoDBCache
|
9
|
-
from ai_lls_lib.cli.aws_client import AWSClient
|
10
|
-
|
11
|
-
@click.group(name="verify")
|
12
|
-
def verify_group():
|
13
|
-
"""Phone verification commands"""
|
14
|
-
pass
|
15
|
-
|
16
|
-
@verify_group.command(name="phone")
|
17
|
-
@click.argument("phone_number")
|
18
|
-
@click.option("--stack", default="landline-api", help="CloudFormation stack name")
|
19
|
-
@click.option("--skip-cache", is_flag=True, help="Skip cache lookup")
|
20
|
-
@click.option("--profile", help="AWS profile to use")
|
21
|
-
@click.option("--region", help="AWS region")
|
22
|
-
def verify_phone(phone_number, stack, skip_cache, profile, region):
|
23
|
-
"""Verify a single phone number"""
|
24
|
-
aws = AWSClient(region=region, profile=profile)
|
25
|
-
|
26
|
-
# Get cache table name from stack
|
27
|
-
cache_table = aws.get_table_name(stack, "PhoneCacheTable")
|
28
|
-
click.echo(f"Using cache table: {cache_table}")
|
29
|
-
|
30
|
-
# Initialize cache and verifier
|
31
|
-
cache = DynamoDBCache(table_name=cache_table)
|
32
|
-
verifier = PhoneVerifier(cache=cache)
|
33
|
-
|
34
|
-
try:
|
35
|
-
if skip_cache:
|
36
|
-
# Force fresh lookup
|
37
|
-
normalized = verifier.normalize_phone(phone_number)
|
38
|
-
line_type = verifier._check_line_type(normalized)
|
39
|
-
dnc = verifier._check_dnc(normalized)
|
40
|
-
result = {
|
41
|
-
"phone_number": normalized,
|
42
|
-
"line_type": line_type,
|
43
|
-
"dnc": dnc,
|
44
|
-
"cached": False,
|
45
|
-
"verified_at": datetime.utcnow().isoformat(),
|
46
|
-
"source": "cli-direct"
|
47
|
-
}
|
48
|
-
else:
|
49
|
-
result = verifier.verify(phone_number)
|
50
|
-
result = result.dict() if hasattr(result, 'dict') else result
|
51
|
-
|
52
|
-
# Display results
|
53
|
-
click.echo("\n" + "=" * 40)
|
54
|
-
click.echo(f"Phone: {result['phone_number']}")
|
55
|
-
click.echo(f"Line Type: {result['line_type']}")
|
56
|
-
click.echo(f"DNC Status: {'Yes' if result['dnc'] else 'No'}")
|
57
|
-
click.echo(f"From Cache: {'Yes' if result.get('cached') else 'No'}")
|
58
|
-
click.echo(f"Verified: {result.get('verified_at', 'Unknown')}")
|
59
|
-
click.echo("=" * 40)
|
60
|
-
|
61
|
-
if click.confirm("\nShow JSON output?"):
|
62
|
-
click.echo(json.dumps(result, indent=2, default=str))
|
63
|
-
|
64
|
-
except ValueError as e:
|
65
|
-
click.echo(f"Error: {e}", err=True)
|
66
|
-
except Exception as e:
|
67
|
-
click.echo(f"Verification failed: {e}", err=True)
|
68
|
-
|
69
|
-
@verify_group.command(name="bulk")
|
70
|
-
@click.argument("csv_file", type=click.Path(exists=True))
|
71
|
-
@click.option("--output", "-o", help="Output CSV file")
|
72
|
-
@click.option("--stack", default="landline-api", help="CloudFormation stack name")
|
73
|
-
@click.option("--profile", help="AWS profile to use")
|
74
|
-
@click.option("--region", help="AWS region")
|
75
|
-
def verify_bulk(csv_file, output, stack, profile, region):
|
76
|
-
"""Process a CSV file for bulk verification"""
|
77
|
-
from ai_lls_lib.core.processor import BulkProcessor
|
78
|
-
|
79
|
-
aws = AWSClient(region=region, profile=profile)
|
80
|
-
cache_table = aws.get_table_name(stack, "PhoneCacheTable")
|
81
|
-
|
82
|
-
cache = DynamoDBCache(table_name=cache_table)
|
83
|
-
verifier = PhoneVerifier(cache=cache)
|
84
|
-
processor = BulkProcessor(verifier=verifier)
|
85
|
-
|
86
|
-
click.echo(f"Processing {csv_file}...")
|
87
|
-
|
88
|
-
try:
|
89
|
-
# Process CSV
|
90
|
-
results = processor.process_csv_sync(csv_file)
|
91
|
-
click.echo(f"\nProcessed {len(results)} phone numbers")
|
92
|
-
|
93
|
-
# Show summary
|
94
|
-
mobile_count = sum(1 for r in results if r.line_type == "mobile")
|
95
|
-
landline_count = sum(1 for r in results if r.line_type == "landline")
|
96
|
-
dnc_count = sum(1 for r in results if r.dnc)
|
97
|
-
cached_count = sum(1 for r in results if r.cached)
|
98
|
-
|
99
|
-
click.echo("\nSummary:")
|
100
|
-
click.echo(f" Mobile: {mobile_count}")
|
101
|
-
click.echo(f" Landline: {landline_count}")
|
102
|
-
click.echo(f" On DNC: {dnc_count}")
|
103
|
-
click.echo(f" From Cache: {cached_count}")
|
104
|
-
|
105
|
-
# Generate output if requested
|
106
|
-
if output:
|
107
|
-
processor.generate_results_csv(csv_file, results, output)
|
108
|
-
click.echo(f"\nResults saved to: {output}")
|
109
|
-
|
110
|
-
except Exception as e:
|
111
|
-
click.echo(f"Bulk processing failed: {e}", err=True)
|
1
|
+
"""
|
2
|
+
Verification commands - direct phone verification bypassing API
|
3
|
+
"""
|
4
|
+
import click
|
5
|
+
import json
|
6
|
+
from datetime import datetime
|
7
|
+
from ai_lls_lib.core.verifier import PhoneVerifier
|
8
|
+
from ai_lls_lib.core.cache import DynamoDBCache
|
9
|
+
from ai_lls_lib.cli.aws_client import AWSClient
|
10
|
+
|
11
|
+
@click.group(name="verify")
|
12
|
+
def verify_group():
|
13
|
+
"""Phone verification commands"""
|
14
|
+
pass
|
15
|
+
|
16
|
+
@verify_group.command(name="phone")
|
17
|
+
@click.argument("phone_number")
|
18
|
+
@click.option("--stack", default="landline-api", help="CloudFormation stack name")
|
19
|
+
@click.option("--skip-cache", is_flag=True, help="Skip cache lookup")
|
20
|
+
@click.option("--profile", help="AWS profile to use")
|
21
|
+
@click.option("--region", help="AWS region")
|
22
|
+
def verify_phone(phone_number, stack, skip_cache, profile, region):
|
23
|
+
"""Verify a single phone number"""
|
24
|
+
aws = AWSClient(region=region, profile=profile)
|
25
|
+
|
26
|
+
# Get cache table name from stack
|
27
|
+
cache_table = aws.get_table_name(stack, "PhoneCacheTable")
|
28
|
+
click.echo(f"Using cache table: {cache_table}")
|
29
|
+
|
30
|
+
# Initialize cache and verifier
|
31
|
+
cache = DynamoDBCache(table_name=cache_table)
|
32
|
+
verifier = PhoneVerifier(cache=cache)
|
33
|
+
|
34
|
+
try:
|
35
|
+
if skip_cache:
|
36
|
+
# Force fresh lookup
|
37
|
+
normalized = verifier.normalize_phone(phone_number)
|
38
|
+
line_type = verifier._check_line_type(normalized)
|
39
|
+
dnc = verifier._check_dnc(normalized)
|
40
|
+
result = {
|
41
|
+
"phone_number": normalized,
|
42
|
+
"line_type": line_type,
|
43
|
+
"dnc": dnc,
|
44
|
+
"cached": False,
|
45
|
+
"verified_at": datetime.utcnow().isoformat(),
|
46
|
+
"source": "cli-direct"
|
47
|
+
}
|
48
|
+
else:
|
49
|
+
result = verifier.verify(phone_number)
|
50
|
+
result = result.dict() if hasattr(result, 'dict') else result
|
51
|
+
|
52
|
+
# Display results
|
53
|
+
click.echo("\n" + "=" * 40)
|
54
|
+
click.echo(f"Phone: {result['phone_number']}")
|
55
|
+
click.echo(f"Line Type: {result['line_type']}")
|
56
|
+
click.echo(f"DNC Status: {'Yes' if result['dnc'] else 'No'}")
|
57
|
+
click.echo(f"From Cache: {'Yes' if result.get('cached') else 'No'}")
|
58
|
+
click.echo(f"Verified: {result.get('verified_at', 'Unknown')}")
|
59
|
+
click.echo("=" * 40)
|
60
|
+
|
61
|
+
if click.confirm("\nShow JSON output?"):
|
62
|
+
click.echo(json.dumps(result, indent=2, default=str))
|
63
|
+
|
64
|
+
except ValueError as e:
|
65
|
+
click.echo(f"Error: {e}", err=True)
|
66
|
+
except Exception as e:
|
67
|
+
click.echo(f"Verification failed: {e}", err=True)
|
68
|
+
|
69
|
+
@verify_group.command(name="bulk")
|
70
|
+
@click.argument("csv_file", type=click.Path(exists=True))
|
71
|
+
@click.option("--output", "-o", help="Output CSV file")
|
72
|
+
@click.option("--stack", default="landline-api", help="CloudFormation stack name")
|
73
|
+
@click.option("--profile", help="AWS profile to use")
|
74
|
+
@click.option("--region", help="AWS region")
|
75
|
+
def verify_bulk(csv_file, output, stack, profile, region):
|
76
|
+
"""Process a CSV file for bulk verification"""
|
77
|
+
from ai_lls_lib.core.processor import BulkProcessor
|
78
|
+
|
79
|
+
aws = AWSClient(region=region, profile=profile)
|
80
|
+
cache_table = aws.get_table_name(stack, "PhoneCacheTable")
|
81
|
+
|
82
|
+
cache = DynamoDBCache(table_name=cache_table)
|
83
|
+
verifier = PhoneVerifier(cache=cache)
|
84
|
+
processor = BulkProcessor(verifier=verifier)
|
85
|
+
|
86
|
+
click.echo(f"Processing {csv_file}...")
|
87
|
+
|
88
|
+
try:
|
89
|
+
# Process CSV
|
90
|
+
results = processor.process_csv_sync(csv_file)
|
91
|
+
click.echo(f"\nProcessed {len(results)} phone numbers")
|
92
|
+
|
93
|
+
# Show summary
|
94
|
+
mobile_count = sum(1 for r in results if r.line_type == "mobile")
|
95
|
+
landline_count = sum(1 for r in results if r.line_type == "landline")
|
96
|
+
dnc_count = sum(1 for r in results if r.dnc)
|
97
|
+
cached_count = sum(1 for r in results if r.cached)
|
98
|
+
|
99
|
+
click.echo("\nSummary:")
|
100
|
+
click.echo(f" Mobile: {mobile_count}")
|
101
|
+
click.echo(f" Landline: {landline_count}")
|
102
|
+
click.echo(f" On DNC: {dnc_count}")
|
103
|
+
click.echo(f" From Cache: {cached_count}")
|
104
|
+
|
105
|
+
# Generate output if requested
|
106
|
+
if output:
|
107
|
+
processor.generate_results_csv(csv_file, results, output)
|
108
|
+
click.echo(f"\nResults saved to: {output}")
|
109
|
+
|
110
|
+
except Exception as e:
|
111
|
+
click.echo(f"Bulk processing failed: {e}", err=True)
|