auris_tools 0.0.1__tar.gz

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.

Potentially problematic release.


This version of auris_tools might be problematic. Click here for more details.

@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.3
2
+ Name: auris_tools
3
+ Version: 0.0.1
4
+ Summary: The swiss knife tools to coordinates cloud frameworks with an easy for Auris platforms
5
+ Author: Antonio Senra
6
+ Author-email: acsenrafilho@gmail.com
7
+ Requires-Python: >=3.10,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Dist: boto3 (>=1.40.29,<2.0.0)
14
+ Requires-Dist: dotenv (>=0.9.9,<0.10.0)
15
+ Requires-Dist: google-generativeai (>=0.8.5,<0.9.0)
16
+ Requires-Dist: python-docx (>=1.2.0,<2.0.0)
17
+ Requires-Dist: rich (>=14.1.0,<15.0.0)
18
+ Description-Content-Type: text/markdown
19
+
20
+ # auris-tools
21
+
22
+ The swiss knife tools to coordinates cloud frameworks with an easy for Auris platforms
23
+
24
+ ## Installation
25
+
26
+ This project requires **Python 3.10** and uses [Poetry](https://python-poetry.org/) for dependency management.
27
+
28
+ 1. **Clone the repository:**
29
+ ```bash
30
+ git clone https://github.com/AurisAASI/auris-tools.git
31
+ cd auris-tools
32
+ ```
33
+ 2. **Install Poetry (if not already installed):**
34
+ ```bash
35
+ pip install poetry
36
+ ```
37
+ 3. **Install dependencies:**
38
+ ```bash
39
+ poetry install
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Project Structure
45
+
46
+ The main classes and modules are organized as follows:
47
+
48
+ ```
49
+ /auris_tools
50
+ ├── __init__.py
51
+ ├── configuration.py # AWS configuration utilities
52
+ ├── databaseHandlers.py # DynamoDB handler class
53
+ ├── officeWordHandler.py # Office Word document handler
54
+ ├── storageHandler.py # AWS S3 storage handler
55
+ ├── textractHandler.py # AWS Textract handler
56
+ ├── utils.py # Utility functions
57
+ ├── geminiHandler.py # Google Gemini AI handler
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Testing & Linting
63
+
64
+ - **Run all tests:**
65
+ ```bash
66
+ task test
67
+ ```
68
+ - **Run linter (ruff):**
69
+ ```bash
70
+ task lint
71
+ ```
72
+
73
+ Test coverage and linting are enforced in CI. Make sure all tests pass and code is linted before submitting a PR.
74
+
75
+ ---
76
+
@@ -0,0 +1,56 @@
1
+ # auris-tools
2
+
3
+ The swiss knife tools to coordinates cloud frameworks with an easy for Auris platforms
4
+
5
+ ## Installation
6
+
7
+ This project requires **Python 3.10** and uses [Poetry](https://python-poetry.org/) for dependency management.
8
+
9
+ 1. **Clone the repository:**
10
+ ```bash
11
+ git clone https://github.com/AurisAASI/auris-tools.git
12
+ cd auris-tools
13
+ ```
14
+ 2. **Install Poetry (if not already installed):**
15
+ ```bash
16
+ pip install poetry
17
+ ```
18
+ 3. **Install dependencies:**
19
+ ```bash
20
+ poetry install
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Project Structure
26
+
27
+ The main classes and modules are organized as follows:
28
+
29
+ ```
30
+ /auris_tools
31
+ ├── __init__.py
32
+ ├── configuration.py # AWS configuration utilities
33
+ ├── databaseHandlers.py # DynamoDB handler class
34
+ ├── officeWordHandler.py # Office Word document handler
35
+ ├── storageHandler.py # AWS S3 storage handler
36
+ ├── textractHandler.py # AWS Textract handler
37
+ ├── utils.py # Utility functions
38
+ ├── geminiHandler.py # Google Gemini AI handler
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Testing & Linting
44
+
45
+ - **Run all tests:**
46
+ ```bash
47
+ task test
48
+ ```
49
+ - **Run linter (ruff):**
50
+ ```bash
51
+ task lint
52
+ ```
53
+
54
+ Test coverage and linting are enforced in CI. Make sure all tests pass and code is linted before submitting a PR.
55
+
56
+ ---
File without changes
@@ -0,0 +1,81 @@
1
+ import logging
2
+ import os
3
+
4
+ from dotenv import load_dotenv
5
+
6
+ # Load environment variables from .env file
7
+ load_dotenv()
8
+
9
+
10
+ class AWSConfiguration:
11
+ """
12
+ AWS Configuration class that handles credentials and region settings.
13
+ Prioritizes environment variables over constructor parameters.
14
+ """
15
+
16
+ def __init__(
17
+ self,
18
+ access_key: str = None,
19
+ secret_key: str = None,
20
+ region: str = None,
21
+ profile: str = None,
22
+ endpoint_url: str = None,
23
+ ):
24
+ # Try to get credentials from environment variables first
25
+ self.access_key = (
26
+ access_key if access_key else os.environ.get('AWS_ACCESS_KEY_ID')
27
+ )
28
+ self.secret_key = (
29
+ secret_key
30
+ if secret_key
31
+ else os.environ.get('AWS_SECRET_ACCESS_KEY')
32
+ )
33
+ self.region = (
34
+ region
35
+ if region
36
+ else os.environ.get('AWS_DEFAULT_REGION') or 'us-east-1'
37
+ )
38
+ self.profile = profile if profile else os.environ.get('AWS_PROFILE')
39
+ self.endpoint_url = (
40
+ endpoint_url
41
+ if endpoint_url
42
+ else os.environ.get('AWS_ENDPOINT_URL')
43
+ )
44
+
45
+ # Validate configuration
46
+ self._validate_config()
47
+
48
+ def _validate_config(self):
49
+ """Validate that we have enough configuration to proceed."""
50
+ if not ((self.access_key and self.secret_key) or self.profile):
51
+ logging.warning(
52
+ 'No AWS credentials provided via environment variables or constructor. '
53
+ 'AWS operations may fail unless credentials are configured via '
54
+ '~/.aws/credentials, IAM roles, or other AWS credential providers.'
55
+ )
56
+
57
+ def get_boto3_session_args(self):
58
+ """
59
+ Return a dictionary of arguments that can be passed to boto3.session.Session()
60
+ """
61
+ session_args = {'region_name': self.region}
62
+
63
+ if self.access_key and self.secret_key:
64
+ session_args['aws_access_key_id'] = self.access_key
65
+ session_args['aws_secret_access_key'] = self.secret_key
66
+
67
+ if self.profile:
68
+ session_args['profile_name'] = self.profile
69
+
70
+ return session_args
71
+
72
+ def get_client_args(self):
73
+ """
74
+ Return a dictionary of arguments that can be passed to boto3 client creation
75
+ """
76
+ client_args = {}
77
+
78
+ if self.endpoint_url:
79
+ client_args['endpoint_url'] = self.endpoint_url
80
+
81
+ return client_args
@@ -0,0 +1,132 @@
1
+ import logging
2
+
3
+ import boto3
4
+ from boto3.dynamodb.types import TypeDeserializer, TypeSerializer
5
+
6
+ from auris_tools.configuration import AWSConfiguration
7
+ from auris_tools.utils import generate_uuid
8
+
9
+
10
+ class DatabaseHandler:
11
+ def __init__(self, table_name, config=None):
12
+ """
13
+ Initialize the database handler.
14
+
15
+ Args:
16
+ table_name: Name of the DynamoDB table.
17
+ config: An AWSConfiguration object, or None to use environment variables.
18
+ """
19
+ self.table_name = table_name
20
+ if config is None:
21
+ config = AWSConfiguration()
22
+
23
+ # Create a boto3 session with the configuration
24
+ session = boto3.session.Session(**config.get_boto3_session_args())
25
+
26
+ # Create a DynamoDB client with additional configuration if needed
27
+ self.client = session.client('dynamodb', **config.get_client_args())
28
+
29
+ if not self._check_table_exists(table_name):
30
+ raise Exception(f'Table does not exist: {table_name}')
31
+
32
+ logging.info(f'Initialized DynamoDB client in region {config.region}')
33
+
34
+ def insert_item(self, item, primary_key: str = 'id'):
35
+ """Insert an item with automatic type conversion"""
36
+ if not isinstance(item, dict):
37
+ raise TypeError('Item must be a dictionary')
38
+
39
+ if primary_key not in item:
40
+ item[primary_key] = generate_uuid()
41
+
42
+ dynamo_item = self._serialize_item(item)
43
+ response = self.client.put_item(
44
+ TableName=self.table_name, Item=dynamo_item
45
+ )
46
+ return response
47
+
48
+ def get_item(self, key):
49
+ """
50
+ Retrieve an item from a DynamoDB table.
51
+
52
+ Args:
53
+ key: A dictionary representing the key of the item to retrieve.
54
+
55
+ Returns:
56
+ The retrieved item, or None if not found.
57
+ """
58
+ if not isinstance(key, dict):
59
+ raise TypeError('Key must be a dictionary')
60
+
61
+ # Check if the key is in DynamoDB format (i.e., values are dicts with type keys)
62
+ if not all(isinstance(v, dict) and len(v) == 1 for v in key.values()):
63
+ # Convert to DynamoDB format
64
+ key = self._serialize_item(key)
65
+
66
+ try:
67
+ response = self.client.get_item(TableName=self.table_name, Key=key)
68
+ return response.get('Item')
69
+ except Exception as e:
70
+ logging.error(
71
+ f'Error retrieving item from {self.table_name}: {str(e)}'
72
+ )
73
+ return None
74
+
75
+ def delete_item(self, key, primary_key='id'):
76
+ """
77
+ Delete an item from a DynamoDB table.
78
+
79
+ Args:
80
+ key (str or dict): Either a string identifier for the primary key,
81
+ or a dictionary containing the complete key structure.
82
+ primary_key (str, optional): Name of the primary key field. Defaults to 'id'.
83
+
84
+ Returns:
85
+ bool: True if deletion was successful, False otherwise.
86
+ """
87
+ # Convert string key to a dictionary with the primary key
88
+ if isinstance(key, str):
89
+ key = {primary_key: key}
90
+ elif not isinstance(key, dict):
91
+ raise TypeError('Key must be a string identifier or a dictionary')
92
+
93
+ # Check if the key is in DynamoDB format
94
+ if not self.item_is_serialized(key):
95
+ key = self._serialize_item(key)
96
+
97
+ try:
98
+ self.client.delete_item(
99
+ TableName=self.table_name,
100
+ Key=key,
101
+ ReturnValues='ALL_OLD', # Return the deleted item
102
+ )
103
+ logging.info(f'Deleted item from {self.table_name} with key {key}')
104
+ return True
105
+ except Exception as e:
106
+ logging.error(
107
+ f'Error deleting item from {self.table_name}: {str(e)}'
108
+ )
109
+ return False
110
+
111
+ def item_is_serialized(self, item):
112
+ """Check if an item is in DynamoDB serialized format"""
113
+ return all(isinstance(v, dict) and len(v) == 1 for v in item.values())
114
+
115
+ def _serialize_item(self, item):
116
+ """Convert Python types to DynamoDB format"""
117
+ serializer = TypeSerializer()
118
+ return {k: serializer.serialize(v) for k, v in item.items()}
119
+
120
+ def _deserialize_item(self, item):
121
+ """Convert DynamoDB format back to Python types"""
122
+ deserializer = TypeDeserializer()
123
+ return {k: deserializer.deserialize(v) for k, v in item.items()}
124
+
125
+ def _check_table_exists(self, table_name):
126
+ """Check if a DynamoDB table exists"""
127
+ try:
128
+ existing_tables = self.client.list_tables().get('TableNames', [])
129
+ return table_name in existing_tables
130
+ except Exception as e:
131
+ logging.error(f'Error checking table existence: {str(e)}')
132
+ return False
@@ -0,0 +1,245 @@
1
+ import logging
2
+ import os
3
+
4
+ import google.generativeai as genai
5
+ from dotenv import load_dotenv
6
+
7
+ # Load environment variables from .env file
8
+ load_dotenv()
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class GoogleGeminiHandler:
14
+ """A handler class for interacting with Google's Gemini AI models.
15
+
16
+ This class provides a convenient interface for generating content using Google's
17
+ Gemini generative AI models. It handles authentication, model configuration,
18
+ and content generation with automatic error handling and logging.
19
+
20
+ Attributes:
21
+ api_key (str): The Google AI API key used for authentication.
22
+ model_name (str): The name of the Gemini model to use.
23
+ temperature (float): Controls randomness in generation (0.0 to 1.0).
24
+ response_schema (dict): Optional schema for structured responses.
25
+ response_mime_type (str): MIME type for response format.
26
+ generation_config (genai.types.GenerationConfig): Configuration for content generation.
27
+ model (genai.GenerativeModel): The configured Gemini model instance.
28
+
29
+ Example:
30
+ Basic usage with environment variable API key:
31
+
32
+ >>> handler = GoogleGeminiHandler()
33
+ >>> response = handler.generate_output("What is artificial intelligence?")
34
+ >>> text = handler.get_text(response)
35
+
36
+ Usage with custom parameters:
37
+
38
+ >>> handler = GoogleGeminiHandler(
39
+ ... api_key="your-api-key",
40
+ ... model="gemini-2.0-flash-exp",
41
+ ... temperature=0.7,
42
+ ... response_mime_type="text/plain"
43
+ ... )
44
+ """
45
+
46
+ def __init__(
47
+ self, api_key: str = None, model: str = 'gemini-2.5-flash', **kwargs
48
+ ):
49
+ """Initialize the Google Gemini handler.
50
+
51
+ Args:
52
+ api_key (str, optional): Google AI API key. If not provided, will attempt
53
+ to load from GEMINI_API_KEY environment variable. Defaults to None.
54
+ model (str, optional): Name of the Gemini model to use.
55
+ Defaults to 'gemini-2.5-flash'.
56
+ **kwargs: Additional configuration parameters:
57
+ - temperature (float): Controls randomness (0.0-1.0). Defaults to 0.5.
58
+ - response_schema (dict): Schema for structured responses. Defaults to None.
59
+ - response_mime_type (str): Response MIME type. Defaults to 'application/json'.
60
+
61
+ Raises:
62
+ TypeError: If the specified model is not available.
63
+
64
+ Example:
65
+ >>> handler = GoogleGeminiHandler(
66
+ ... api_key="your-api-key",
67
+ ... model="gemini-2.0-flash-exp",
68
+ ... temperature=0.7
69
+ ... )
70
+ """
71
+
72
+ self.api_key = api_key if api_key else os.getenv('GEMINI_API_KEY')
73
+ if self.api_key is None:
74
+ logger.error(
75
+ 'Gemini API key not configured. Please, define the GEMINI_API_KEY environment variable or enter your key directly in the code.'
76
+ )
77
+
78
+ self.model_name = model
79
+ self._check_model_availability()
80
+
81
+ # More configuration from input parameters
82
+ self.temperature = kwargs.get('temperature', 0.5)
83
+ self.response_schema = kwargs.get('response_schema', None)
84
+ self.response_mime_type = kwargs.get(
85
+ 'response_mime_type', 'application/json'
86
+ )
87
+
88
+ self.generation_config = genai.types.GenerationConfig(
89
+ temperature=self.temperature,
90
+ response_schema=self.response_schema,
91
+ response_mime_type=self.response_mime_type,
92
+ )
93
+
94
+ self.model = genai.GenerativeModel(
95
+ generation_config=self.generation_config,
96
+ model_name=self.model_name,
97
+ )
98
+
99
+ def generate_output(
100
+ self, prompt: str, input_data: str = None, input_mime_type: str = None
101
+ ):
102
+ """Generate content using the configured Gemini model.
103
+
104
+ This method sends a prompt to the Gemini model and returns the generated response.
105
+ It supports both text-only prompts and multimodal inputs with additional data.
106
+
107
+ Args:
108
+ prompt (str): The text prompt to send to the model. This is the main
109
+ instruction or question for the AI to respond to.
110
+ input_data (str, optional): Additional input data to include with the prompt.
111
+ This could be text content, encoded media, or other data. Requires
112
+ input_mime_type to be specified. Defaults to None.
113
+ input_mime_type (str, optional): MIME type of the input_data. Required if
114
+ input_data is provided. Examples: 'text/plain', 'image/jpeg',
115
+ 'application/pdf'. Defaults to None.
116
+
117
+ Returns:
118
+ genai.types.GenerateContentResponse or str: The response from the Gemini model
119
+ if successful, or an empty string if an error occurred.
120
+
121
+ Raises:
122
+ ValueError: If input_data is provided without input_mime_type or vice versa.
123
+
124
+ Example:
125
+ Text-only generation:
126
+
127
+ >>> response = handler.generate_output("Explain quantum computing")
128
+
129
+ Multimodal generation with additional data:
130
+
131
+ >>> response = handler.generate_output(
132
+ ... prompt="Describe this image",
133
+ ... input_data=base64_encoded_image,
134
+ ... input_mime_type="image/jpeg"
135
+ ... )
136
+ """
137
+ if (input_data is not None and input_mime_type is None) or (
138
+ input_data is None and input_mime_type is not None
139
+ ):
140
+ raise ValueError(
141
+ 'input_mime_type must be provided if input_data is given, or otherwise both must be None.'
142
+ )
143
+
144
+ if input_data and input_mime_type: # Add input data if provided
145
+ prompt = [
146
+ prompt,
147
+ {'mime_type': input_mime_type, 'content': input_data},
148
+ ]
149
+
150
+ try:
151
+ response = self.model.generate_content(prompt)
152
+ return response
153
+ except Exception as e:
154
+ logger.error(f'Error generating LLM output: {str(e)}')
155
+ return ''
156
+
157
+ def get_text(self, response) -> str:
158
+ """Extract text content from a Gemini model response.
159
+
160
+ This method parses the response object returned by the Gemini model and
161
+ extracts the generated text content. It handles the response structure
162
+ safely and provides fallbacks for various response formats.
163
+
164
+ Args:
165
+ response (genai.types.GenerateContentResponse or dict): The response object
166
+ returned from the generate_output method. This can be either a
167
+ GenerateContentResponse object or a dictionary representation.
168
+
169
+ Returns:
170
+ str: The extracted text content from the response. Returns an empty
171
+ string if no content is found or if an error occurs during extraction.
172
+
173
+ Example:
174
+ >>> response = handler.generate_output("What is AI?")
175
+ >>> text_content = handler.get_text(response)
176
+ >>> print(text_content)
177
+ "Artificial Intelligence (AI) refers to..."
178
+
179
+ >>> # Handle case with no candidates
180
+ >>> empty_response = {'candidates': []}
181
+ >>> text = handler.get_text(empty_response)
182
+ >>> print(text) # Returns empty string
183
+ ""
184
+ """
185
+ try:
186
+ if 'candidates' in response and len(response['candidates']) > 0:
187
+ return response['candidates'][0]['content']
188
+ else:
189
+ logger.warning('No candidates found in the response.')
190
+ return ''
191
+ except Exception as e:
192
+ logger.error(f'Error extracting text from response: {str(e)}')
193
+ return ''
194
+
195
+ def _check_model_availability(self):
196
+ """Check if the specified Gemini model is available.
197
+
198
+ This private method validates that the requested model name exists in the
199
+ list of available Google Gemini models. It queries the Google AI API to
200
+ get the current list of available models and compares against the requested
201
+ model name.
202
+
203
+ Raises:
204
+ TypeError: If the specified model is not found in the list of available
205
+ models from the Google AI API.
206
+
207
+ Note:
208
+ This method is called automatically during initialization and will
209
+ prevent the handler from being created if an invalid model is specified.
210
+ It also logs the availability check results for debugging purposes.
211
+
212
+ Example:
213
+ This method is called internally during initialization:
214
+
215
+ >>> # This will call _check_model_availability internally
216
+ >>> handler = GoogleGeminiHandler(model="gemini-2.5-flash") # Success
217
+ >>> handler = GoogleGeminiHandler(model="invalid-model") # Raises TypeError
218
+ """
219
+ try:
220
+ available_models = genai.list_models()
221
+ # Extract model names and handle the 'models/' prefix
222
+ available_model_names = []
223
+ for model in available_models:
224
+ model_name = model.name
225
+ # Remove 'models/' prefix if present
226
+ if model_name.startswith('models/'):
227
+ model_name = model_name[7:] # Remove 'models/' prefix
228
+ available_model_names.append(model_name)
229
+
230
+ if self.model_name not in available_model_names:
231
+ logger.error(
232
+ f'Model {self.model_name} is not available. Please check the model name.'
233
+ )
234
+ logger.info(
235
+ f'Available models: {", ".join(available_model_names)}'
236
+ )
237
+ raise TypeError(f'Invalid model name: {self.model_name}')
238
+ else:
239
+ logger.info(f'Model {self.model_name} is available.')
240
+ except Exception as e:
241
+ if 'Invalid model name' in str(e):
242
+ raise # Re-raise our custom error
243
+ else:
244
+ logger.error(f'Error checking model availability: {str(e)}')
245
+ # Don't raise error for API connectivity issues, just log