srx-lib-azure 0.1.5__py3-none-any.whl → 0.4.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.
srx_lib_azure/table.py CHANGED
@@ -3,14 +3,24 @@ from __future__ import annotations
3
3
  import os
4
4
  from dataclasses import dataclass
5
5
  from datetime import datetime, timezone
6
- from typing import Any, Dict, Iterable, Optional
6
+ from typing import Any, Dict, Iterable, List, Optional
7
7
 
8
8
  from loguru import logger
9
9
 
10
10
  try:
11
11
  from azure.data.tables import TableServiceClient
12
+ from azure.core.exceptions import (
13
+ ResourceNotFoundError,
14
+ ResourceExistsError,
15
+ ClientAuthenticationError,
16
+ HttpResponseError,
17
+ )
12
18
  except Exception: # pragma: no cover
13
19
  TableServiceClient = None # type: ignore
20
+ ResourceNotFoundError = None # type: ignore
21
+ ResourceExistsError = None # type: ignore
22
+ ClientAuthenticationError = None # type: ignore
23
+ HttpResponseError = None # type: ignore
14
24
 
15
25
 
16
26
  def _now_iso() -> str:
@@ -19,7 +29,11 @@ def _now_iso() -> str:
19
29
 
20
30
  @dataclass
21
31
  class AzureTableService:
22
- connection_string: Optional[str] = os.getenv("AZURE_STORAGE_CONNECTION_STRING")
32
+ connection_string: Optional[str] = None
33
+
34
+ def __init__(self, connection_string: Optional[str] = None) -> None:
35
+ # Constructor injection preferred; fallback to env only if not provided
36
+ self.connection_string = connection_string or os.getenv("AZURE_STORAGE_CONNECTION_STRING")
23
37
 
24
38
  def _get_client(self) -> "TableServiceClient":
25
39
  if not self.connection_string:
@@ -36,44 +50,449 @@ class AzureTableService:
36
50
  except Exception as e:
37
51
  logger.warning("ensure_table(%s) warning: %s", table_name, e)
38
52
 
53
+ def list_tables(self) -> List[str]:
54
+ """List all tables in the storage account.
55
+
56
+ Returns:
57
+ List of table names
58
+ """
59
+ client = self._get_client()
60
+ try:
61
+ tables = [table.name for table in client.list_tables()]
62
+ logger.info("Listed %d tables", len(tables))
63
+ return tables
64
+ except Exception as exc:
65
+ logger.error("Failed to list tables: %s", exc)
66
+ return []
67
+
68
+ def delete_table(self, table_name: str) -> bool:
69
+ """Delete a table.
70
+
71
+ Args:
72
+ table_name: Name of the table to delete
73
+
74
+ Returns:
75
+ True if deleted successfully or table doesn't exist, False on other errors
76
+ """
77
+ client = self._get_client()
78
+ try:
79
+ client.delete_table(table_name=table_name)
80
+ logger.info("Deleted table: %s", table_name)
81
+ return True
82
+ except ResourceNotFoundError:
83
+ # Table already deleted or doesn't exist - this is still success
84
+ logger.info(f"Table {table_name} already deleted or doesn't exist")
85
+ return True
86
+ except ClientAuthenticationError as exc:
87
+ logger.error("Authentication failed deleting table %s: %s", table_name, exc)
88
+ return False
89
+ except HttpResponseError as exc:
90
+ logger.error("Azure service error deleting table %s: %s", table_name, exc.message)
91
+ return False
92
+ except Exception as exc:
93
+ logger.error("Unexpected error deleting table %s: %s", table_name, exc)
94
+ return False
95
+
96
+ def table_exists(self, table_name: str) -> bool:
97
+ """Check if a table exists.
98
+
99
+ Args:
100
+ table_name: Name of the table
101
+
102
+ Returns:
103
+ True if table exists, False otherwise
104
+ """
105
+ return table_name in self.list_tables()
106
+
39
107
  def put_entity(self, table_name: str, entity: Dict[str, Any]) -> Dict[str, Any]:
108
+ """Insert a new entity into a table.
109
+
110
+ Args:
111
+ table_name: Name of the table
112
+ entity: Entity dictionary with PartitionKey and RowKey
113
+
114
+ Returns:
115
+ Dict with etag and timestamp
116
+
117
+ Raises:
118
+ RuntimeError: If entity already exists or other errors occur
119
+ """
40
120
  client = self._get_client()
