srx-lib-azure 0.1.4__tar.gz → 0.1.6__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 srx-lib-azure might be problematic. Click here for more details.
- {srx_lib_azure-0.1.4 → srx_lib_azure-0.1.6}/PKG-INFO +1 -1
- {srx_lib_azure-0.1.4 → srx_lib_azure-0.1.6}/pyproject.toml +1 -1
- {srx_lib_azure-0.1.4 → srx_lib_azure-0.1.6}/src/srx_lib_azure/blob.py +25 -11
- srx_lib_azure-0.1.6/src/srx_lib_azure/email.py +66 -0
- {srx_lib_azure-0.1.4 → srx_lib_azure-0.1.6}/src/srx_lib_azure/table.py +5 -2
- srx_lib_azure-0.1.4/src/srx_lib_azure/email.py +0 -41
- {srx_lib_azure-0.1.4 → srx_lib_azure-0.1.6}/.github/workflows/publish.yml +0 -0
- {srx_lib_azure-0.1.4 → srx_lib_azure-0.1.6}/.gitignore +0 -0
- {srx_lib_azure-0.1.4 → srx_lib_azure-0.1.6}/README.md +0 -0
- {srx_lib_azure-0.1.4 → srx_lib_azure-0.1.6}/src/srx_lib_azure/__init__.py +0 -0
|
@@ -10,18 +10,32 @@ from loguru import logger
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class AzureBlobService:
|
|
13
|
-
"""Minimal Azure Blob helper with SAS URL generation.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
"""Minimal Azure Blob helper with SAS URL generation.
|
|
14
|
+
|
|
15
|
+
All configuration can be passed explicitly via constructor. If omitted, falls back
|
|
16
|
+
to environment variables. By default, it does not warn at startup when not
|
|
17
|
+
configured; operations will error if required values are missing.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
connection_string: Optional[str] = None,
|
|
24
|
+
account_key: Optional[str] = None,
|
|
25
|
+
container_name: Optional[str] = None,
|
|
26
|
+
base_blob_url: Optional[str] = None,
|
|
27
|
+
sas_token: Optional[str] = None,
|
|
28
|
+
warn_if_unconfigured: bool = False,
|
|
29
|
+
) -> None:
|
|
30
|
+
self.container_name = container_name or os.getenv("AZURE_BLOB_CONTAINER", "uploads")
|
|
31
|
+
self.connection_string = connection_string or os.getenv("AZURE_STORAGE_CONNECTION_STRING")
|
|
32
|
+
self.account_key = account_key or os.getenv("AZURE_STORAGE_ACCOUNT_KEY")
|
|
33
|
+
self.sas_token = sas_token or os.getenv("AZURE_SAS_TOKEN")
|
|
34
|
+
self.base_blob_url = base_blob_url or os.getenv("AZURE_BLOB_URL")
|
|
35
|
+
|
|
36
|
+
if warn_if_unconfigured and not self.connection_string:
|
|
23
37
|
logger.warning(
|
|
24
|
-
"Azure Storage connection string not configured; blob operations
|
|
38
|
+
"Azure Storage connection string not configured; blob operations may fail."
|
|
25
39
|
)
|
|
26
40
|
|
|
27
41
|
def _get_blob_service(self) -> BlobServiceClient:
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from azure.communication.email.aio import EmailClient
|
|
7
|
+
except Exception: # pragma: no cover - optional dependency at import time
|
|
8
|
+
EmailClient = None # type: ignore
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EmailService:
|
|
14
|
+
"""Thin wrapper over Azure Communication Services EmailClient.
|
|
15
|
+
|
|
16
|
+
Does not raise on missing configuration to keep the library optional.
|
|
17
|
+
If not configured, send calls are skipped with a warning and a 'skipped' status.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
connection_string: str | None = None,
|
|
24
|
+
sender_address: str | None = None,
|
|
25
|
+
warn_if_unconfigured: bool = False,
|
|
26
|
+
):
|
|
27
|
+
self.connection_string = connection_string or os.getenv("ACS_CONNECTION_STRING")
|
|
28
|
+
self.sender_address = sender_address or os.getenv("EMAIL_SENDER")
|
|
29
|
+
if not self.connection_string or not self.sender_address or EmailClient is None:
|
|
30
|
+
self.email_client = None
|
|
31
|
+
if warn_if_unconfigured:
|
|
32
|
+
logger.warning(
|
|
33
|
+
"EmailService not configured (missing ACS_CONNECTION_STRING/EMAIL_SENDER or azure SDK). Calls will be skipped."
|
|
34
|
+
)
|
|
35
|
+
else:
|
|
36
|
+
try:
|
|
37
|
+
self.email_client = EmailClient.from_connection_string(self.connection_string)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
self.email_client = None
|
|
40
|
+
logger.warning("EmailService initialization failed: %s", e)
|
|
41
|
+
|
|
42
|
+
async def send_notification(self, recipient: str, subject: str, body: str, html: bool = False) -> Dict[str, Any]:
|
|
43
|
+
if not self.email_client or not self.sender_address:
|
|
44
|
+
logger.warning("Email skipped: service not configured")
|
|
45
|
+
return {"status": "skipped", "message": "Email service not configured"}
|
|
46
|
+
message = {
|
|
47
|
+
"content": {"subject": subject},
|
|
48
|
+
"recipients": {"to": [{"address": recipient}]},
|
|
49
|
+
"senderAddress": self.sender_address,
|
|
50
|
+
}
|
|
51
|
+
if html:
|
|
52
|
+
message["content"]["html"] = body
|
|
53
|
+
else:
|
|
54
|
+
message["content"]["plainText"] = body
|
|
55
|
+
try:
|
|
56
|
+
poller = await self.email_client.begin_send(message)
|
|
57
|
+
result = await poller.result()
|
|
58
|
+
message_id = result.get("id")
|
|
59
|
+
if message_id:
|
|
60
|
+
logger.info("Email sent to %s with Message ID: %s", recipient, message_id)
|
|
61
|
+
return {"status": "success", "message": "Email sent successfully", "message_id": message_id}
|
|
62
|
+
logger.error("Failed to send email. Result: %s", result)
|
|
63
|
+
return {"status": "error", "message": f"Failed to send email: {result}"}
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error("Email send exception: %s", e)
|
|
66
|
+
return {"status": "error", "message": str(e)}
|
|
@@ -19,7 +19,11 @@ def _now_iso() -> str:
|
|
|
19
19
|
|
|
20
20
|
@dataclass
|
|
21
21
|
class AzureTableService:
|
|
22
|
-
connection_string: Optional[str] =
|
|
22
|
+
connection_string: Optional[str] = None
|
|
23
|
+
|
|
24
|
+
def __init__(self, connection_string: Optional[str] = None) -> None:
|
|
25
|
+
# Constructor injection preferred; fallback to env only if not provided
|
|
26
|
+
self.connection_string = connection_string or os.getenv("AZURE_STORAGE_CONNECTION_STRING")
|
|
23
27
|
|
|
24
28
|
def _get_client(self) -> "TableServiceClient":
|
|
25
29
|
if not self.connection_string:
|
|
@@ -76,4 +80,3 @@ class AzureTableService:
|
|
|
76
80
|
table = client.get_table_client(table_name)
|
|
77
81
|
for entity in table.query_entities(filter=filter_query):
|
|
78
82
|
yield dict(entity)
|
|
79
|
-
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from azure.communication.email.aio import EmailClient
|
|
3
|
-
|
|
4
|
-
import logging
|
|
5
|
-
|
|
6
|
-
logger = logging.getLogger(__name__)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class EmailService:
|
|
10
|
-
"""Thin wrapper over Azure Communication Services EmailClient."""
|
|
11
|
-
|
|
12
|
-
def __init__(self):
|
|
13
|
-
self.connection_string = os.getenv("ACS_CONNECTION_STRING")
|
|
14
|
-
self.sender_address = os.getenv("EMAIL_SENDER")
|
|
15
|
-
if not self.connection_string or not self.sender_address:
|
|
16
|
-
raise ValueError("Missing ACS_CONNECTION_STRING or EMAIL_SENDER in environment variables")
|
|
17
|
-
self.email_client = EmailClient.from_connection_string(self.connection_string)
|
|
18
|
-
|
|
19
|
-
async def send_notification(self, recipient: str, subject: str, body: str, html: bool = False):
|
|
20
|
-
message = {
|
|
21
|
-
"content": {"subject": subject},
|
|
22
|
-
"recipients": {"to": [{"address": recipient}]},
|
|
23
|
-
"senderAddress": self.sender_address,
|
|
24
|
-
}
|
|
25
|
-
if html:
|
|
26
|
-
message["content"]["html"] = body
|
|
27
|
-
else:
|
|
28
|
-
message["content"]["plainText"] = body
|
|
29
|
-
try:
|
|
30
|
-
poller = await self.email_client.begin_send(message)
|
|
31
|
-
result = await poller.result()
|
|
32
|
-
message_id = result.get("id")
|
|
33
|
-
if message_id:
|
|
34
|
-
logger.info("Email sent to %s with Message ID: %s", recipient, message_id)
|
|
35
|
-
return {"status": "success", "message": "Email sent successfully", "message_id": message_id}
|
|
36
|
-
logger.error("Failed to send email. Result: %s", result)
|
|
37
|
-
return {"status": "error", "message": f"Failed to send email: {result}"}
|
|
38
|
-
except Exception as e:
|
|
39
|
-
logger.error("Email send exception: %s", e)
|
|
40
|
-
return {"status": "error", "message": str(e)}
|
|
41
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|