sharexlsx-py 0.1.0__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.
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: sharexlsx_py
3
+ Version: 0.1.0
4
+ Summary: Librería para consultar y leer archivos Excel de SharePoint usando MS Graph API
5
+ Author-email: Mario Eduardo <mfernandez@sumimsa.com.mx>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: msal>=1.20.0
9
+ Requires-Dist: httpx>=0.28.1
10
+
11
+ # sharexlsx_py
12
+
13
+ Librería asíncrona de Python para consultar y leer archivos Excel (`.xlsx`) alojados en Microsoft SharePoint utilizando Microsoft Graph API, `httpx` y `python-calamine`.
14
+
15
+ ## Características
16
+
17
+ * **Rendimiento Asíncrono:** Basado en `httpx.AsyncClient` con reutilización de conexiones TCP (Connection Pooling).
18
+ * **Autenticación No Bloqueante:** Delegación de MSAL a hilos secundarios vía `asyncio.to_thread`.
19
+ * **Lectura Rápida de Excel:** Uso del motor `python-calamine` (escrito en Rust) para un parseo acelerado a DataFrames de Pandas.
20
+ * **Procesamiento en Memoria:** Sin persistencia en disco de archivos temporales.
@@ -0,0 +1,10 @@
1
+ # sharexlsx_py
2
+
3
+ Librería asíncrona de Python para consultar y leer archivos Excel (`.xlsx`) alojados en Microsoft SharePoint utilizando Microsoft Graph API, `httpx` y `python-calamine`.
4
+
5
+ ## Características
6
+
7
+ * **Rendimiento Asíncrono:** Basado en `httpx.AsyncClient` con reutilización de conexiones TCP (Connection Pooling).
8
+ * **Autenticación No Bloqueante:** Delegación de MSAL a hilos secundarios vía `asyncio.to_thread`.
9
+ * **Lectura Rápida de Excel:** Uso del motor `python-calamine` (escrito en Rust) para un parseo acelerado a DataFrames de Pandas.
10
+ * **Procesamiento en Memoria:** Sin persistencia en disco de archivos temporales.
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sharexlsx_py"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name="Mario Eduardo", email="mfernandez@sumimsa.com.mx" },
10
+ ]
11
+ description = "Librería para consultar y leer archivos Excel de SharePoint usando MS Graph API"
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ dependencies = [
15
+ "msal>=1.20.0",
16
+ "httpx>=0.28.1",
17
+ ]
18
+
19
+ [tool.setuptools.packages.find]
20
+ where = ["."]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ from .client import SharePointExcelClient
2
+
3
+ __all__ = [
4
+ "SharePointExcelClient",
5
+ ]
@@ -0,0 +1,49 @@
1
+ import asyncio
2
+
3
+ import httpx
4
+ import msal
5
+
6
+
7
+ class GraphAuth:
8
+ def __init__(self, tenant_id: str, client_id: str, client_secret: str) -> None:
9
+ self.authority = f"https://login.microsoftonline.com/{tenant_id}"
10
+ self.scope = ["https://graph.microsoft.com/.default"]
11
+
12
+ self.app = msal.ConfidentialClientApplication(
13
+ client_id,
14
+ authority=self.authority,
15
+ client_credential=client_secret,
16
+ )
17
+
18
+ async def get_access_token(self) -> str:
19
+ """
20
+ Obtiene el token usando caché o solicita uno nuevo en un hilo secundario (non-blocking).
21
+ """
22
+ result = await asyncio.to_thread(
23
+ self.app.acquire_token_silent, self.scope, account=None
24
+ )
25
+
26
+ if not result:
27
+ result = await asyncio.to_thread(
28
+ self.app.acquire_token_for_client, scopes=self.scope
29
+ )
30
+
31
+ if result and "access_token" in result:
32
+ return result["access_token"]
33
+
34
+ error_msg = result.get("error_description") if result else "Error desconocido"
35
+ raise PermissionError(f"Error autenticando en MS Graph: {error_msg}")
36
+
37
+
38
+ class MSGraphAuth(httpx.Auth):
39
+ """
40
+ Inyecta dinámicamente el token Bearer en peticiones asíncronas de httpx.
41
+ """
42
+
43
+ def __init__(self, graph_auth: GraphAuth) -> None:
44
+ self.graph_auth = graph_auth
45
+
46
+ async def async_auth_flow(self, request: httpx.Request):
47
+ token = await self.graph_auth.get_access_token()
48
+ request.headers["Authorization"] = f"Bearer {token}"
49
+ yield request
@@ -0,0 +1,111 @@
1
+ import asyncio
2
+ from typing import Self
3
+
4
+ import httpx
5
+
6
+ from .auth import GraphAuth, MSGraphAuth
7
+ from .utils import filter_xlsx_items
8
+
9
+
10
+ class SharePointExcelClient:
11
+ def __init__(
12
+ self,
13
+ tenant_id: str,
14
+ client_id: str,
15
+ client_secret: str,
16
+ site_hostname: str,
17
+ site_path: str,
18
+ timeout: float = 30.0,
19
+ ) -> None:
20
+ self.auth = GraphAuth(tenant_id, client_id, client_secret)
21
+ self.site_hostname = site_hostname
22
+ self.site_path = site_path.strip("/")
23
+ self._site_id: str | None = None
24
+ self.timeout = timeout
25
+ self._client: httpx.AsyncClient | None = None
26
+
27
+ @property
28
+ def client(self) -> httpx.AsyncClient:
29
+ """
30
+ Inicializa o retorna la sesión de cliente persistente.
31
+ """
32
+ if self._client is None or self._client.is_closed:
33
+ self._client = httpx.AsyncClient(
34
+ base_url="https://graph.microsoft.com/v1.0",
35
+ auth=MSGraphAuth(self.auth),
36
+ timeout=httpx.Timeout(self.timeout),
37
+ )
38
+ return self._client
39
+
40
+ async def get_site_id(self) -> str:
41
+ """
42
+ Obtiene y almacena en caché el Site ID de SharePoint.
43
+ """
44
+ if not self._site_id:
45
+ url = f"/sites/{self.site_hostname}:/{self.site_path}"
46
+ response = await self.client.get(url)
47
+ response.raise_for_status()
48
+ self._site_id = response.json()["id"]
49
+
50
+ assert self._site_id is not None
51
+ return self._site_id
52
+
53
+ async def list_excel_files(self, relative_folder_path: str = "") -> list[dict]:
54
+ """
55
+ Retorna únicamente la lista de metadatos de los archivos .xlsx
56
+ """
57
+ site_id = await self.get_site_id()
58
+ folder_path = relative_folder_path.strip("/")
59
+
60
+ endpoint = (
61
+ f"/sites/{site_id}/drive/root:/{folder_path}:/children"
62
+ if folder_path
63
+ else f"/sites/{site_id}/drive/root/children"
64
+ )
65
+
66
+ response = await self.client.get(endpoint)
67
+ response.raise_for_status()
68
+ return filter_xlsx_items(response.json().get("value", []))
69
+
70
+ async def get_excel_bytes(self, relative_file_path: str) -> bytes:
71
+ """
72
+ Descarga y retorna los bytes puros de un archivo .xlsx específico.
73
+ """
74
+ site_id = await self.get_site_id()
75
+ file_path = relative_file_path.strip("/")
76
+ endpoint = f"/sites/{site_id}/drive/root:/{file_path}:/content"
77
+
78
+ response = await self.client.get(endpoint)
79
+ response.raise_for_status()
80
+ return response.content # Retorna el binario puro (.xlsx en memoria)
81
+
82
+ async def get_folder_excels_bytes(
83
+ self, relative_folder_path: str = ""
84
+ ) -> list[dict]:
85
+ """
86
+ Descarga en paralelo todos los archivos .xlsx del directorio
87
+ y retorna una lista con metadatos + bytes puros de cada uno.
88
+ """
89
+ metadatas = await self.list_excel_files(relative_folder_path)
90
+ folder_path = relative_folder_path.strip("/")
91
+
92
+ async def _download(meta: dict) -> dict:
93
+ file_rel_path = f"{folder_path}/{meta['name']}" if folder_path else meta["name"]
94
+ content_bytes = await self.get_excel_bytes(file_rel_path)
95
+ return {**meta, "content": content_bytes}
96
+
97
+ return list(await asyncio.gather(*[_download(meta) for meta in metadatas]))
98
+
99
+ async def close(self) -> None:
100
+ """
101
+ Cierra el pool de conexiones del cliente HTTP y limpia la referencia.
102
+ """
103
+ if self._client and not self._client.is_closed:
104
+ await self._client.aclose()
105
+ self._client = None
106
+
107
+ async def __aenter__(self) -> Self:
108
+ return self
109
+
110
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
111
+ await self.close()
@@ -0,0 +1,19 @@
1
+ def filter_xlsx_items(items: list[dict]) -> list[dict]:
2
+ """
3
+ Filtra los elementos retornados por Microsoft Graph API para extraer
4
+ únicamente archivos con extensión .xlsx y normalizar sus metadatos.
5
+ """
6
+ files = []
7
+ for item in items:
8
+ if "file" in item and item["name"].lower().endswith(".xlsx"):
9
+ files.append(
10
+ {
11
+ "id": item.get("id"),
12
+ "name": item.get("name"),
13
+ "size": item.get("size"),
14
+ "web_url": item.get("webUrl"),
15
+ "created_at": item.get("createdDateTime"),
16
+ "last_modified": item.get("lastModifiedDateTime"),
17
+ }
18
+ )
19
+ return files
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: sharexlsx_py
3
+ Version: 0.1.0
4
+ Summary: Librería para consultar y leer archivos Excel de SharePoint usando MS Graph API
5
+ Author-email: Mario Eduardo <mfernandez@sumimsa.com.mx>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: msal>=1.20.0
9
+ Requires-Dist: httpx>=0.28.1
10
+
11
+ # sharexlsx_py
12
+
13
+ Librería asíncrona de Python para consultar y leer archivos Excel (`.xlsx`) alojados en Microsoft SharePoint utilizando Microsoft Graph API, `httpx` y `python-calamine`.
14
+
15
+ ## Características
16
+
17
+ * **Rendimiento Asíncrono:** Basado en `httpx.AsyncClient` con reutilización de conexiones TCP (Connection Pooling).
18
+ * **Autenticación No Bloqueante:** Delegación de MSAL a hilos secundarios vía `asyncio.to_thread`.
19
+ * **Lectura Rápida de Excel:** Uso del motor `python-calamine` (escrito en Rust) para un parseo acelerado a DataFrames de Pandas.
20
+ * **Procesamiento en Memoria:** Sin persistencia en disco de archivos temporales.
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ sharexlsx_py/__init__.py
4
+ sharexlsx_py/auth.py
5
+ sharexlsx_py/client.py
6
+ sharexlsx_py/utils.py
7
+ sharexlsx_py.egg-info/PKG-INFO
8
+ sharexlsx_py.egg-info/SOURCES.txt
9
+ sharexlsx_py.egg-info/dependency_links.txt
10
+ sharexlsx_py.egg-info/requires.txt
11
+ sharexlsx_py.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ msal>=1.20.0
2
+ httpx>=0.28.1
@@ -0,0 +1,2 @@
1
+ dist
2
+ sharexlsx_py