41
121
  table = client.get_table_client(table_name)
42
- res = table.create_entity(entity=entity)
43
- logger.info(
44
- "Inserted entity into %s: PK=%s RK=%s",
45
- table_name,
46
- entity.get("PartitionKey"),
47
- entity.get("RowKey"),
48
- )
49
- return {"etag": getattr(res, "etag", None), "ts": _now_iso()}
122
+ try:
123
+ res = table.create_entity(entity=entity)
124
+ logger.info(
125
+ "Inserted entity into %s: PK=%s RK=%s",
126
+ table_name,
127
+ entity.get("PartitionKey"),
128
+ entity.get("RowKey"),
129
+ )
130
+ return {"etag": getattr(res, "etag", None), "ts": _now_iso()}
131
+ except ResourceExistsError as e:
132
+ pk, rk = entity.get("PartitionKey"), entity.get("RowKey")
133
+ logger.error(f"Entity already exists in {table_name}: PK={pk} RK={rk}")
134
+ raise RuntimeError(f"Entity already exists: {e}") from e
135
+ except ClientAuthenticationError as e:
136
+ logger.error(f"Authentication failed inserting entity into {table_name}: {e}")
137
+ raise RuntimeError(f"Authentication failed: {e}") from e
138
+ except HttpResponseError as e:
139
+ logger.error(f"Azure service error inserting entity into {table_name}: {e.message}")
140
+ raise RuntimeError(f"Failed to insert entity: {e.message}") from e
50
141
 
51
142
  def upsert_entity(self, table_name: str, entity: Dict[str, Any]) -> Dict[str, Any]:
143
+ """Insert or update an entity in a table.
144
+
145
+ Args:
146
+ table_name: Name of the table
147
+ entity: Entity dictionary with PartitionKey and RowKey
148
+
149
+ Returns:
150
+ Dict with etag and timestamp
151
+
152
+ Raises:
153
+ RuntimeError: If authentication or service errors occur
154
+ """
52
155
  client = self._get_client()
53
156
  table = client.get_table_client(table_name)
54
- res = table.upsert_entity(entity=entity, mode="merge")
55
- logger.info(
56
- "Upserted entity into %s: PK=%s RK=%s",
57
- table_name,
58
- entity.get("PartitionKey"),
59
- entity.get("RowKey"),
60
- )
61
- return {"etag": getattr(res, "etag", None), "ts": _now_iso()}
157
+ try:
158
+ res = table.upsert_entity(entity=entity, mode="merge")
159
+ logger.info(
160
+ "Upserted entity into %s: PK=%s RK=%s",
161
+ table_name,
162
+ entity.get("PartitionKey"),
163
+ entity.get("RowKey"),
164
+ )
165
+ return {"etag": getattr(res, "etag", None), "ts": _now_iso()}
166
+ except ClientAuthenticationError as e:
167
+ logger.error(f"Authentication failed upserting entity into {table_name}: {e}")
168
+ raise RuntimeError(f"Authentication failed: {e}") from e
169
+ except HttpResponseError as e:
170
+ logger.error(f"Azure service error upserting entity into {table_name}: {e.message}")
171
+ raise RuntimeError(f"Failed to upsert entity: {e.message}") from e
62
172
 
63
173
  def delete_entity(self, table_name: str, partition_key: str, row_key: str) -> bool:
174
+ """Delete an entity from a table.
175
+
176
+ Args:
177
+ table_name: Name of the table
178
+ partition_key: Partition key of the entity
179
+ row_key: Row key of the entity
180
+
181
+ Returns:
182
+ True if deleted successfully or entity doesn't exist, False on other errors
183
+
184
+ Note:
185
+ Azure Tables delete_entity doesn't fail if entity doesn't exist
186
+ """
64
187
  client = self._get_client()
65
188
  table = client.get_table_client(table_name)
66
189
  try:
67
190
  table.delete_entity(partition_key=partition_key, row_key=row_key)
68
191
  logger.info("Deleted entity in %s (%s/%s)", table_name, partition_key, row_key)
69
192
  return True
193
+ except ResourceNotFoundError:
194
+ # Entity already deleted or doesn't exist - this is still success
195
+ logger.info(
196
+ f"Entity in {table_name} ({partition_key}/{row_key}) already deleted or doesn't exist"
197
+ )
198
+ return True
199
+ except ClientAuthenticationError as exc:
200
+ logger.error("Authentication failed deleting entity in %s: %s", table_name, exc)
201
+ return False
202
+ except HttpResponseError as exc:
203
+ logger.error("Azure service error deleting entity in %s: %s", table_name, exc.message)
204
+ return False
70
205
  except Exception as exc:
