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.
- amplify_excel_migrator/__init__.py +17 -0
- amplify_excel_migrator/auth/__init__.py +6 -0
- amplify_excel_migrator/auth/cognito_auth.py +306 -0
- amplify_excel_migrator/auth/provider.py +42 -0
- amplify_excel_migrator/cli/__init__.py +5 -0
- amplify_excel_migrator/cli/commands.py +165 -0
- amplify_excel_migrator/client.py +47 -0
- amplify_excel_migrator/core/__init__.py +5 -0
- amplify_excel_migrator/core/config.py +98 -0
- amplify_excel_migrator/data/__init__.py +7 -0
- amplify_excel_migrator/data/excel_reader.py +23 -0
- amplify_excel_migrator/data/transformer.py +119 -0
- amplify_excel_migrator/data/validator.py +48 -0
- amplify_excel_migrator/graphql/__init__.py +8 -0
- amplify_excel_migrator/graphql/client.py +137 -0
- amplify_excel_migrator/graphql/executor.py +405 -0
- amplify_excel_migrator/graphql/mutation_builder.py +80 -0
- amplify_excel_migrator/graphql/query_builder.py +194 -0
- amplify_excel_migrator/migration/__init__.py +8 -0
- amplify_excel_migrator/migration/batch_uploader.py +23 -0
- amplify_excel_migrator/migration/failure_tracker.py +92 -0
- amplify_excel_migrator/migration/orchestrator.py +143 -0
- amplify_excel_migrator/migration/progress_reporter.py +57 -0
- amplify_excel_migrator/schema/__init__.py +6 -0
- model_field_parser.py → amplify_excel_migrator/schema/field_parser.py +100 -22
- amplify_excel_migrator/schema/introspector.py +95 -0
- {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/METADATA +121 -26
- amplify_excel_migrator-1.2.15.dist-info/RECORD +40 -0
- amplify_excel_migrator-1.2.15.dist-info/entry_points.txt +2 -0
- amplify_excel_migrator-1.2.15.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_cli_commands.py +292 -0
- tests/test_client.py +187 -0
- tests/test_cognito_auth.py +363 -0
- tests/test_config_manager.py +347 -0
- tests/test_field_parser.py +615 -0
- tests/test_mutation_builder.py +391 -0
- tests/test_query_builder.py +384 -0
- amplify_client.py +0 -941
- amplify_excel_migrator-1.1.5.dist-info/RECORD +0 -9
- amplify_excel_migrator-1.1.5.dist-info/entry_points.txt +0 -2
- amplify_excel_migrator-1.1.5.dist-info/top_level.txt +0 -3
- migrator.py +0 -437
- {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/WHEEL +0 -0
- {amplify_excel_migrator-1.1.5.dist-info → amplify_excel_migrator-1.2.15.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Configuration management for Amplify Excel Migrator."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from getpass import getpass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Any, Optional
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConfigManager:
|
|
13
|
+
"""Manages configuration loading, saving, and user prompts."""
|
|
14
|
+
|
|
15
|
+
DEFAULT_CONFIG_DIR = Path.home() / ".amplify-migrator"
|
|
16
|
+
DEFAULT_CONFIG_FILE = "config.json"
|
|
17
|
+
|
|
18
|
+
SENSITIVE_KEYS = {"password", "ADMIN_PASSWORD"}
|
|
19
|
+
|
|
20
|
+
def __init__(self, config_path: Optional[str] = None):
|
|
21
|
+
if config_path:
|
|
22
|
+
self.config_path = Path(config_path)
|
|
23
|
+
else:
|
|
24
|
+
self.config_path = self.DEFAULT_CONFIG_DIR / self.DEFAULT_CONFIG_FILE
|
|
25
|
+
|
|
26
|
+
self._config: Dict[str, Any] = {}
|
|
27
|
+
|
|
28
|
+
def load(self) -> Dict[str, Any]:
|
|
29
|
+
if not self.config_path.exists():
|
|
30
|
+
logger.debug(f"Config file not found at {self.config_path}")
|
|
31
|
+
return {}
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
with open(self.config_path, "r") as f:
|
|
35
|
+
self._config = json.load(f)
|
|
36
|
+
logger.debug(f"Loaded configuration from {self.config_path}")
|
|
37
|
+
return self._config
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.warning(f"Failed to load cached config: {e}")
|
|
40
|
+
return {}
|
|
41
|
+
|
|
42
|
+
def save(self, config: Dict[str, Any]) -> None:
|
|
43
|
+
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
|
|
45
|
+
sanitized_config = {k: v for k, v in config.items() if k not in self.SENSITIVE_KEYS}
|
|
46
|
+
|
|
47
|
+
with open(self.config_path, "w") as f:
|
|
48
|
+
json.dump(sanitized_config, f, indent=2)
|
|
49
|
+
|
|
50
|
+
self._config = sanitized_config
|
|
51
|
+
logger.info(f"✅ Configuration saved to {self.config_path}")
|
|
52
|
+
|
|
53
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
54
|
+
if not self._config:
|
|
55
|
+
self.load()
|
|
56
|
+
return self._config.get(key, default)
|
|
57
|
+
|
|
58
|
+
def set(self, key: str, value: Any) -> None:
|
|
59
|
+
if not self._config:
|
|
60
|
+
self.load()
|
|
61
|
+
self._config[key] = value
|
|
62
|
+
|
|
63
|
+
def update(self, updates: Dict[str, Any]) -> None:
|
|
64
|
+
if not self._config:
|
|
65
|
+
self.load()
|
|
66
|
+
self._config.update(updates)
|
|
67
|
+
self.save(self._config)
|
|
68
|
+
|
|
69
|
+
def prompt_for_value(self, prompt_text: str, default: str = "", secret: bool = False) -> str:
|
|
70
|
+
if default:
|
|
71
|
+
display_prompt = f"{prompt_text} [{default}]: "
|
|
72
|
+
else:
|
|
73
|
+
display_prompt = f"{prompt_text}: "
|
|
74
|
+
|
|
75
|
+
if secret:
|
|
76
|
+
value = getpass(display_prompt)
|
|
77
|
+
else:
|
|
78
|
+
value = input(display_prompt)
|
|
79
|
+
|
|
80
|
+
return value.strip() if value.strip() else default
|
|
81
|
+
|
|
82
|
+
def get_or_prompt(self, key: str, prompt_text: str, default: str = "", secret: bool = False) -> str:
|
|
83
|
+
if not self._config:
|
|
84
|
+
self.load()
|
|
85
|
+
|
|
86
|
+
if key in self._config:
|
|
87
|
+
return self._config[key]
|
|
88
|
+
|
|
89
|
+
return self.prompt_for_value(prompt_text, default, secret)
|
|
90
|
+
|
|
91
|
+
def exists(self) -> bool:
|
|
92
|
+
return self.config_path.exists()
|
|
93
|
+
|
|
94
|
+
def clear(self) -> None:
|
|
95
|
+
self._config = {}
|
|
96
|
+
if self.config_path.exists():
|
|
97
|
+
self.config_path.unlink()
|
|
98
|
+
logger.info(f"Configuration cleared from {self.config_path}")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Excel file reading functionality."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ExcelReader:
|
|
12
|
+
def __init__(self, file_path: str):
|
|
13
|
+
self.file_path = file_path
|
|
14
|
+
|
|
15
|
+
def read_all_sheets(self) -> Dict[str, pd.DataFrame]:
|
|
16
|
+
logger.info(f"Reading Excel file: {self.file_path}")
|
|
17
|
+
all_sheets = pd.read_excel(self.file_path, sheet_name=None)
|
|
18
|
+
logger.info(f"Loaded {len(all_sheets)} sheets from Excel")
|
|
19
|
+
return all_sheets
|
|
20
|
+
|
|
21
|
+
def read_sheet(self, sheet_name: str) -> pd.DataFrame:
|
|
22
|
+
logger.info(f"Reading sheet '{sheet_name}' from Excel file: {self.file_path}")
|
|
23
|
+
return pd.read_excel(self.file_path, sheet_name=sheet_name)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Data transformation from Excel rows to Amplify records."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
from typing import Dict, Any, Optional, List, Tuple
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from amplify_excel_migrator.schema import FieldParser
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DataTransformer:
|
|
15
|
+
def __init__(self, field_parser: FieldParser):
|
|
16
|
+
self.field_parser = field_parser
|
|
17
|
+
|
|
18
|
+
def transform_rows_to_records(
|
|
19
|
+
self,
|
|
20
|
+
df: pd.DataFrame,
|
|
21
|
+
parsed_model_structure: Dict[str, Any],
|
|
22
|
+
primary_field: str,
|
|
23
|
+
fk_lookup_cache: Dict[str, Dict[str, str]],
|
|
24
|
+
) -> Tuple[List[Dict], Dict[str, Dict], List[Dict]]:
|
|
25
|
+
records = []
|
|
26
|
+
row_dict_by_primary = {}
|
|
27
|
+
failed_rows = []
|
|
28
|
+
row_count = 0
|
|
29
|
+
|
|
30
|
+
for row_tuple in df.itertuples(index=False, name="Row"):
|
|
31
|
+
row_count += 1
|
|
32
|
+
row_dict = {col: getattr(row_tuple, col) for col in df.columns}
|
|
33
|
+
primary_field_value = row_dict.get(primary_field, f"Row {row_count}")
|
|
34
|
+
|
|
35
|
+
row_dict_by_primary[str(primary_field_value)] = row_dict.copy()
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
record = self.transform_row_to_record(row_dict, parsed_model_structure, fk_lookup_cache)
|
|
39
|
+
if record:
|
|
40
|
+
records.append(record)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
error_msg = str(e)
|
|
43
|
+
logger.error(f"Error transforming row {row_count} ({primary_field}={primary_field_value}): {error_msg}")
|
|
44
|
+
failed_rows.append(
|
|
45
|
+
{
|
|
46
|
+
"primary_field": primary_field,
|
|
47
|
+
"primary_field_value": primary_field_value,
|
|
48
|
+
"error": f"Parsing error: {error_msg}",
|
|
49
|
+
"original_row": row_dict,
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
logger.info(f"Prepared {len(records)} records for upload")
|
|
54
|
+
|
|
55
|
+
return records, row_dict_by_primary, failed_rows
|
|
56
|
+
|
|
57
|
+
def transform_row_to_record(
|
|
58
|
+
self, row_dict: Dict, parsed_model_structure: Dict[str, Any], fk_lookup_cache: Dict[str, Dict[str, str]]
|
|
59
|
+
) -> Optional[Dict]:
|
|
60
|
+
model_record = {}
|
|
61
|
+
|
|
62
|
+
for field in parsed_model_structure["fields"]:
|
|
63
|
+
input_value = self.parse_input(row_dict, field, fk_lookup_cache)
|
|
64
|
+
if input_value is not None:
|
|
65
|
+
model_record[field["name"]] = input_value
|
|
66
|
+
|
|
67
|
+
return model_record
|
|
68
|
+
|
|
69
|
+
def parse_input(
|
|
70
|
+
self,
|
|
71
|
+
row_dict: Dict,
|
|
72
|
+
field: Dict[str, Any],
|
|
73
|
+
fk_lookup_cache: Dict[str, Dict[str, str]],
|
|
74
|
+
) -> Any:
|
|
75
|
+
field_name = field["name"][:-2] if field["is_id"] else field["name"]
|
|
76
|
+
|
|
77
|
+
if field_name not in row_dict or pd.isna(row_dict[field_name]):
|
|
78
|
+
if field["is_required"]:
|
|
79
|
+
raise ValueError(f"Required field '{field_name}' is missing")
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
value = self.field_parser.clean_input(row_dict[field_name])
|
|
83
|
+
|
|
84
|
+
if field["is_id"]:
|
|
85
|
+
return self._resolve_foreign_key(field, value, fk_lookup_cache)
|
|
86
|
+
elif field["is_list"] and field["is_scalar"]:
|
|
87
|
+
return self.field_parser.parse_scalar_array(field, field_name, row_dict[field_name])
|
|
88
|
+
else:
|
|
89
|
+
return self.field_parser.parse_field_input(field, field_name, value)
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _resolve_foreign_key(
|
|
93
|
+
field: Dict[str, Any], value: Any, fk_lookup_cache: Dict[str, Dict[str, str]]
|
|
94
|
+
) -> Optional[str]:
|
|
95
|
+
if "related_model" in field:
|
|
96
|
+
related_model = field["related_model"]
|
|
97
|
+
else:
|
|
98
|
+
related_model = (temp := field["name"][:-2])[0].upper() + temp[1:]
|
|
99
|
+
|
|
100
|
+
if related_model in fk_lookup_cache:
|
|
101
|
+
lookup_dict = fk_lookup_cache[related_model]["lookup"]
|
|
102
|
+
record_id = lookup_dict.get(str(value))
|
|
103
|
+
|
|
104
|
+
if record_id:
|
|
105
|
+
return record_id
|
|
106
|
+
elif field["is_required"]:
|
|
107
|
+
raise ValueError(f"{related_model}: {value} does not exist")
|
|
108
|
+
return None
|
|
109
|
+
else:
|
|
110
|
+
if field["is_required"]:
|
|
111
|
+
raise ValueError(f"No pre-fetched data for {related_model}, cannot resolve FK for required field")
|
|
112
|
+
logger.warning(f"No pre-fetched data for {related_model}, skipping optional FK field")
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def to_camel_case(s: str) -> str:
|
|
117
|
+
s_with_spaces = re.sub(r"(?<!^)(?=[A-Z])", " ", s)
|
|
118
|
+
parts = re.split(r"[\s_\-]+", s_with_spaces.strip())
|
|
119
|
+
return parts[0].lower() + "".join(word.capitalize() for word in parts[1:])
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Record validation functionality."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Dict, Any, List
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RecordValidator:
|
|
12
|
+
@staticmethod
|
|
13
|
+
def validate_required_fields(row_dict: Dict, parsed_model_structure: Dict[str, Any]) -> List[str]:
|
|
14
|
+
errors = []
|
|
15
|
+
|
|
16
|
+
for field in parsed_model_structure["fields"]:
|
|
17
|
+
if not field["is_required"]:
|
|
18
|
+
continue
|
|
19
|
+
|
|
20
|
+
field_name = field["name"][:-2] if field["is_id"] else field["name"]
|
|
21
|
+
|
|
22
|
+
if field_name not in row_dict or pd.isna(row_dict[field_name]):
|
|
23
|
+
errors.append(f"Required field '{field_name}' is missing")
|
|
24
|
+
|
|
25
|
+
return errors
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def validate_foreign_key(
|
|
29
|
+
field: Dict[str, Any], value: Any, fk_lookup_cache: Dict[str, Dict[str, str]]
|
|
30
|
+
) -> List[str]:
|
|
31
|
+
errors = []
|
|
32
|
+
|
|
33
|
+
if "related_model" in field:
|
|
34
|
+
related_model = field["related_model"]
|
|
35
|
+
else:
|
|
36
|
+
related_model = (temp := field["name"][:-2])[0].upper() + temp[1:]
|
|
37
|
+
|
|
38
|
+
if related_model not in fk_lookup_cache:
|
|
39
|
+
if field["is_required"]:
|
|
40
|
+
errors.append(f"No pre-fetched data for required foreign key {related_model}")
|
|
41
|
+
return errors
|
|
42
|
+
|
|
43
|
+
lookup_dict = fk_lookup_cache[related_model]["lookup"]
|
|
44
|
+
if str(value) not in lookup_dict:
|
|
45
|
+
if field["is_required"]:
|
|
46
|
+
errors.append(f"{related_model}: {value} does not exist")
|
|
47
|
+
|
|
48
|
+
return errors
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""GraphQL module for query and mutation building."""
|
|
2
|
+
|
|
3
|
+
from .query_builder import QueryBuilder
|
|
4
|
+
from .mutation_builder import MutationBuilder
|
|
5
|
+
from .client import GraphQLClient, AuthenticationError, GraphQLError
|
|
6
|
+
from .executor import QueryExecutor
|
|
7
|
+
|
|
8
|
+
__all__ = ["QueryBuilder", "MutationBuilder", "GraphQLClient", "AuthenticationError", "GraphQLError", "QueryExecutor"]
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""GraphQL HTTP client for making requests to GraphQL APIs."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Dict, Any, Optional
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from amplify_excel_migrator.auth import AuthenticationProvider
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AuthenticationError(Exception):
|
|
16
|
+
"""Raised when authentication is required but not completed"""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GraphQLError(Exception):
|
|
22
|
+
"""Raised when GraphQL query returns errors"""
|
|
23
|
+
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GraphQLClient:
|
|
28
|
+
def __init__(self, api_endpoint: str, auth_provider: Optional[AuthenticationProvider] = None):
|
|
29
|
+
self.api_endpoint = api_endpoint
|
|
30
|
+
self.auth_provider = auth_provider
|
|
31
|
+
|
|
32
|
+
def request(
|
|
33
|
+
self, query: str, variables: Optional[Dict[str, Any]] = None, context: Optional[str] = None
|
|
34
|
+
) -> Optional[Dict[str, Any]]:
|
|
35
|
+
if not self.auth_provider or not self.auth_provider.is_authenticated():
|
|
36
|
+
raise AuthenticationError("Not authenticated. Call authenticate() on the auth provider first.")
|
|
37
|
+
|
|
38
|
+
id_token = self.auth_provider.get_id_token()
|
|
39
|
+
headers = {"Authorization": id_token, "Content-Type": "application/json"}
|
|
40
|
+
|
|
41
|
+
payload = {"query": query, "variables": variables or {}}
|
|
42
|
+
|
|
43
|
+
context_msg = f" [{context}]" if context else ""
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
response = requests.post(self.api_endpoint, headers=headers, json=payload)
|
|
47
|
+
|
|
48
|
+
if response.status_code == 200:
|
|
49
|
+
result = response.json()
|
|
50
|
+
|
|
51
|
+
if "errors" in result:
|
|
52
|
+
raise GraphQLError(f"GraphQL errors{context_msg}: {result['errors']}")
|
|
53
|
+
|
|
54
|
+
return result
|
|
55
|
+
else:
|
|
56
|
+
logger.error(f"HTTP Error {response.status_code}{context_msg}: {response.text}")
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
except requests.exceptions.ConnectionError:
|
|
60
|
+
logger.error(
|
|
61
|
+
f"Connection error{context_msg}: Unable to connect to API endpoint. Check your internet connection or the API endpoint URL."
|
|
62
|
+
)
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
|
|
65
|
+
except requests.exceptions.Timeout as e:
|
|
66
|
+
logger.error(f"Request timeout{context_msg}: {e}")
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
except requests.exceptions.HTTPError as e:
|
|
70
|
+
logger.error(f"HTTP error{context_msg}: {e}")
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
except GraphQLError as e:
|
|
74
|
+
logger.error(str(e))
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
except requests.exceptions.RequestException as e:
|
|
78
|
+
logger.error(f"Request error{context_msg}: {e}")
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
async def request_async(
|
|
82
|
+
self,
|
|
83
|
+
session: aiohttp.ClientSession,
|
|
84
|
+
query: str,
|
|
85
|
+
variables: Optional[Dict[str, Any]] = None,
|
|
86
|
+
context: Optional[str] = None,
|
|
87
|
+
) -> Optional[Dict[str, Any]]:
|
|
88
|
+
if not self.auth_provider or not self.auth_provider.is_authenticated():
|
|
89
|
+
raise AuthenticationError("Not authenticated. Call authenticate() on the auth provider first.")
|
|
90
|
+
|
|
91
|
+
id_token = self.auth_provider.get_id_token()
|
|
92
|
+
headers = {"Authorization": id_token, "Content-Type": "application/json"}
|
|
93
|
+
|
|
94
|
+
payload = {"query": query, "variables": variables or {}}
|
|
95
|
+
|
|
96
|
+
context_msg = f" [{context}]" if context else ""
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
async with session.post(self.api_endpoint, headers=headers, json=payload) as response:
|
|
100
|
+
if response.status == 200:
|
|
101
|
+
result = await response.json()
|
|
102
|
+
|
|
103
|
+
if "errors" in result:
|
|
104
|
+
raise GraphQLError(f"GraphQL errors{context_msg}: {result['errors']}")
|
|
105
|
+
|
|
106
|
+
return result
|
|
107
|
+
else:
|
|
108
|
+
text = await response.text()
|
|
109
|
+
error_msg = f"HTTP Error {response.status}{context_msg}: {text}"
|
|
110
|
+
logger.error(error_msg)
|
|
111
|
+
raise aiohttp.ClientError(error_msg)
|
|
112
|
+
|
|
113
|
+
except aiohttp.ServerTimeoutError as e:
|
|
114
|
+
error_msg = f"Request timeout{context_msg}: {e}"
|
|
115
|
+
logger.error(error_msg)
|
|
116
|
+
raise aiohttp.ServerTimeoutError(error_msg)
|
|
117
|
+
|
|
118
|
+
except aiohttp.ClientConnectionError as e:
|
|
119
|
+
error_msg = f"Connection error{context_msg}: Unable to connect to API endpoint. {e}"
|
|
120
|
+
logger.error(error_msg)
|
|
121
|
+
raise aiohttp.ClientConnectionError(error_msg)
|
|
122
|
+
|
|
123
|
+
except aiohttp.ClientResponseError as e:
|
|
124
|
+
error_msg = f"HTTP response error{context_msg}: {e}"
|
|
125
|
+
logger.error(error_msg)
|
|
126
|
+
raise aiohttp.ClientResponseError(
|
|
127
|
+
request_info=e.request_info, history=e.history, status=e.status, message=error_msg
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
except GraphQLError as e:
|
|
131
|
+
logger.error(str(e))
|
|
132
|
+
raise
|
|
133
|
+
|
|
134
|
+
except aiohttp.ClientError as e:
|
|
135
|
+
error_msg = f"Client error{context_msg}: {e}"
|
|
136
|
+
logger.error(error_msg)
|
|
137
|
+
raise aiohttp.ClientError(error_msg)
|