amplify-excel-migrator 1.1.5__py3-none-any.whl → 1.2.15__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.
Files changed (45) hide show
  1. amplify_excel_migrator/__init__.py +17 -0
  2. amplify_excel_migrator/auth/__init__.py +6 -0
  3. amplify_excel_migrator/auth/cognito_auth.py +306 -0
  4. amplify_excel_migrator/auth/provider.py +42 -0
  5. amplify_excel_migrator/cli/__init__.py +5 -0
  6. amplify_excel_migrator/cli/commands.py +165 -0
  7. amplify_excel_migrator/client.py +47 -0
  8. amplify_excel_migrator/core/__init__.py +5 -0
  9. amplify_excel_migrator/core/config.py +98 -0
  10. amplify_excel_migrator/data/__init__.py +7 -0
  11. amplify_excel_migrator/data/excel_reader.py +23 -0
  12. amplify_excel_migrator/data/transformer.py +119 -0
  13. amplify_excel_migrator/data/validator.py +48 -0
  14. amplify_excel_migrator/graphql/__init__.py +8 -0
  15. amplify_excel_migrator/graphql/client.py +137 -0
  16. amplify_excel_migrator/graphql/executor.py +405 -0
  17. amplify_excel_migrator/graphql/mutation_builder.py +80 -0
  18. amplify_excel_migrator/graphql/query_builder.py +194 -0
  19. amplify_excel_migrator/migration/__init__.py +8 -0
  20. amplify_excel_migrator/migration/batch_uploader.py +23 -0
  21. amplify_excel_migrator/migration/failure_tracker.py +92 -0
  22. amplify_excel_migrator/migration/orchestrator.py +143 -0
  23. amplify_excel_migrator/migration/progress_reporter.py +57 -0
  24. amplify_excel_migrator/schema/__init__.py +6 -0
  25. model_field_parser.py → amplify_excel_migrator/schema/field_parser.py +100 -22
  26. amplify_excel_migrator/schema/introspector.py +95 -0
  27. {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/METADATA +121 -26
  28. amplify_excel_migrator-1.2.15.dist-info/RECORD +40 -0
  29. amplify_excel_migrator-1.2.15.dist-info/entry_points.txt +2 -0
  30. amplify_excel_migrator-1.2.15.dist-info/top_level.txt +2 -0
  31. tests/__init__.py +1 -0
  32. tests/test_cli_commands.py +292 -0
  33. tests/test_client.py +187 -0
  34. tests/test_cognito_auth.py +363 -0
  35. tests/test_config_manager.py +347 -0
  36. tests/test_field_parser.py +615 -0
  37. tests/test_mutation_builder.py +391 -0
  38. tests/test_query_builder.py +384 -0
  39. amplify_client.py +0 -941
  40. amplify_excel_migrator-1.1.5.dist-info/RECORD +0 -9
  41. amplify_excel_migrator-1.1.5.dist-info/entry_points.txt +0 -2
  42. amplify_excel_migrator-1.1.5.dist-info/top_level.txt +0 -3
  43. migrator.py +0 -437
  44. {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/WHEEL +0 -0
  45. {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,17 @@
1
+ """Amplify Excel Migrator - Migrate Excel data to AWS Amplify GraphQL API."""
2
+
3
+ __version__ = "1.2.5"
4
+
5
+ from amplify_excel_migrator.client import AmplifyClient
6
+ from amplify_excel_migrator.migration import MigrationOrchestrator
7
+ from amplify_excel_migrator.auth import AuthenticationProvider, CognitoAuthProvider
8
+ from amplify_excel_migrator.core import ConfigManager
9
+
10
+ __all__ = [
11
+ "AmplifyClient",
12
+ "MigrationOrchestrator",
13
+ "AuthenticationProvider",
14
+ "CognitoAuthProvider",
15
+ "ConfigManager",
16
+ "__version__",
17
+ ]
@@ -0,0 +1,6 @@
1
+ """Authentication module for Amplify Excel Migrator."""
2
+
3
+ from .provider import AuthenticationProvider
4
+ from .cognito_auth import CognitoAuthProvider
5
+
6
+ __all__ = ["AuthenticationProvider", "CognitoAuthProvider"]
@@ -0,0 +1,306 @@
1
+ """AWS Cognito authentication provider implementation."""
2
+
3
+ import logging
4
+ from typing import Optional
5
+ from getpass import getpass
6
+
7
+ import boto3
8
+ import jwt
9
+ from botocore.exceptions import NoCredentialsError, ProfileNotFound, NoRegionError, ClientError
10
+ from pycognito import Cognito, MFAChallengeException
11
+ from pycognito.exceptions import ForceChangePasswordException
12
+
13
+ from .provider import AuthenticationProvider
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class CognitoAuthProvider(AuthenticationProvider):
19
+ """AWS Cognito authentication provider."""
20
+
21
+ def __init__(
22
+ self,
23
+ user_pool_id: str,
24
+ client_id: str,
25
+ region: str,
26
+ admin_group_name: str = "ADMINS",
27
+ ):
28
+ """
29
+ Initialize the Cognito authentication provider.
30
+
31
+ Args:
32
+ user_pool_id: Cognito User Pool ID
33
+ client_id: Cognito App Client ID
34
+ region: AWS region
35
+ admin_group_name: Name of the admin group to check membership
36
+ """
37
+ self.user_pool_id = user_pool_id
38
+ self.client_id = client_id
39
+ self.region = region
40
+ self.admin_group_name = admin_group_name
41
+
42
+ self.cognito_client: Optional[Cognito] = None
43
+ self.boto_cognito_admin_client: Optional[any] = None
44
+ self._id_token: Optional[str] = None
45
+ self._mfa_tokens: Optional[dict] = None
46
+
47
+ def authenticate(self, username: str, password: str, mfa_code: Optional[str] = None) -> bool:
48
+ """
49
+ Authenticate using standard Cognito user authentication.
50
+
51
+ Args:
52
+ username: User's username or email
53
+ password: User's password
54
+ mfa_code: Optional MFA code if MFA is enabled
55
+
56
+ Returns:
57
+ True if authentication successful, False otherwise
58
+ """
59
+ try:
60
+ if not self.cognito_client:
61
+ self._init_cognito_client(username)
62
+
63
+ if mfa_code and self._mfa_tokens:
64
+ if not self._complete_mfa_challenge(mfa_code):
65
+ return False
66
+ else:
67
+ self.cognito_client.authenticate(password=password)
68
+
69
+ self._id_token = self.cognito_client.id_token
70
+
71
+ self._check_user_in_admins_group(self._id_token)
72
+
73
+ logger.info("✅ Authentication successful")
74
+ return True
75
+
76
+ except MFAChallengeException as e:
77
+ logger.warning("MFA required")
78
+ if hasattr(e, "get_tokens"):
79
+ self._mfa_tokens = e.get_tokens()
80
+
81
+ mfa_code = input("Enter MFA code: ").strip()
82
+ if mfa_code:
83
+ return self.authenticate(username, password, mfa_code)
84
+ else:
85
+ logger.error("MFA code required but not provided")
86
+ return False
87
+ else:
88
+ logger.error("MFA challenge received but no session tokens available")
89
+ return False
90
+
91
+ except ForceChangePasswordException:
92
+ logger.warning("Password change required")
93
+ new_password = input("Your password has expired. Enter new password: ").strip()
94
+ confirm_password = input("Confirm new password: ").strip()
95
+ if new_password != confirm_password:
96
+ logger.error("Passwords do not match")
97
+ return False
98
+
99
+ try:
100
+ self.cognito_client.new_password_challenge(password, new_password)
101
+ return self.authenticate(username, new_password)
102
+
103
+ except Exception as e:
104
+ logger.error(f"Failed to change password: {e}")
105
+ return False
106
+
107
+ except Exception as e:
108
+ logger.error(f"Authentication failed: {e}")
109
+ return False
110
+
111
+ def authenticate_admin(self, username: str, password: str, aws_profile: Optional[str] = None) -> bool:
112
+ """
113
+ Authenticate using AWS admin credentials (ADMIN_USER_PASSWORD_AUTH flow).
114
+
115
+ Requires AWS credentials with cognito-idp:ListUserPoolClients permission.
116
+
117
+ Args:
118
+ username: User's username or email
119
+ password: User's password
120
+ aws_profile: Optional AWS profile name
121
+
122
+ Returns:
123
+ True if authentication successful, False otherwise
124
+ """
125
+ try:
126
+ if not self.boto_cognito_admin_client:
127
+ self._init_boto_admin_client(aws_profile)
128
+
129
+ logger.info(f"Authenticating {username} using ADMIN_USER_PASSWORD_AUTH flow...")
130
+
131
+ response = self.boto_cognito_admin_client.admin_initiate_auth(
132
+ UserPoolId=self.user_pool_id,
133
+ ClientId=self.client_id,
134
+ AuthFlow="ADMIN_USER_PASSWORD_AUTH",
135
+ AuthParameters={"USERNAME": username, "PASSWORD": password},
136
+ )
137
+
138
+ self._check_for_mfa_challenges(response, username)
139
+
140
+ if "AuthenticationResult" in response:
141
+ self._id_token = response["AuthenticationResult"]["IdToken"]
142
+ else:
143
+ logger.error("❌ Authentication failed: No AuthenticationResult in response")
144
+ return False
145
+
146
+ self._check_user_in_admins_group(self._id_token)
147
+
148
+ logger.info("✅ Authentication successful")
149
+ return True
150
+
151
+ except Exception as e:
152
+ if hasattr(e, "response"):
153
+ error_code = e.response.get("Error", {}).get("Code", "Unknown")
154
+ if error_code == "NotAuthorizedException":
155
+ logger.error(f"❌ Authentication failed: {e}")
156
+ elif error_code == "UserNotFoundException":
157
+ logger.error(f"❌ User not found: {username}")
158
+ else:
159
+ logger.error(f"❌ Error during authentication: {e}")
160
+ else:
161
+ logger.error(f"❌ Error during authentication: {e}")
162
+ return False
163
+
164
+ def get_id_token(self) -> Optional[str]:
165
+ """
166
+ Get the ID token from the last successful authentication.
167
+
168
+ Returns:
169
+ ID token string if authenticated, None otherwise
170
+ """
171
+ return self._id_token
172
+
173
+ def is_authenticated(self) -> bool:
174
+ """
175
+ Check if the provider is currently authenticated.
176
+
177
+ Returns:
178
+ True if authenticated, False otherwise
179
+ """
180
+ return self._id_token is not None
181
+
182
+ def _init_cognito_client(self, username: str) -> None:
183
+ """Initialize the standard Cognito client."""
184
+ try:
185
+ self.cognito_client = Cognito(
186
+ user_pool_id=self.user_pool_id,
187
+ client_id=self.client_id,
188
+ user_pool_region=self.region,
189
+ username=username,
190
+ )
191
+ except ValueError as e:
192
+ logger.error(f"Invalid parameter: {e}")
193
+ raise
194
+
195
+ def _init_boto_admin_client(self, aws_profile: Optional[str] = None) -> None:
196
+ """Initialize the boto3 Cognito admin client."""
197
+ try:
198
+ if aws_profile:
199
+ session = boto3.Session(profile_name=aws_profile)
200
+ self.boto_cognito_admin_client = session.client("cognito-idp", region_name=self.region)
201
+ else:
202
+ # Use default AWS credentials (from ~/.aws/credentials, env vars, or IAM role)
203
+ self.boto_cognito_admin_client = boto3.client("cognito-idp", region_name=self.region)
204
+
205
+ except NoCredentialsError:
206
+ logger.error("AWS credentials not found. Please configure AWS credentials.")
207
+ logger.error("Options: 1) AWS CLI: 'aws configure', 2) Environment variables, 3) Pass credentials directly")
208
+ raise RuntimeError(
209
+ "Failed to initialize client: No AWS credentials found. "
210
+ "Run 'aws configure' or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables."
211
+ )
212
+
213
+ except ProfileNotFound:
214
+ logger.error(f"AWS profile '{aws_profile}' not found")
215
+ raise RuntimeError(
216
+ f"Failed to initialize client: AWS profile '{aws_profile}' not found. "
217
+ f"Available profiles can be found in ~/.aws/credentials"
218
+ )
219
+
220
+ except NoRegionError:
221
+ logger.error("No AWS region specified")
222
+ raise RuntimeError(
223
+ "Failed to initialize client: No AWS region specified. "
224
+ "Provide region parameter or set AWS_DEFAULT_REGION environment variable."
225
+ )
226
+
227
+ except ClientError as e:
228
+ error_code = e.response.get("Error", {}).get("Code", "Unknown")
229
+ error_msg = e.response.get("Error", {}).get("Message", str(e))
230
+ logger.error(f"AWS Client Error [{error_code}]: {error_msg}")
231
+ raise RuntimeError(f"Failed to initialize client: AWS error [{error_code}]: {error_msg}")
232
+
233
+ except Exception as e:
234
+ logger.error(f"Error during client initialization: {e}")
235
+ raise RuntimeError(f"Failed to initialize client: {e}")
236
+
237
+ def _complete_mfa_challenge(self, mfa_code: str) -> bool:
238
+ """Complete MFA challenge."""
239
+ try:
240
+ if not self._mfa_tokens:
241
+ logger.error("No MFA session tokens available")
242
+ return False
243
+
244
+ challenge_name = self._mfa_tokens.get("ChallengeName", "SMS_MFA")
245
+
246
+ if "SOFTWARE_TOKEN" in challenge_name:
247
+ self.cognito_client.respond_to_software_token_mfa_challenge(code=mfa_code, mfa_tokens=self._mfa_tokens)
248
+ else:
249
+ self.cognito_client.respond_to_sms_mfa_challenge(code=mfa_code, mfa_tokens=self._mfa_tokens)
250
+
251
+ logger.info("✅ MFA challenge successful")
252
+ return True
253
+
254
+ except Exception as e:
255
+ logger.error(f"MFA challenge failed: {e}")
256
+ return False
257
+
258
+ def _check_for_mfa_challenges(self, response: dict, username: str) -> bool:
259
+ """Check and handle MFA challenges in admin auth response."""
260
+ if "ChallengeName" in response:
261
+ challenge = response["ChallengeName"]
262
+
263
+ if challenge == "MFA_SETUP":
264
+ logger.error("MFA setup required")
265
+ return False
266
+
267
+ elif challenge == "SMS_MFA" or challenge == "SOFTWARE_TOKEN_MFA":
268
+ mfa_code = input("Enter MFA code: ")
269
+ _ = self.boto_cognito_admin_client.admin_respond_to_auth_challenge(
270
+ UserPoolId=self.user_pool_id,
271
+ ClientId=self.client_id,
272
+ ChallengeName=challenge,
273
+ Session=response["Session"],
274
+ ChallengeResponses={
275
+ "USERNAME": username,
276
+ "SMS_MFA_CODE" if challenge == "SMS_MFA" else "SOFTWARE_TOKEN_MFA_CODE": mfa_code,
277
+ },
278
+ )
279
+
280
+ elif challenge == "NEW_PASSWORD_REQUIRED":
281
+ new_password = getpass("Enter new password: ")
282
+ _ = self.boto_cognito_admin_client.admin_respond_to_auth_challenge(
283
+ UserPoolId=self.user_pool_id,
284
+ ClientId=self.client_id,
285
+ ChallengeName=challenge,
286
+ Session=response["Session"],
287
+ ChallengeResponses={"USERNAME": username, "NEW_PASSWORD": new_password},
288
+ )
289
+
290
+ return False
291
+
292
+ def _check_user_in_admins_group(self, id_token: str) -> None:
293
+ """
294
+ Check if user belongs to the admin group.
295
+
296
+ Args:
297
+ id_token: JWT ID token
298
+
299
+ Raises:
300
+ PermissionError: If user is not in admin group
301
+ """
302
+ claims = jwt.decode(id_token, options={"verify_signature": False})
303
+ groups = claims.get("cognito:groups", [])
304
+
305
+ if self.admin_group_name not in groups:
306
+ raise PermissionError(f"User is not in {self.admin_group_name} group")
@@ -0,0 +1,42 @@
1
+ """Abstract authentication provider interface."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Optional
5
+
6
+
7
+ class AuthenticationProvider(ABC):
8
+ """Abstract base class for authentication providers."""
9
+
10
+ @abstractmethod
11
+ def authenticate(self, username: str, password: str) -> bool:
12
+ """
13
+ Authenticate a user with username and password.
14
+
15
+ Args:
16
+ username: User's username or email
17
+ password: User's password
18
+
19
+ Returns:
20
+ True if authentication successful, False otherwise
21
+ """
22
+ pass
23
+
24
+ @abstractmethod
25
+ def get_id_token(self) -> Optional[str]:
26
+ """
27
+ Get the ID token from the last successful authentication.
28
+
29
+ Returns:
30
+ ID token string if authenticated, None otherwise
31
+ """
32
+ pass
33
+
34
+ @abstractmethod
35
+ def is_authenticated(self) -> bool:
36
+ """
37
+ Check if the provider is currently authenticated.
38
+
39
+ Returns:
40
+ True if authenticated, False otherwise
41
+ """
42
+ pass
@@ -0,0 +1,5 @@
1
+ """CLI module for Amplify Excel Migrator."""
2
+
3
+ from .commands import cmd_show, cmd_config, cmd_migrate, main
4
+
5
+ __all__ = ["cmd_show", "cmd_config", "cmd_migrate", "main"]
@@ -0,0 +1,165 @@
1
+ """CLI command handlers for Amplify Excel Migrator."""
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ from amplify_excel_migrator.client import AmplifyClient
7
+ from amplify_excel_migrator.core import ConfigManager
8
+ from amplify_excel_migrator.schema import FieldParser
9
+ from amplify_excel_migrator.data import ExcelReader, DataTransformer
10
+ from amplify_excel_migrator.migration import (
11
+ FailureTracker,
12
+ ProgressReporter,
13
+ BatchUploader,
14
+ MigrationOrchestrator,
15
+ )
16
+
17
+
18
+ def cmd_show(args=None):
19
+ print(
20
+ """
21
+ ╔════════════════════════════════════════════════════╗
22
+ ║ Amplify Migrator - Current Configuration ║
23
+ ╚════════════════════════════════════════════════════╝
24
+ """
25
+ )
26
+
27
+ config_manager = ConfigManager()
28
+ cached_config = config_manager.load()
29
+
30
+ if not cached_config:
31
+ print("\n❌ No configuration found!")
32
+ print("💡 Run 'amplify-migrator config' first to set up your configuration.")
33
+ return
34
+
35
+ print("\n📋 Cached Configuration:")
36
+ print("-" * 54)
37
+ print(f"Excel file path: {cached_config.get('excel_path', 'N/A')}")
38
+ print(f"API endpoint: {cached_config.get('api_endpoint', 'N/A')}")
39
+ print(f"AWS Region: {cached_config.get('region', 'N/A')}")
40
+ print(f"User Pool ID: {cached_config.get('user_pool_id', 'N/A')}")
41
+ print(f"Client ID: {cached_config.get('client_id', 'N/A')}")
42
+ print(f"Admin Username: {cached_config.get('username', 'N/A')}")
43
+ print("-" * 54)
44
+ print(f"\n📍 Config location: {config_manager.config_path}")
45
+ print(f"💡 Run 'amplify-migrator config' to update configuration.")
46
+
47
+
48
+ def cmd_config(args=None):
49
+ print(
50
+ """
51
+ ╔════════════════════════════════════════════════════╗
52
+ ║ Amplify Migrator - Configuration Setup ║
53
+ ╚════════════════════════════════════════════════════╝
54
+ """
55
+ )
56
+
57
+ config_manager = ConfigManager()
58
+ cached_config = config_manager.load()
59
+
60
+ config = {
61
+ "excel_path": config_manager.prompt_for_value("Excel file path", cached_config.get("excel_path", "")),
62
+ "api_endpoint": config_manager.prompt_for_value(
63
+ "AWS Amplify API endpoint", cached_config.get("api_endpoint", "")
64
+ ),
65
+ "region": config_manager.prompt_for_value("AWS Region", cached_config.get("region", "")),
66
+ "user_pool_id": config_manager.prompt_for_value("Cognito User Pool ID", cached_config.get("user_pool_id", "")),
67
+ "client_id": config_manager.prompt_for_value("Cognito Client ID", cached_config.get("client_id", "")),
68
+ "username": config_manager.prompt_for_value("Admin Username", cached_config.get("username", "")),
69
+ }
70
+
71
+ config_manager.save(config)
72
+ print("\n✅ Configuration saved successfully!")
73
+ print(f"💡 You can now run 'amplify-migrator migrate' to start the migration.")
74
+
75
+
76
+ def cmd_migrate(args=None):
77
+ print(
78
+ """
79
+ ╔════════════════════════════════════════════════════╗
80
+ ║ Migrator Tool for Amplify ║
81
+ ╠════════════════════════════════════════════════════╣
82
+ ║ This tool requires admin privileges to execute ║
83
+ ╚════════════════════════════════════════════════════╝
84
+ """
85
+ )
86
+
87
+ config_manager = ConfigManager()
88
+ cached_config = config_manager.load()
89
+
90
+ if not cached_config:
91
+ print("\n❌ No configuration found!")
92
+ print("💡 Run 'amplify-migrator config' first to set up your configuration.")
93
+ sys.exit(1)
94
+
95
+ excel_path = config_manager.get_or_prompt("excel_path", "Excel file path", "data.xlsx")
96
+ api_endpoint = config_manager.get_or_prompt("api_endpoint", "AWS Amplify API endpoint")
97
+ region = config_manager.get_or_prompt("region", "AWS Region", "us-east-1")
98
+ user_pool_id = config_manager.get_or_prompt("user_pool_id", "Cognito User Pool ID")
99
+ client_id = config_manager.get_or_prompt("client_id", "Cognito Client ID")
100
+ username = config_manager.get_or_prompt("username", "Admin Username")
101
+
102
+ print("\n🔐 Authentication:")
103
+ print("-" * 54)
104
+ password = config_manager.prompt_for_value("Admin Password", secret=True)
105
+
106
+ from amplify_excel_migrator.auth import CognitoAuthProvider
107
+
108
+ auth_provider = CognitoAuthProvider(
109
+ user_pool_id=user_pool_id,
110
+ client_id=client_id,
111
+ region=region,
112
+ )
113
+
114
+ amplify_client = AmplifyClient(
115
+ api_endpoint=api_endpoint,
116
+ auth_provider=auth_provider,
117
+ )
118
+
119
+ if not auth_provider.authenticate(username, password):
120
+ return
121
+
122
+ excel_reader = ExcelReader(excel_path)
123
+ field_parser = FieldParser()
124
+ data_transformer = DataTransformer(field_parser)
125
+ failure_tracker = FailureTracker()
126
+ progress_reporter = ProgressReporter()
127
+ batch_uploader = BatchUploader(amplify_client)
128
+
129
+ orchestrator = MigrationOrchestrator(
130
+ excel_reader=excel_reader,
131
+ data_transformer=data_transformer,
132
+ amplify_client=amplify_client,
133
+ failure_tracker=failure_tracker,
134
+ progress_reporter=progress_reporter,
135
+ batch_uploader=batch_uploader,
136
+ field_parser=field_parser,
137
+ )
138
+
139
+ orchestrator.run()
140
+
141
+
142
+ def main():
143
+ parser = argparse.ArgumentParser(
144
+ description="Amplify Excel Migrator - Migrate Excel data to AWS Amplify GraphQL API",
145
+ formatter_class=argparse.RawDescriptionHelpFormatter,
146
+ )
147
+
148
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
149
+
150
+ config_parser = subparsers.add_parser("config", help="Configure the migration tool")
151
+ config_parser.set_defaults(func=cmd_config)
152
+
153
+ show_parser = subparsers.add_parser("show", help="Show current configuration")
154
+ show_parser.set_defaults(func=cmd_show)
155
+
156
+ migrate_parser = subparsers.add_parser("migrate", help="Run the migration")
157
+ migrate_parser.set_defaults(func=cmd_migrate)
158
+
159
+ args = parser.parse_args()
160
+
161
+ if args.command is None:
162
+ parser.print_help()
163
+ sys.exit(1)
164
+
165
+ args.func(args)
@@ -0,0 +1,47 @@
1
+ import logging
2
+ from typing import Dict, Any, Optional, List
3
+
4
+ from amplify_excel_migrator.graphql import GraphQLClient, QueryExecutor
5
+ from amplify_excel_migrator.auth import AuthenticationProvider
6
+
7
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class AmplifyClient:
12
+ """
13
+ Simplified client for Amplify GraphQL API operations.
14
+
15
+ Provides a clean interface for schema introspection, foreign key lookup,
16
+ and batch uploading. Delegates to GraphQLClient and QueryExecutor.
17
+ """
18
+
19
+ def __init__(self, api_endpoint: str, auth_provider: Optional[AuthenticationProvider] = None):
20
+ self.api_endpoint = api_endpoint
21
+ self._auth_provider = auth_provider
22
+
23
+ self._client = GraphQLClient(api_endpoint, auth_provider)
24
+ self._executor = QueryExecutor(self._client, batch_size=20)
25
+
26
+ @property
27
+ def auth_provider(self) -> Optional[AuthenticationProvider]:
28
+ return self._auth_provider
29
+
30
+ @auth_provider.setter
31
+ def auth_provider(self, value: Optional[AuthenticationProvider]):
32
+ self._auth_provider = value
33
+ self._client.auth_provider = value
34
+
35
+ def get_model_structure(self, model_type: str) -> Dict[str, Any]:
36
+ return self._executor.get_model_structure(model_type)
37
+
38
+ def get_primary_field_name(self, model_name: str, parsed_model_structure: Dict[str, Any]) -> tuple[str, bool, str]:
39
+ return self._executor.get_primary_field_name(model_name, parsed_model_structure)
40
+
41
+ def build_foreign_key_lookups(self, df, parsed_model_structure: Dict[str, Any]) -> Dict[str, Dict[str, str]]:
42
+ return self._executor.build_foreign_key_lookups(df, parsed_model_structure)
43
+
44
+ def upload(
45
+ self, records: List[Dict], model_name: str, parsed_model_structure: Dict[str, Any]
46
+ ) -> tuple[int, int, List[Dict]]:
47
+ return self._executor.upload(records, model_name, parsed_model_structure)
@@ -0,0 +1,5 @@
1
+ """Core module for configuration and utilities."""
2
+
3
+ from .config import ConfigManager
4
+
5
+ __all__ = ["ConfigManager"]