71
- logger.error("Failed to delete entity in %s: %s", table_name, exc)
206
+ logger.error("Unexpected error deleting entity in %s: %s", table_name, exc)
72
207
  return False
73
208
 
209
+ def get_entity(
210
+ self, table_name: str, partition_key: str, row_key: str
211
+ ) -> Optional[Dict[str, Any]]:
212
+ """Retrieve a single entity by partition and row key.
213
+
214
+ Args:
215
+ table_name: Name of the table
216
+ partition_key: Partition key of the entity
217
+ row_key: Row key of the entity
218
+
219
+ Returns:
220
+ Entity dict if found, None otherwise (including on auth/service errors)
221
+ """
222
+ client = self._get_client()
223
+ table = client.get_table_client(table_name)
224
+ try:
225
+ entity = table.get_entity(partition_key=partition_key, row_key=row_key)
226
+ logger.info("Retrieved entity from %s: PK=%s RK=%s", table_name, partition_key, row_key)
227
+ return dict(entity)
228
+ except ResourceNotFoundError:
229
+ logger.info("Entity not found in %s (%s/%s)", table_name, partition_key, row_key)
230
+ return None
231
+ except ClientAuthenticationError as exc:
232
+ logger.error("Authentication failed retrieving entity from %s: %s", table_name, exc)
233
+ return None
234
+ except HttpResponseError as exc:
235
+ logger.error(
236
+ "Azure service error retrieving entity from %s: %s", table_name, exc.message
237
+ )
238
+ return None
239
+ except Exception as exc:
240
+ logger.error("Unexpected error retrieving entity from %s: %s", table_name, exc)
241
+ return None
242
+
243
+ def entity_exists(self, table_name: str, partition_key: str, row_key: str) -> bool:
244
+ """Check if an entity exists without retrieving it.
245
+
246
+ Args:
247
+ table_name: Name of the table
248
+ partition_key: Partition key of the entity
249
+ row_key: Row key of the entity
250
+
251
+ Returns:
252
+ True if entity exists, False otherwise
253
+ """
254
+ return self.get_entity(table_name, partition_key, row_key) is not None
255
+
256
+ def batch_insert_entities(
257
+ self, table_name: str, entities: List[Dict[str, Any]]
258
+ ) -> Dict[str, Any]:
259
+ """Insert multiple entities in a batch operation.
260
+
261
+ Note: All entities must have the same PartitionKey for batch operations.
262
+
263
+ Args:
264
+ table_name: Name of the table
265
+ entities: List of entity dictionaries to insert
266
+
267
+ Returns:
268
+ Dict with count of successful operations and any errors
269
+ """
270
+ if not entities:
271
+ return {"success": 0, "errors": []}
272
+
273
+ client = self._get_client()
274
+ table = client.get_table_client(table_name)
275
+
276
+ # Group by partition key (batch requirement)
277
+ from collections import defaultdict
278
+
279
+ by_partition: defaultdict[str, List[Dict[str, Any]]] = defaultdict(list)
280
+ for entity in entities:
281
+ pk = entity.get("PartitionKey")
282
+ if not pk:
283
+ logger.error("Entity missing PartitionKey, skipping")
284
+ continue
285
+ by_partition[pk].append(entity)
286
+
287
+ success_count = 0
288
+ errors = []
289
+
290
+ for partition_key, partition_entities in by_partition.items():
291
+ # Process in chunks of 100 (Azure limit)
292
+ for i in range(0, len(partition_entities), 100):
293
+ chunk = partition_entities[i : i + 100]
294
+ operations = [("create", entity) for entity in chunk]
295
+
296
+ try:
297
+ table.submit_transaction(operations)
298
+ success_count += len(chunk)
299
+ logger.info(
300
+ "Batch inserted %d entities into %s (PK=%s)",
301
+ len(chunk),
302
+ table_name,
303
+ partition_key,
304
+ )
305
+ except Exception as exc:
306
+ error_msg = f"Batch insert failed for PK={partition_key}: {exc}"
307
+ logger.error(error_msg)
308
+ errors.append(error_msg)
309
+
310
+ return {"success": success_count, "errors": errors, "ts": _now_iso()}
311
+
312
+ def batch_upsert_entities(
313
+ self, table_name: str, entities: List[Dict[str, Any]]
314
+ ) -> Dict[str, Any]:
315
+ """Upsert multiple entities in a batch operation.
316
+
317
+ Note: All entities must have the same PartitionKey for batch operations.
318
+
319
+ Args:
320
+ table_name: Name of the table
321
+ entities: List of entity dictionaries to upsert
322
+
323
+ Returns:
324
+ Dict with count of successful operations and any errors
325
+ """
326
+ if not entities:
327
+ return {"success": 0, "errors": []}
328
+
329
+ client = self._get_client()
330
+ table = client.get_table_client(table_name)
331
+
332
+ # Group by partition key
333
+ from collections import defaultdict
334
+
335
+ by_partition: defaultdict[str, List[Dict[str, Any]]] = defaultdict(list)
336
+ for entity in entities:
337
+ pk = entity.get("PartitionKey")
338
+ if not pk:
339
+ logger.error("Entity missing PartitionKey, skipping")
340
+ continue
341
+ by_partition[pk].append(entity)
342
+
343
+ success_count = 0
344
+ errors = []
345
+
346
+ for partition_key, partition_entities in by_partition.items():
347
+ # Process in chunks of 100
348
+ for i in range(0, len(partition_entities), 100):
349
+ chunk = partition_entities[i : i + 100]
350
+ operations = [("upsert", entity, {"mode": "merge"}) for entity in chunk]
351
+
352
+ try:
353
+ table.submit_transaction(operations)
354
+ success_count += len(chunk)
355
+ logger.info(
356
+ "Batch upserted %d entities into %s (PK=%s)",
357
+ len(chunk),
358
+ table_name,
359
+ partition_key,
360
+ )
361
+ except Exception as exc:
362
+ error_msg = f"Batch upsert failed for PK={partition_key}: {exc}"
363
+ logger.error(error_msg)
364
+ errors.append(error_msg)
365
+
366
+ return {"success": success_count, "errors": errors, "ts": _now_iso()}
367
+
368
+ def batch_delete_entities(self, table_name: str, keys: List[tuple[str, str]]) -> Dict[str, Any]:
369
+ """Delete multiple entities in a batch operation.
370
+
371
+ Note: All entities must have the same PartitionKey for batch operations.
372
+
373
+ Args:
374
+ table_name: Name of the table
375
+ keys: List of (partition_key, row_key) tuples
376
+
377
+ Returns:
378
+ Dict with count of successful operations and any errors
379
+ """
380
+ if not keys:
381
+ return {"success": 0, "errors": []}
382
+
383
+ client = self._get_client()
384
+ table = client.get_table_client(table_name)
385
+
386
+ # Group by partition key
387
+ from collections import defaultdict
388
+
389
+ by_partition: defaultdict[str, List[tuple[str, str]]] = defaultdict(list)
390
+ for pk, rk in keys:
391
+ by_partition[pk].append((pk, rk))
392
+
393
+ success_count = 0
394
+ errors = []
395
+
396
+ for partition_key, partition_keys in by_partition.items():
397
+ # Process in chunks of 100
398
+ for i in range(0, len(partition_keys), 100):
399
+ chunk = partition_keys[i : i + 100]
400
+ operations = [("delete", {"PartitionKey": pk, "RowKey": rk}) for pk, rk in chunk]
401
+
402
+ try:
403
+ table.submit_transaction(operations)
404
+ success_count += len(chunk)
405
+ logger.info(
406
+ "Batch deleted %d entities from %s (PK=%s)",
407
+ len(chunk),
408
+ table_name,
409
+ partition_key,
410
+ )
411
+ except Exception as exc:
412
+ error_msg = f"Batch delete failed for PK={partition_key}: {exc}"
413
+ logger.error(error_msg)
414
+ errors.append(error_msg)
415
+
416
+ return {"success": success_count, "errors": errors, "ts": _now_iso()}
417
+
74
418
  def query(self, table_name: str, filter_query: str) -> Iterable[Dict[str, Any]]:
419
+ """Query entities with a filter.
420
+
421
+ Args:
422
+ table_name: Name of the table
423
+ filter_query: OData filter query string
424
+
425
+ Yields:
426
+ Entity dictionaries matching the filter
427
+ """
75
428
  client = self._get_client()
76
429
  table = client.get_table_client(table_name)
77
430
  for entity in table.query_entities(filter=filter_query):
78
431
  yield dict(entity)
79
432
 
433
+ def query_with_options(
434
+ self,
435
+ table_name: str,
436
+ filter_query: Optional[str] = None,
437
+ select: Optional[List[str]] = None,
438
+ top: Optional[int] = None,
439
+ ) -> Iterable[Dict[str, Any]]:
440
+ """Query entities with advanced options.
441
+
442
+ Args:
443
+ table_name: Name of the table
444
+ filter_query: Optional OData filter query string
445
+ select: Optional list of property names to return (projection)
446
+ top: Optional maximum number of entities to return
447
+
448
+ Yields:
449
+ Entity dictionaries matching the criteria
450
+ """
451
+ client = self._get_client()
452
+ table = client.get_table_client(table_name)
453
+
454
+ kwargs: Dict[str, Any] = {}
455
+ if filter_query:
456
+ kwargs["filter"] = filter_query
457
+ if select:
458
+ kwargs["select"] = select
459
+ if top:
460
+ kwargs["results_per_page"] = top
461
+
462
+ for entity in table.query_entities(**kwargs):
463
+ yield dict(entity)
464
+
465
+ def query_all(
466
+ self,
467
+ table_name: str,
468
+ filter_query: Optional[str] = None,
469
+ select: Optional[List[str]] = None,
470
+ ) -> List[Dict[str, Any]]:
471
+ """Query all entities and return as a list.
472
+
473
+ Warning: This loads all results into memory. Use query() for large result sets.
474
+
475
+ Args:
476
+ table_name: Name of the table
477
+ filter_query: Optional OData filter query string
478
+ select: Optional list of property names to return
479
+
480
+ Returns:
481
+ List of entity dictionaries
482
+ """
483
+ return list(self.query_with_options(table_name, filter_query, select))
484
+
485
+ def count_entities(self, table_name: str, filter_query: Optional[str] = None) -> int:
486
+ """Count entities matching a filter.
487
+
488
+ Args:
489
+ table_name: Name of the table
490
+ filter_query: Optional OData filter query string
491
+
492
+ Returns:
493
+ Count of matching entities
494
+ """
495
+ count = 0
496
+ for _ in self.query_with_options(table_name, filter_query):
497
+ count += 1
498
+ return count
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: srx-lib-azure
3
+ Version: 0.4.0
4
+ Summary: Azure helpers for SRX services: Blob, Email, Table, Document Intelligence, Speech Services
5
+ Author-email: SRX <dev@srx.id>
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: azure-ai-documentintelligence>=1.0.0
8
+ Requires-Dist: azure-communication-email>=1.0.0
9
+ Requires-Dist: azure-data-tables>=12.7.0
10
+ Requires-Dist: azure-storage-blob>=12.22.0
11
+ Requires-Dist: loguru>=0.7.2
12
+ Provides-Extra: all
13
+ Requires-Dist: azure-ai-documentintelligence>=1.0.0; extra == 'all'
14
+ Requires-Dist: azure-cognitiveservices-speech>=1.41.1; extra == 'all'
15
+ Provides-Extra: document
16
+ Requires-Dist: azure-ai-documentintelligence>=1.0.0; extra == 'document'
17
+ Provides-Extra: speech
18
+ Requires-Dist: azure-cognitiveservices-speech>=1.41.1; extra == 'speech'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # srx-lib-azure
22
+
23
+ Lightweight wrappers over Azure SDKs used across SRX services.
24
+
25
+ What it includes:
26
+ - **Blob**: upload/download helpers, SAS URL generation
27
+ - **Email** (Azure Communication Services): simple async sender
28
+ - **Table**: simple CRUD helpers
29
+ - **Document Intelligence** (OCR): document analysis from URLs or bytes
30
+
31
+ ## Install
32
+
33
+ PyPI (public):
34
+
35
+ - `pip install srx-lib-azure`
36
+
37
+ uv (pyproject):
38
+ ```
39
+ [project]
40
+ dependencies = ["srx-lib-azure>=0.1.0"]
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ Blob:
46
+ ```
47
+ from srx_lib_azure.blob import AzureBlobService
48
+ blob = AzureBlobService()
49
+ url = await blob.upload_file(upload_file, "documents/report.pdf")
50
+ ```
51
+
52
+ Email:
53
+ ```
54
+ from srx_lib_azure.email import EmailService
55
+ svc = EmailService()
56
+ await svc.send_notification("user@example.com", "Subject", "Hello", html=False)
57
+ ```
58
+
59
+ Table:
60
+ ```
61
+ from srx_lib_azure.table import AzureTableService
62
+ store = AzureTableService()
63
+ store.ensure_table("events")
64
+ store.upsert_entity("events", {"PartitionKey":"p","RowKey":"r","EventType":"x"})
65
+ ```
66
+
67
+ Document Intelligence (OCR):
68
+ ```python
69
+ from srx_lib_azure import AzureDocumentIntelligenceService
70
+
71
+ # Initialize with endpoint and key
72
+ doc_service = AzureDocumentIntelligenceService(
73
+ endpoint="https://your-resource.cognitiveservices.azure.com/",
74
+ key="your-api-key"
75
+ )
76
+
77
+ # Analyze document from URL
78
+ result = await doc_service.analyze_document_from_url(
79
+ url="https://example.com/document.pdf",
80
+ model_id="prebuilt-read" # or "prebuilt-layout", "prebuilt-invoice", etc.
81
+ )
82
+
83
+ # Analyze document from bytes
84
+ with open("document.pdf", "rb") as f:
85
+ content = f.read()
86
+ result = await doc_service.analyze_document_from_bytes(
87
+ file_content=content,
88
+ model_id="prebuilt-read"
89
+ )
90
+
91
+ # Result structure:
92
+ # {
93
+ # "success": True/False,
94
+ # "content": "extracted text...",
95
+ # "pages": [{"page_number": 1, "width": 8.5, ...}, ...],
96
+ # "page_count": 10,
97
+ # "confidence": 0.98,
98
+ # "model_id": "prebuilt-read",
99
+ # "metadata": {...},
100
+ # "error": None # or error message if failed
101
+ # }
102
+ ```
103
+
104
+ ## Environment Variables
105
+
106
+ - **Blob & Table**: `AZURE_STORAGE_CONNECTION_STRING` (required)
107
+ - **Email (ACS)**: `ACS_CONNECTION_STRING`, `EMAIL_SENDER`
108
+ - **Document Intelligence**: `AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT`, `AZURE_DOCUMENT_INTELLIGENCE_KEY`
109
+ - **Optional**: `AZURE_STORAGE_ACCOUNT_KEY`, `AZURE_BLOB_URL`, `AZURE_SAS_TOKEN`
110
+
111
+ ## Optional Dependencies
112
+
113
+ All services are optional and won't break if their dependencies aren't installed:
114
+
115
+ ```bash
116
+ # Base installation (includes all services by default)
117
+ pip install srx-lib-azure
118
+
119
+ # Or install only what you need - document intelligence is optional
120
+ pip install srx-lib-azure[document] # Adds Document Intelligence support
121
+
122
+ # Install with all optional dependencies
123
+ pip install srx-lib-azure[all]
124
+ ```
125
+
126
+ If you import a service without its required Azure SDK, it will log a warning but won't crash.
127
+
128
+ ## Release
129
+
130
+ Tag `vX.Y.Z` to publish to GitHub Packages via Actions.
131
+
132
+ ## License
133
+
134
+ Proprietary © SRX
@@ -0,0 +1,9 @@
1
+ srx_lib_azure/__init__.py,sha256=QFF0enDeZGidEkFDHLQJ_IOmKofqii9FRoj8ToaxC7c,671
2
+ srx_lib_azure/blob.py,sha256=pAOIPAImhIChMCoxQhe79N11vLF24yAlw0R8gcnitvA,12730
3
+ srx_lib_azure/document.py,sha256=gAVIJRRwtDscAYAUKH7XiAqdCSmoZ-TpzivWS12XQXM,10551
4
+ srx_lib_azure/email.py,sha256=qZWZUjoUMocivU-S7Qa4mJV8aj4jtQPfy868wnPgPlA,4837
5
+ srx_lib_azure/speech.py,sha256=lBMkhUFIrJ0SAOczEDQEL2Is6VoEp8usAKI8AjUXe2U,18199
6
+ srx_lib_azure/table.py,sha256=-RPMlZ2gzXi-6bJSkGFuV5XyIp8-3pdwulI-IEZUs1Y,18607
7
+ srx_lib_azure-0.4.0.dist-info/METADATA,sha256=uE8ZaBcUEuL0G3kpt2xdORUPh-h3ecw-dzHR2xvy3Mw,3767
8
+ srx_lib_azure-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ srx_lib_azure-0.4.0.dist-info/RECORD,,