luxorasap 0.1.1__tar.gz → 0.1.3__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.
Files changed (38) hide show
  1. luxorasap-0.1.3/PKG-INFO +241 -0
  2. luxorasap-0.1.3/README.md +196 -0
  3. {luxorasap-0.1.1 → luxorasap-0.1.3}/pyproject.toml +2 -2
  4. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/__init__.py +1 -1
  5. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/btgapi/__init__.py +3 -2
  6. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/btgapi/auth.py +2 -1
  7. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/btgapi/reports.py +56 -5
  8. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/ingest/cloud/__init__.py +9 -2
  9. luxorasap-0.1.3/src/luxorasap.egg-info/PKG-INFO +241 -0
  10. luxorasap-0.1.3/tests/test_btgapi_reports.py +139 -0
  11. luxorasap-0.1.1/PKG-INFO +0 -80
  12. luxorasap-0.1.1/README.md +0 -35
  13. luxorasap-0.1.1/src/luxorasap.egg-info/PKG-INFO +0 -80
  14. luxorasap-0.1.1/tests/test_btgapi_reports.py +0 -62
  15. {luxorasap-0.1.1 → luxorasap-0.1.3}/setup.cfg +0 -0
  16. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/btgapi/trades.py +0 -0
  17. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/datareader/__init__.py +0 -0
  18. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/datareader/core.py +0 -0
  19. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/ingest/__init__.py +0 -0
  20. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/ingest/legacy_local/dataloader.py +0 -0
  21. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/utils/__init__.py +0 -0
  22. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/utils/dataframe/__init__.py +0 -0
  23. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/utils/dataframe/reader.py +0 -0
  24. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/utils/dataframe/transforms.py +0 -0
  25. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/utils/storage/__init__.py +0 -0
  26. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap/utils/storage/blob.py +0 -0
  27. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap.egg-info/SOURCES.txt +0 -0
  28. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap.egg-info/dependency_links.txt +0 -0
  29. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap.egg-info/entry_points.txt +0 -0
  30. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap.egg-info/requires.txt +0 -0
  31. {luxorasap-0.1.1 → luxorasap-0.1.3}/src/luxorasap.egg-info/top_level.txt +0 -0
  32. {luxorasap-0.1.1 → luxorasap-0.1.3}/tests/test_btgapi_auth.py +0 -0
  33. {luxorasap-0.1.1 → luxorasap-0.1.3}/tests/test_btgapi_trades.py +0 -0
  34. {luxorasap-0.1.1 → luxorasap-0.1.3}/tests/test_datareader.py +0 -0
  35. {luxorasap-0.1.1 → luxorasap-0.1.3}/tests/test_ingest_cloud.py +0 -0
  36. {luxorasap-0.1.1 → luxorasap-0.1.3}/tests/test_ingest_legacy_local.py +0 -0
  37. {luxorasap-0.1.1 → luxorasap-0.1.3}/tests/test_utils_dataframe.py +0 -0
  38. {luxorasap-0.1.1 → luxorasap-0.1.3}/tests/test_utils_storage.py +0 -0
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: luxorasap
3
+ Version: 0.1.3
4
+ Summary: Luxor’s unified toolbox for data ingestion, querying and analytics.
5
+ Author-email: Luxor Group <backoffice@luxor.com.br>
6
+ License: Proprietary – All rights reserved
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: Other/Proprietary License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: pandas>=2.2
13
+ Requires-Dist: numpy>=1.25
14
+ Requires-Dist: loguru>=0.7
15
+ Requires-Dist: python-dotenv>=1.0
16
+ Requires-Dist: azure-storage-blob>=12.19
17
+ Requires-Dist: pyarrow>=15.0
18
+ Requires-Dist: requests>=2.32
19
+ Requires-Dist: pydantic>=2.7
20
+ Requires-Dist: scipy>=1.13
21
+ Requires-Dist: openpyxl
22
+ Provides-Extra: storage
23
+ Requires-Dist: azure-storage-blob>=12.19; extra == "storage"
24
+ Requires-Dist: pyarrow>=15.0; extra == "storage"
25
+ Provides-Extra: dataframe
26
+ Requires-Dist: pandas>=2.2; extra == "dataframe"
27
+ Provides-Extra: datareader
28
+ Requires-Dist: luxorasap[dataframe,storage]; extra == "datareader"
29
+ Requires-Dist: numpy>=1.25; extra == "datareader"
30
+ Requires-Dist: scipy>=1.13; extra == "datareader"
31
+ Provides-Extra: ingest
32
+ Requires-Dist: luxorasap[dataframe,storage]; extra == "ingest"
33
+ Requires-Dist: pandas>=2.2; extra == "ingest"
34
+ Provides-Extra: btgapi
35
+ Requires-Dist: requests>=2.32; extra == "btgapi"
36
+ Requires-Dist: pydantic>=2.7; extra == "btgapi"
37
+ Provides-Extra: dev
38
+ Requires-Dist: pytest>=8.2; extra == "dev"
39
+ Requires-Dist: requests-mock>=1.11; extra == "dev"
40
+ Requires-Dist: black>=24.4.0; extra == "dev"
41
+ Requires-Dist: isort>=5.13; extra == "dev"
42
+ Requires-Dist: bumpver>=2024.3; extra == "dev"
43
+ Requires-Dist: pre-commit>=3.7; extra == "dev"
44
+ Requires-Dist: build>=1.2; extra == "dev"
45
+
46
+ # 📚 Documentação LuxorASAP
47
+
48
+ > Guia do desenvolvedor para os subpacotes **datareader**, **ingest**, **btgapi** e **utils**.
49
+ >
50
+ > • Instalação rápida • Visão arquitetural • APIs detalhadas • Exemplos de uso • Extras opcional
51
+
52
+ ---
53
+
54
+ ## Índice
55
+
56
+ 1. [Visão Geral](#visao-geral)
57
+ 2. [Instalação](#instalacao)
58
+ 3. [utils](#utils)
59
+ 4. [datareader](#datareader)
60
+ 5. [ingest](#ingest)
61
+ 6. [btgapi](#btgapi)
62
+ 7. [Roadmap & Contribuições](#roadmap)
63
+
64
+ ---
65
+
66
+ ## 1. Visão Geral
67
+
68
+ LuxorASAP é o *toolbox* unificado da Luxor para ingestão, consulta e automação de dados financeiros.
69
+
70
+ | Subpacote | Função‑chave | Extras PyPI |
71
+ | -------------- | --------------------------------------------------------------------- | ---------------------- |
72
+ | **utils** | Utilidades puras (I/O ADLS, transformação de DataFrame, decorators) | `storage`, `dataframe` |
73
+ | **datareader** | Consulta de preços/tabelas no data lake via `LuxorQuery` | `datareader` |
74
+ | **ingest** | Carga de dados nova (parquet/zip/excel) em ADLS + loader legado | `ingest` |
75
+ | **btgapi** | Wrapper autenticado para as APIs do BTG Pactual (relatórios & trades) | `btgapi` |
76
+
77
+ ---
78
+
79
+ ## 2. Instalação
80
+
81
+ ```bash
82
+ # core + leitura de dados
83
+ pip install luxorasap[datareader]
84
+
85
+ # tudo incluído (leitura, ingest, btg)
86
+ pip install luxorasap[datareader,ingest,btgapi]
87
+
88
+ # desenvolvimento
89
+ pip install -e ".[dev]"
90
+ ```
91
+
92
+ Extras podem ser combinados à vontade (`luxorasap[storage]`, etc.).
93
+
94
+ Configuração obrigatória do **ADLS**:
95
+
96
+ ```bash
97
+ export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=..."
98
+ ```
99
+
100
+ ---
101
+
102
+ ## 3. utils
103
+
104
+ Camada de utilidades **sem dependências internas**.
105
+
106
+ ### 3.1 storage.BlobParquetClient
107
+
108
+ ```python
109
+ from luxorasap.utils.storage import BlobParquetClient
110
+ client = BlobParquetClient(container="luxorasap")
111
+
112
+ # write
113
+ client.write_df(df, "bronze/parquet/mytable.parquet")
114
+
115
+ # read (tuple -> DataFrame, success_flag)
116
+ df, ok = client.read_df("bronze/parquet/mytable.parquet")
117
+ ```
118
+
119
+ ### 3.2 dataframe
120
+
121
+ ```python
122
+ from luxorasap.utils.dataframe import prep_for_save, persist_column_formatting, read_bytes
123
+
124
+ df2 = prep_for_save(df, index=True, index_name="ID", normalize=True)
125
+ ```
126
+
127
+ ### 3.3 decorators & misc
128
+
129
+ `with_retry`, `chunkify`, `timer`… ficam em *utils.helpers* (caso precise).
130
+
131
+ ---
132
+
133
+ ## 4. datareader
134
+
135
+ ### 4.1 Visão rápida
136
+
137
+ ```python
138
+ from luxorasap.datareader import LuxorQuery
139
+ lq = LuxorQuery()
140
+
141
+ # DataFrame completo
142
+ df = lq.get_table("assets")
143
+
144
+ # Série de preços diária
145
+ aapl = lq.get_prices("aapl us equity", start="2025-01-01", end="2025-03-31")
146
+
147
+ # Preço pontual
148
+ price = lq.get_price("aapl us equity", on="2025-03-31")
149
+ ```
150
+
151
+ *Caching*: `@lru_cache(maxsize=32)` evita hits repetidos ao Blob.
152
+
153
+ ### 4.2 Métodos-chave
154
+
155
+ | Método | Descrição |
156
+ | ----------------------------------------------- | ----------------------------- |
157
+ | `table_exists(name)` | checa metadados no ADLS |
158
+ | `get_table(name)` | DataFrame completo (cached) |
159
+ | `get_prices(asset, start, end, column="Price")` | `pd.Series` |
160
+ | `get_price(asset, on)` | preço pontual (float ou None) |
161
+
162
+ ---
163
+
164
+ ## 5. ingest
165
+
166
+ ### 5.1 ingest.cloud (novo)
167
+
168
+ ```python
169
+ from luxorasap.ingest import save_table, incremental_load
170
+ from luxorasap.datareader import LuxorQuery
171
+
172
+ save_table("trades", df)
173
+
174
+ lq = LuxorQuery()
175
+ incremental_load(lq, "prices_daily", df_new, increment_column="Date")
176
+ ```
177
+
178
+ ### 5.2 ingest.legacy\_local
179
+
180
+ ```python
181
+ from luxorasap.ingest import DataLoader # Deprecado – ainda funcional
182
+ ```
183
+
184
+ *Decoration*: ao importar `DataLoader` você verá `DeprecationWarning`.
185
+
186
+ ---
187
+
188
+ ## 6. btgapi
189
+
190
+ ### 6.1 Autenticação
191
+
192
+ ```python
193
+ from luxorasap.btgapi import get_access_token
194
+ TOKEN = get_access_token(test_env=True)
195
+ ```
196
+
197
+ ### 6.2 Relatórios – Portfolio & Investor Transactions
198
+
199
+ ```python
200
+ from luxorasap.btgapi.reports import (
201
+ request_portfolio, await_report_ticket_result, process_zip_to_dfs,
202
+ request_investors_transactions_report,
203
+ )
204
+
205
+ ticket = request_portfolio(TOKEN, "LUXOR FUND - CLASS A",
206
+ start=dt.date(2025,1,1), end=dt.date(2025,1,31))
207
+ zip_bytes = await_report_ticket_result(TOKEN, ticket)
208
+ carteiras = process_zip_to_dfs(zip_bytes)
209
+ ```
210
+
211
+ ### 6.3 Trades offshore
212
+
213
+ ```python
214
+ from luxorasap.btgapi.trades import (
215
+ submit_offshore_equity_trades,
216
+ await_transaction_ticket_result,
217
+ )
218
+
219
+ ticket = submit_offshore_equity_trades(TOKEN, trades=[{...}], test_env=True)
220
+ status_df = await_transaction_ticket_result(TOKEN, ticket, test_env=True)
221
+ ```
222
+
223
+ ### 6.4 Extras
224
+
225
+ * `BTGApiError` — exceção customizada para qualquer falha.
226
+
227
+ ---
228
+
229
+ ## 7. Roadmap & Contribuições
230
+
231
+ * **Remover** `ingest.legacy_local` quando não houver mais dependências.
232
+ * Suporte a partições Parquet (delta‑like) na gravação.
233
+ * Adicionar `pydantic` para validar contratos BTG.
234
+ * Pull requests bem‑vindos! Rode `make lint && pytest -q` antes de enviar.
235
+
236
+ ---
237
+
238
+ ### Contatos
239
+
240
+ * Dados / Back‑Office – [backoffice@luxor.com.br](mailto:backoffice@luxor.com.br)
241
+ * Mantenedor principal – Sergio
@@ -0,0 +1,196 @@
1
+ # 📚 Documentação LuxorASAP
2
+
3
+ > Guia do desenvolvedor para os subpacotes **datareader**, **ingest**, **btgapi** e **utils**.
4
+ >
5
+ > • Instalação rápida • Visão arquitetural • APIs detalhadas • Exemplos de uso • Extras opcional
6
+
7
+ ---
8
+
9
+ ## Índice
10
+
11
+ 1. [Visão Geral](#visao-geral)
12
+ 2. [Instalação](#instalacao)
13
+ 3. [utils](#utils)
14
+ 4. [datareader](#datareader)
15
+ 5. [ingest](#ingest)
16
+ 6. [btgapi](#btgapi)
17
+ 7. [Roadmap & Contribuições](#roadmap)
18
+
19
+ ---
20
+
21
+ ## 1. Visão Geral
22
+
23
+ LuxorASAP é o *toolbox* unificado da Luxor para ingestão, consulta e automação de dados financeiros.
24
+
25
+ | Subpacote | Função‑chave | Extras PyPI |
26
+ | -------------- | --------------------------------------------------------------------- | ---------------------- |
27
+ | **utils** | Utilidades puras (I/O ADLS, transformação de DataFrame, decorators) | `storage`, `dataframe` |
28
+ | **datareader** | Consulta de preços/tabelas no data lake via `LuxorQuery` | `datareader` |
29
+ | **ingest** | Carga de dados nova (parquet/zip/excel) em ADLS + loader legado | `ingest` |
30
+ | **btgapi** | Wrapper autenticado para as APIs do BTG Pactual (relatórios & trades) | `btgapi` |
31
+
32
+ ---
33
+
34
+ ## 2. Instalação
35
+
36
+ ```bash
37
+ # core + leitura de dados
38
+ pip install luxorasap[datareader]
39
+
40
+ # tudo incluído (leitura, ingest, btg)
41
+ pip install luxorasap[datareader,ingest,btgapi]
42
+
43
+ # desenvolvimento
44
+ pip install -e ".[dev]"
45
+ ```
46
+
47
+ Extras podem ser combinados à vontade (`luxorasap[storage]`, etc.).
48
+
49
+ Configuração obrigatória do **ADLS**:
50
+
51
+ ```bash
52
+ export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=..."
53
+ ```
54
+
55
+ ---
56
+
57
+ ## 3. utils
58
+
59
+ Camada de utilidades **sem dependências internas**.
60
+
61
+ ### 3.1 storage.BlobParquetClient
62
+
63
+ ```python
64
+ from luxorasap.utils.storage import BlobParquetClient
65
+ client = BlobParquetClient(container="luxorasap")
66
+
67
+ # write
68
+ client.write_df(df, "bronze/parquet/mytable.parquet")
69
+
70
+ # read (tuple -> DataFrame, success_flag)
71
+ df, ok = client.read_df("bronze/parquet/mytable.parquet")
72
+ ```
73
+
74
+ ### 3.2 dataframe
75
+
76
+ ```python
77
+ from luxorasap.utils.dataframe import prep_for_save, persist_column_formatting, read_bytes
78
+
79
+ df2 = prep_for_save(df, index=True, index_name="ID", normalize=True)
80
+ ```
81
+
82
+ ### 3.3 decorators & misc
83
+
84
+ `with_retry`, `chunkify`, `timer`… ficam em *utils.helpers* (caso precise).
85
+
86
+ ---
87
+
88
+ ## 4. datareader
89
+
90
+ ### 4.1 Visão rápida
91
+
92
+ ```python
93
+ from luxorasap.datareader import LuxorQuery
94
+ lq = LuxorQuery()
95
+
96
+ # DataFrame completo
97
+ df = lq.get_table("assets")
98
+
99
+ # Série de preços diária
100
+ aapl = lq.get_prices("aapl us equity", start="2025-01-01", end="2025-03-31")
101
+
102
+ # Preço pontual
103
+ price = lq.get_price("aapl us equity", on="2025-03-31")
104
+ ```
105
+
106
+ *Caching*: `@lru_cache(maxsize=32)` evita hits repetidos ao Blob.
107
+
108
+ ### 4.2 Métodos-chave
109
+
110
+ | Método | Descrição |
111
+ | ----------------------------------------------- | ----------------------------- |
112
+ | `table_exists(name)` | checa metadados no ADLS |
113
+ | `get_table(name)` | DataFrame completo (cached) |
114
+ | `get_prices(asset, start, end, column="Price")` | `pd.Series` |
115
+ | `get_price(asset, on)` | preço pontual (float ou None) |
116
+
117
+ ---
118
+
119
+ ## 5. ingest
120
+
121
+ ### 5.1 ingest.cloud (novo)
122
+
123
+ ```python
124
+ from luxorasap.ingest import save_table, incremental_load
125
+ from luxorasap.datareader import LuxorQuery
126
+
127
+ save_table("trades", df)
128
+
129
+ lq = LuxorQuery()
130
+ incremental_load(lq, "prices_daily", df_new, increment_column="Date")
131
+ ```
132
+
133
+ ### 5.2 ingest.legacy\_local
134
+
135
+ ```python
136
+ from luxorasap.ingest import DataLoader # Deprecado – ainda funcional
137
+ ```
138
+
139
+ *Decoration*: ao importar `DataLoader` você verá `DeprecationWarning`.
140
+
141
+ ---
142
+
143
+ ## 6. btgapi
144
+
145
+ ### 6.1 Autenticação
146
+
147
+ ```python
148
+ from luxorasap.btgapi import get_access_token
149
+ TOKEN = get_access_token(test_env=True)
150
+ ```
151
+
152
+ ### 6.2 Relatórios – Portfolio & Investor Transactions
153
+
154
+ ```python
155
+ from luxorasap.btgapi.reports import (
156
+ request_portfolio, await_report_ticket_result, process_zip_to_dfs,
157
+ request_investors_transactions_report,
158
+ )
159
+
160
+ ticket = request_portfolio(TOKEN, "LUXOR FUND - CLASS A",
161
+ start=dt.date(2025,1,1), end=dt.date(2025,1,31))
162
+ zip_bytes = await_report_ticket_result(TOKEN, ticket)
163
+ carteiras = process_zip_to_dfs(zip_bytes)
164
+ ```
165
+
166
+ ### 6.3 Trades offshore
167
+
168
+ ```python
169
+ from luxorasap.btgapi.trades import (
170
+ submit_offshore_equity_trades,
171
+ await_transaction_ticket_result,
172
+ )
173
+
174
+ ticket = submit_offshore_equity_trades(TOKEN, trades=[{...}], test_env=True)
175
+ status_df = await_transaction_ticket_result(TOKEN, ticket, test_env=True)
176
+ ```
177
+
178
+ ### 6.4 Extras
179
+
180
+ * `BTGApiError` — exceção customizada para qualquer falha.
181
+
182
+ ---
183
+
184
+ ## 7. Roadmap & Contribuições
185
+
186
+ * **Remover** `ingest.legacy_local` quando não houver mais dependências.
187
+ * Suporte a partições Parquet (delta‑like) na gravação.
188
+ * Adicionar `pydantic` para validar contratos BTG.
189
+ * Pull requests bem‑vindos! Rode `make lint && pytest -q` antes de enviar.
190
+
191
+ ---
192
+
193
+ ### Contatos
194
+
195
+ * Dados / Back‑Office – [backoffice@luxor.com.br](mailto:backoffice@luxor.com.br)
196
+ * Mantenedor principal – Sergio
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
10
10
  #############################
11
11
  [project]
12
12
  name = "luxorasap"
13
- version = "0.1.1"
13
+ version = "0.1.3"
14
14
  description = "Luxor’s unified toolbox for data ingestion, querying and analytics."
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.9"
@@ -74,7 +74,7 @@ exclude = ["tests*"]
74
74
  # bumpver (sem-ver)
75
75
  #############################
76
76
  [tool.bumpver]
77
- current_version = "0.1.1"
77
+ current_version = "0.1.3"
78
78
  version_pattern = "MAJOR.MINOR.PATCH"
79
79
 
80
80
  # regex explícito – obrigatório no bumpver 2024+
@@ -13,7 +13,7 @@ from types import ModuleType
13
13
  try:
14
14
  __version__: str = metadata.version(__name__)
15
15
  except metadata.PackageNotFoundError: # editable install
16
- __version__ = "0.1.1"
16
+ __version__ = "0.1.3"
17
17
 
18
18
  # ─── Lazy loader ─────────────────────────────────────────────────
19
19
  def __getattr__(name: str) -> ModuleType:
@@ -1,7 +1,7 @@
1
1
  """Wrapper para as APIs do BTG Pactual."""
2
2
 
3
3
  from .auth import get_access_token, BTGApiError
4
- from .reports import request_portfolio, await_report_ticket_result, process_zip_to_dfs, request_investors_transactions_report
4
+ from .reports import request_portfolio, await_report_ticket_result, process_zip_to_dfs, request_investors_transactions_report, request_fundflow_report
5
5
  from .trades import submit_offshore_equity_trades, await_transaction_ticket_result
6
6
 
7
7
  __all__ = [
@@ -12,5 +12,6 @@ __all__ = [
12
12
  "submit_offshore_equity_trades",
13
13
  "await_transaction_ticket_result",
14
14
  "process_zip_to_dfs",
15
- "request_investors_transactions_report"
15
+ "request_investors_transactions_report",
16
+ "request_fundflow_report",
16
17
  ]
@@ -52,6 +52,7 @@ def get_access_token(*, client_id=None, client_secret=None, test_env: bool = Tru
52
52
 
53
53
  if resp.ok:
54
54
  token = resp.json().get("access_token")
55
- logger.debug("Token BTG obtido (len=%s)", len(token) if token else "None")
55
+ len_token = len(token) if token else None
56
+ logger.debug(f"Token BTG obtido (len={len_token})")
56
57
  return token or ""
57
58
  raise BTGApiError(f"Falha ao autenticar: HTTP {resp.status_code} – {resp.text}")
@@ -16,7 +16,8 @@ __all__ = [
16
16
  "check_report_ticket",
17
17
  "await_report_ticket_result",
18
18
  "process_zip_to_dfs",
19
- "request_investors_transactions_report"
19
+ "request_investors_transactions_report",
20
+ "request_fundflow_report"
20
21
  ]
21
22
 
22
23
  _REPORT_ENDPOINT = "https://funds.btgpactual.com/reports/Portfolio"
@@ -24,6 +25,7 @@ _TICKET_ENDPOINT = "https://funds.btgpactual.com/reports/Ticket"
24
25
  _INVESTOR_TX_ENDPOINT = (
25
26
  "https://funds.btgpactual.com/reports/RTA/InvestorTransactionsFileReport"
26
27
  )
28
+ _FUNDFLOW_ENDPOINT = "https://funds.btgpactual.com/reports/RTA/FundFlow"
27
29
  _REPORT_TYPES = {"excel": 10, "xml5": 81, "pdf": 2}
28
30
 
29
31
 
@@ -93,7 +95,8 @@ def check_report_ticket(token: str, ticket: str, *, page: Optional[int] = None)
93
95
  # 2. Caso contrário tenta decodificar JSON
94
96
 
95
97
  result = payload.get("result")
96
- if result == "Processando":
98
+
99
+ if result == "Processando" or result == 'Aguardando processamento':
97
100
  raise BTGApiError("Processando")
98
101
 
99
102
  # 3. Quando pronto, result é JSON string com UrlDownload
@@ -103,7 +106,16 @@ def check_report_ticket(token: str, ticket: str, *, page: Optional[int] = None)
103
106
  url = info["UrlDownload"]
104
107
  return _download_url(url)
105
108
  except Exception as exc:
106
- raise BTGApiError(f"Falha ao interpretar result: {exc}") from exc
109
+ raise BTGApiError(f"Falha ao interpretar resultado: {exc}") from exc
110
+
111
+ # 4. result pode ser uma lista de dados
112
+ if isinstance(result, list):
113
+ # Vamos tentar transformar num dataframe e retornar
114
+ try:
115
+ df = pd.DataFrame(result)
116
+ return df
117
+ except Exception as exc:
118
+ raise BTGApiError(f"Falha ao converter resultado em DataFrame: {exc}") from exc
107
119
 
108
120
  raise BTGApiError("Formato de resposta desconhecido")
109
121
 
@@ -129,7 +141,7 @@ def await_report_ticket_result(token: str, ticket: str, *, attempts: int = 10,
129
141
  return check_report_ticket(token, ticket)
130
142
  except BTGApiError as err:
131
143
  if "Processando" in str(err):
132
- logger.debug("Ticket %s pendente (%d/%d)", ticket, i + 1, attempts)
144
+ logger.debug(f"Ticket {ticket} pendente ({i+1}/{attempts})")
133
145
  time.sleep(interval)
134
146
  continue
135
147
  raise
@@ -185,4 +197,43 @@ def request_investors_transactions_report( token: str, query_date: dt.date, *,
185
197
  return r.json()["ticket"]
186
198
  raise BTGApiError(
187
199
  f"Erro InvestorTransactionsFileReport: {r.status_code} – {r.text}"
188
- )
200
+ )
201
+
202
+
203
+ def request_fundflow_report( token: str, start_date: dt.date,
204
+ end_date: dt.date, *, fund_name: str = "", date_type: str = "LIQUIDACAO", page_size: int = 100) -> str:
205
+ """Dispara geração do **Fund Flow** (RTA) e devolve *ticket*.
206
+
207
+ Args:
208
+ token: JWT obtido via :pyfunc:`luxorasap.btgapi.get_access_token`.
209
+ start_date,end_date: Datas do intervalo desejado.
210
+ fund_name: Nome do fundo conforme BTG. String vazia retorna as movimentacoes para todos os fundos.
211
+ date_type: Enum da API (`LIQUIDACAO`, `MOVIMENTO`, etc.).
212
+ page_size: Página retornada por chamada (default 100).
213
+
214
+ Returns
215
+ -------
216
+ str
217
+ ID do ticket a ser acompanhado em :pyfunc:`await_fundflow_ticket_result`.
218
+ """
219
+
220
+ body = {
221
+ "contract": {
222
+ "startDate": f"{start_date}T00:00:00Z",
223
+ "endDate": f"{end_date}T00:00:00Z",
224
+ "dateType": date_type,
225
+ "fundName": fund_name,
226
+ },
227
+ "pageSize": page_size,
228
+ "webhookEndpoint": "string",
229
+ }
230
+
231
+ r = requests.post(
232
+ _FUNDFLOW_ENDPOINT,
233
+ headers={"X-SecureConnect-Token": token, "Content-Type": "application/json"},
234
+ json=body,
235
+ timeout=30,
236
+ )
237
+ if r.ok:
238
+ return r.json()["ticket"]
239
+ raise BTGApiError(f"Erro FundFlow: {r.status_code} - {r.text}")
@@ -18,10 +18,17 @@ def save_table(
18
18
  *,
19
19
  index: bool = False,
20
20
  index_name: str = "index",
21
- normalize_columns: bool = False,
21
+ normalize_columns: bool = True,
22
22
  directory: str = "enriched/parquet",
23
+ override=False
23
24
  ):
24
25
  """Salva DataFrame como Parquet em ADLS (sobrescrevendo)."""
26
+
27
+ if override == False:
28
+ lq = LuxorQuery()
29
+ if lq.table_exists(table_name):
30
+ return
31
+
25
32
  df = prep_for_save(df, index=index, index_name=index_name, normalize=normalize_columns)
26
33
  _client.write_df(df.astype(str), f"{directory}/{table_name}.parquet")
27
34
 
@@ -34,7 +41,7 @@ def incremental_load(
34
41
  increment_column: str = "Date",
35
42
  index: bool = False,
36
43
  index_name: str = "index",
37
- normalize_columns: bool = False,
44
+ normalize_columns: bool = True,
38
45
  directory: str = "enriched/parquet",
39
46
  ):
40
47
  """Concatena novos dados aos existentes, cortando duplicados pela data."""
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: luxorasap
3
+ Version: 0.1.3
4
+ Summary: Luxor’s unified toolbox for data ingestion, querying and analytics.
5
+ Author-email: Luxor Group <backoffice@luxor.com.br>
6
+ License: Proprietary – All rights reserved
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: Other/Proprietary License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: pandas>=2.2
13
+ Requires-Dist: numpy>=1.25
14
+ Requires-Dist: loguru>=0.7
15
+ Requires-Dist: python-dotenv>=1.0
16
+ Requires-Dist: azure-storage-blob>=12.19
17
+ Requires-Dist: pyarrow>=15.0
18
+ Requires-Dist: requests>=2.32
19
+ Requires-Dist: pydantic>=2.7
20
+ Requires-Dist: scipy>=1.13
21
+ Requires-Dist: openpyxl
22
+ Provides-Extra: storage
23
+ Requires-Dist: azure-storage-blob>=12.19; extra == "storage"
24
+ Requires-Dist: pyarrow>=15.0; extra == "storage"
25
+ Provides-Extra: dataframe
26
+ Requires-Dist: pandas>=2.2; extra == "dataframe"
27
+ Provides-Extra: datareader
28
+ Requires-Dist: luxorasap[dataframe,storage]; extra == "datareader"
29
+ Requires-Dist: numpy>=1.25; extra == "datareader"
30
+ Requires-Dist: scipy>=1.13; extra == "datareader"
31
+ Provides-Extra: ingest
32
+ Requires-Dist: luxorasap[dataframe,storage]; extra == "ingest"
33
+ Requires-Dist: pandas>=2.2; extra == "ingest"
34
+ Provides-Extra: btgapi
35
+ Requires-Dist: requests>=2.32; extra == "btgapi"
36
+ Requires-Dist: pydantic>=2.7; extra == "btgapi"
37
+ Provides-Extra: dev
38
+ Requires-Dist: pytest>=8.2; extra == "dev"
39
+ Requires-Dist: requests-mock>=1.11; extra == "dev"
40
+ Requires-Dist: black>=24.4.0; extra == "dev"
41
+ Requires-Dist: isort>=5.13; extra == "dev"
42
+ Requires-Dist: bumpver>=2024.3; extra == "dev"
43
+ Requires-Dist: pre-commit>=3.7; extra == "dev"
44
+ Requires-Dist: build>=1.2; extra == "dev"
45
+
46
+ # 📚 Documentação LuxorASAP
47
+
48
+ > Guia do desenvolvedor para os subpacotes **datareader**, **ingest**, **btgapi** e **utils**.
49
+ >
50
+ > • Instalação rápida • Visão arquitetural • APIs detalhadas • Exemplos de uso • Extras opcional
51
+
52
+ ---
53
+
54
+ ## Índice
55
+
56
+ 1. [Visão Geral](#visao-geral)
57
+ 2. [Instalação](#instalacao)
58
+ 3. [utils](#utils)
59
+ 4. [datareader](#datareader)
60
+ 5. [ingest](#ingest)
61
+ 6. [btgapi](#btgapi)
62
+ 7. [Roadmap & Contribuições](#roadmap)
63
+
64
+ ---
65
+
66
+ ## 1. Visão Geral
67
+
68
+ LuxorASAP é o *toolbox* unificado da Luxor para ingestão, consulta e automação de dados financeiros.
69
+
70
+ | Subpacote | Função‑chave | Extras PyPI |
71
+ | -------------- | --------------------------------------------------------------------- | ---------------------- |
72
+ | **utils** | Utilidades puras (I/O ADLS, transformação de DataFrame, decorators) | `storage`, `dataframe` |
73
+ | **datareader** | Consulta de preços/tabelas no data lake via `LuxorQuery` | `datareader` |
74
+ | **ingest** | Carga de dados nova (parquet/zip/excel) em ADLS + loader legado | `ingest` |
75
+ | **btgapi** | Wrapper autenticado para as APIs do BTG Pactual (relatórios & trades) | `btgapi` |
76
+
77
+ ---
78
+
79
+ ## 2. Instalação
80
+
81
+ ```bash
82
+ # core + leitura de dados
83
+ pip install luxorasap[datareader]
84
+
85
+ # tudo incluído (leitura, ingest, btg)
86
+ pip install luxorasap[datareader,ingest,btgapi]
87
+
88
+ # desenvolvimento
89
+ pip install -e ".[dev]"
90
+ ```
91
+
92
+ Extras podem ser combinados à vontade (`luxorasap[storage]`, etc.).
93
+
94
+ Configuração obrigatória do **ADLS**:
95
+
96
+ ```bash
97
+ export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=..."
98
+ ```
99
+
100
+ ---
101
+
102
+ ## 3. utils
103
+
104
+ Camada de utilidades **sem dependências internas**.
105
+
106
+ ### 3.1 storage.BlobParquetClient
107
+
108
+ ```python
109
+ from luxorasap.utils.storage import BlobParquetClient
110
+ client = BlobParquetClient(container="luxorasap")
111
+
112
+ # write
113
+ client.write_df(df, "bronze/parquet/mytable.parquet")
114
+
115
+ # read (tuple -> DataFrame, success_flag)
116
+ df, ok = client.read_df("bronze/parquet/mytable.parquet")
117
+ ```
118
+
119
+ ### 3.2 dataframe
120
+
121
+ ```python
122
+ from luxorasap.utils.dataframe import prep_for_save, persist_column_formatting, read_bytes
123
+
124
+ df2 = prep_for_save(df, index=True, index_name="ID", normalize=True)
125
+ ```
126
+
127
+ ### 3.3 decorators & misc
128
+
129
+ `with_retry`, `chunkify`, `timer`… ficam em *utils.helpers* (caso precise).
130
+
131
+ ---
132
+
133
+ ## 4. datareader
134
+
135
+ ### 4.1 Visão rápida
136
+
137
+ ```python
138
+ from luxorasap.datareader import LuxorQuery
139
+ lq = LuxorQuery()
140
+
141
+ # DataFrame completo
142
+ df = lq.get_table("assets")
143
+
144
+ # Série de preços diária
145
+ aapl = lq.get_prices("aapl us equity", start="2025-01-01", end="2025-03-31")
146
+
147
+ # Preço pontual
148
+ price = lq.get_price("aapl us equity", on="2025-03-31")
149
+ ```
150
+
151
+ *Caching*: `@lru_cache(maxsize=32)` evita hits repetidos ao Blob.
152
+
153
+ ### 4.2 Métodos-chave
154
+
155
+ | Método | Descrição |
156
+ | ----------------------------------------------- | ----------------------------- |
157
+ | `table_exists(name)` | checa metadados no ADLS |
158
+ | `get_table(name)` | DataFrame completo (cached) |
159
+ | `get_prices(asset, start, end, column="Price")` | `pd.Series` |
160
+ | `get_price(asset, on)` | preço pontual (float ou None) |
161
+
162
+ ---
163
+
164
+ ## 5. ingest
165
+
166
+ ### 5.1 ingest.cloud (novo)
167
+
168
+ ```python
169
+ from luxorasap.ingest import save_table, incremental_load
170
+ from luxorasap.datareader import LuxorQuery
171
+
172
+ save_table("trades", df)
173
+
174
+ lq = LuxorQuery()
175
+ incremental_load(lq, "prices_daily", df_new, increment_column="Date")
176
+ ```
177
+
178
+ ### 5.2 ingest.legacy\_local
179
+
180
+ ```python
181
+ from luxorasap.ingest import DataLoader # Deprecado – ainda funcional
182
+ ```
183
+
184
+ *Decoration*: ao importar `DataLoader` você verá `DeprecationWarning`.
185
+
186
+ ---
187
+
188
+ ## 6. btgapi
189
+
190
+ ### 6.1 Autenticação
191
+
192
+ ```python
193
+ from luxorasap.btgapi import get_access_token
194
+ TOKEN = get_access_token(test_env=True)
195
+ ```
196
+
197
+ ### 6.2 Relatórios – Portfolio & Investor Transactions
198
+
199
+ ```python
200
+ from luxorasap.btgapi.reports import (
201
+ request_portfolio, await_report_ticket_result, process_zip_to_dfs,
202
+ request_investors_transactions_report,
203
+ )
204
+
205
+ ticket = request_portfolio(TOKEN, "LUXOR FUND - CLASS A",
206
+ start=dt.date(2025,1,1), end=dt.date(2025,1,31))
207
+ zip_bytes = await_report_ticket_result(TOKEN, ticket)
208
+ carteiras = process_zip_to_dfs(zip_bytes)
209
+ ```
210
+
211
+ ### 6.3 Trades offshore
212
+
213
+ ```python
214
+ from luxorasap.btgapi.trades import (
215
+ submit_offshore_equity_trades,
216
+ await_transaction_ticket_result,
217
+ )
218
+
219
+ ticket = submit_offshore_equity_trades(TOKEN, trades=[{...}], test_env=True)
220
+ status_df = await_transaction_ticket_result(TOKEN, ticket, test_env=True)
221
+ ```
222
+
223
+ ### 6.4 Extras
224
+
225
+ * `BTGApiError` — exceção customizada para qualquer falha.
226
+
227
+ ---
228
+
229
+ ## 7. Roadmap & Contribuições
230
+
231
+ * **Remover** `ingest.legacy_local` quando não houver mais dependências.
232
+ * Suporte a partições Parquet (delta‑like) na gravação.
233
+ * Adicionar `pydantic` para validar contratos BTG.
234
+ * Pull requests bem‑vindos! Rode `make lint && pytest -q` antes de enviar.
235
+
236
+ ---
237
+
238
+ ### Contatos
239
+
240
+ * Dados / Back‑Office – [backoffice@luxor.com.br](mailto:backoffice@luxor.com.br)
241
+ * Mantenedor principal – Sergio
@@ -0,0 +1,139 @@
1
+ import datetime as dt, io, json, zipfile
2
+ import pandas as pd
3
+ import pytest
4
+
5
+ from luxorasap.btgapi.reports import (
6
+ request_portfolio,
7
+ await_report_ticket_result,
8
+ process_zip_to_dfs,
9
+ check_report_ticket,
10
+ request_fundflow_report,
11
+ )
12
+
13
+ _TICKET_URL = "https://funds.btgpactual.com/reports/Ticket"
14
+ _POST_URL = "https://funds.btgpactual.com/reports/Portfolio"
15
+ _FUNDFLOW_URL = "https://funds.btgpactual.com/reports/RTA/FundFlow"
16
+
17
+ _TOKEN = "dummy-token"
18
+
19
+ def test_request_portfolio_returns_ticket(requests_mock):
20
+ requests_mock.post(_POST_URL, json={"ticket": "ABC"})
21
+ tk = request_portfolio("tok", "FUND", dt.date(2025,1,1), dt.date(2025,1,31))
22
+ assert tk == "ABC"
23
+
24
+ def test_await_ticket_inline_zip(requests_mock, monkeypatch):
25
+ # 1ª chamada: ainda processando 2ª: devolve ZIP binário
26
+ # Explicando o que requests_mock faz:
27
+ #
28
+ requests_mock.get(_TICKET_URL, [
29
+ {"json": {"result": "Processando"}},
30
+ {"content": b"ZIP!"},
31
+ ])
32
+ monkeypatch.setattr("time.sleep", lambda *_: None)
33
+ out = await_report_ticket_result("tok", "ABC", attempts=2, interval=0)
34
+ assert out == b"ZIP!"
35
+
36
+ def test_await_ticket_via_urldownload(requests_mock, monkeypatch):
37
+ dl_url = "https://download/file.zip"
38
+ # 1ª chamada ao ticket devolve JSON com UrlDownload
39
+ requests_mock.get(_TICKET_URL, json={"result": json.dumps({"UrlDownload": dl_url})})
40
+ requests_mock.get(dl_url, content=b"ZIP2", headers={"Content-Type": "application/zip"})
41
+ monkeypatch.setattr("time.sleep", lambda *_: None)
42
+ out = await_report_ticket_result("tok", "XYZ", attempts=1)
43
+ assert out == b"ZIP2"
44
+
45
+
46
+ def test_process_zip_to_dfs():
47
+ buf = io.BytesIO()
48
+ with zipfile.ZipFile(buf, "w") as zf:
49
+ df = pd.DataFrame({"x": [1]})
50
+ zf.writestr("data.csv", df.to_csv(index=False))
51
+ dfs = process_zip_to_dfs(buf.getvalue())
52
+ assert dfs["data.csv"].iloc[0, 0] == 1
53
+
54
+
55
+ def test_process_zip_latin1_csv():
56
+ import io, zipfile, pandas as pd
57
+ from luxorasap.btgapi.reports import process_zip_to_dfs
58
+
59
+ df_src = pd.DataFrame({"nome": ["ação", "æøå"]})
60
+ csv_latin1 = df_src.to_csv(index=False, encoding="latin1")
61
+
62
+ buf = io.BytesIO()
63
+ with zipfile.ZipFile(buf, "w") as zf:
64
+ zf.writestr("dados.csv", csv_latin1)
65
+
66
+ dfs = process_zip_to_dfs(buf.getvalue())
67
+ assert dfs["dados.csv"].equals(df_src)
68
+
69
+
70
+ def test_request_fundflow_report_success(requests_mock):
71
+ """POST devolvendo ticket e corpo montado corretamente."""
72
+ start, end = dt.date(2025, 5, 4), dt.date(2025, 6, 4)
73
+ fund_name = "luxor lipizzaner fia"
74
+ expected_id = "TCK-123"
75
+
76
+ # valida corpo enviado
77
+ def _match(request):
78
+ payload = request.json()
79
+ c = payload["contract"]
80
+ assert c["startDate"] == f"{start}T00:00:00Z"
81
+ assert c["endDate"] == f"{end}T00:00:00Z"
82
+ assert c["fundName"] == fund_name
83
+ assert c["dateType"] == "LIQUIDACAO"
84
+ return True
85
+
86
+ requests_mock.post(
87
+ _FUNDFLOW_URL,
88
+ additional_matcher=_match,
89
+ json={"ticket": expected_id},
90
+ status_code=200,
91
+ )
92
+
93
+ ticket = request_fundflow_report(
94
+ _TOKEN, start, end, fund_name=fund_name
95
+ )
96
+ assert ticket == expected_id
97
+
98
+
99
+ def test_check_report_ticket_returns_dataframe(requests_mock):
100
+ """GET devolvendo JSON-list deve virar DataFrame."""
101
+ ticket_id = "TCK-LIST"
102
+ sample_rows = [
103
+ {"customerName": "A", "valueTotal": 1_000},
104
+ {"customerName": "B", "valueTotal": 2_000},
105
+ ]
106
+
107
+ # qualquer query param é aceito; podemos validar se quiser via matcher
108
+ requests_mock.get(
109
+ _TICKET_URL,
110
+ json={"result": sample_rows},
111
+ status_code=200,
112
+ )
113
+
114
+ df = check_report_ticket(_TOKEN, ticket_id)
115
+ assert isinstance(df, pd.DataFrame)
116
+ assert len(df) == 2
117
+ assert set(df.columns) == {"customerName", "valueTotal"}
118
+
119
+
120
+ def test_await_report_ticket_polls_until_ready(requests_mock, monkeypatch):
121
+ """Primeira chamada ‘Processando’ → segunda retorna lista."""
122
+ ticket_id = "TCK-POLL"
123
+ ready_rows = [{"x": 1}]
124
+
125
+ # sequencia de respostas: processamento → pronto
126
+ requests_mock.get(
127
+ _TICKET_URL,
128
+ [
129
+ {"json": {"result": "Processando"}, "status_code": 200},
130
+ {"json": {"result": ready_rows}, "status_code": 200},
131
+ ],
132
+ )
133
+
134
+ # evita atraso real de sleep
135
+ monkeypatch.setattr("luxorasap.btgapi.reports.time.sleep", lambda *_: None)
136
+
137
+ df = await_report_ticket_result(_TOKEN, ticket_id, attempts=2, interval=0)
138
+ assert isinstance(df, pd.DataFrame)
139
+ assert df.iloc[0, 0] == 1
luxorasap-0.1.1/PKG-INFO DELETED
@@ -1,80 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: luxorasap
3
- Version: 0.1.1
4
- Summary: Luxor’s unified toolbox for data ingestion, querying and analytics.
5
- Author-email: Luxor Group <backoffice@luxor.com.br>
6
- License: Proprietary – All rights reserved
7
- Classifier: Programming Language :: Python :: 3
8
- Classifier: License :: Other/Proprietary License
9
- Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.9
11
- Description-Content-Type: text/markdown
12
- Requires-Dist: pandas>=2.2
13
- Requires-Dist: numpy>=1.25
14
- Requires-Dist: loguru>=0.7
15
- Requires-Dist: python-dotenv>=1.0
16
- Requires-Dist: azure-storage-blob>=12.19
17
- Requires-Dist: pyarrow>=15.0
18
- Requires-Dist: requests>=2.32
19
- Requires-Dist: pydantic>=2.7
20
- Requires-Dist: scipy>=1.13
21
- Requires-Dist: openpyxl
22
- Provides-Extra: storage
23
- Requires-Dist: azure-storage-blob>=12.19; extra == "storage"
24
- Requires-Dist: pyarrow>=15.0; extra == "storage"
25
- Provides-Extra: dataframe
26
- Requires-Dist: pandas>=2.2; extra == "dataframe"
27
- Provides-Extra: datareader
28
- Requires-Dist: luxorasap[dataframe,storage]; extra == "datareader"
29
- Requires-Dist: numpy>=1.25; extra == "datareader"
30
- Requires-Dist: scipy>=1.13; extra == "datareader"
31
- Provides-Extra: ingest
32
- Requires-Dist: luxorasap[dataframe,storage]; extra == "ingest"
33
- Requires-Dist: pandas>=2.2; extra == "ingest"
34
- Provides-Extra: btgapi
35
- Requires-Dist: requests>=2.32; extra == "btgapi"
36
- Requires-Dist: pydantic>=2.7; extra == "btgapi"
37
- Provides-Extra: dev
38
- Requires-Dist: pytest>=8.2; extra == "dev"
39
- Requires-Dist: requests-mock>=1.11; extra == "dev"
40
- Requires-Dist: black>=24.4.0; extra == "dev"
41
- Requires-Dist: isort>=5.13; extra == "dev"
42
- Requires-Dist: bumpver>=2024.3; extra == "dev"
43
- Requires-Dist: pre-commit>=3.7; extra == "dev"
44
- Requires-Dist: build>=1.2; extra == "dev"
45
-
46
- # LuxorASAP
47
-
48
- **LuxorASAP** é o pacote-guarda-chuva que concentra as ferramentas internas de dados da Luxor Group:
49
- consulta estruturada ao data lake, cargas padronizadas para ADLS, wrappers de API, utilitários e muito mais.
50
-
51
- [![PyPI](https://img.shields.io/pypi/v/luxorasap.svg)](https://pypi.org/project/luxorasap/)
52
- ![Python](https://img.shields.io/pypi/pyversions/luxorasap)
53
-
54
- ---
55
-
56
- ## Instalação
57
-
58
- ```bash
59
- # pacote base
60
- pip install luxorasap
61
-
62
- # com o submódulo datareader
63
- pip install "luxorasap[datareader]"
64
- ```
65
- ## Uso rápido
66
- ```python
67
- from luxorasap.datareader import LuxorQuery
68
-
69
- lq = LuxorQuery(blob_directory="enriched/parquet")
70
- prices = lq.get_prices("aapl us equity", "2024-01-01", "2024-12-31")
71
- print(prices.head())
72
- ```
73
- ## Submódulos
74
- | Módulo | Descrição rápida | Extras |
75
- | ---------------------- | ---------------------------------------- | ------------------------------------- |
76
- | `luxorasap.datareader` | Leitura de tabelas e séries no data lake | `pip install "luxorasap[datareader]"` |
77
- | `luxorasap.ingest` | Funções de carga padronizada para ADLS | `"luxorasap[ingest]"` |
78
- | `luxorasap.btgapi` | Wrapper REST para dados BTG | `"luxorasap[btgapi]"` |
79
-
80
- © Luxor Group – uso interno. Todos os direitos reservados.
luxorasap-0.1.1/README.md DELETED
@@ -1,35 +0,0 @@
1
- # LuxorASAP
2
-
3
- **LuxorASAP** é o pacote-guarda-chuva que concentra as ferramentas internas de dados da Luxor Group:
4
- consulta estruturada ao data lake, cargas padronizadas para ADLS, wrappers de API, utilitários e muito mais.
5
-
6
- [![PyPI](https://img.shields.io/pypi/v/luxorasap.svg)](https://pypi.org/project/luxorasap/)
7
- ![Python](https://img.shields.io/pypi/pyversions/luxorasap)
8
-
9
- ---
10
-
11
- ## Instalação
12
-
13
- ```bash
14
- # pacote base
15
- pip install luxorasap
16
-
17
- # com o submódulo datareader
18
- pip install "luxorasap[datareader]"
19
- ```
20
- ## Uso rápido
21
- ```python
22
- from luxorasap.datareader import LuxorQuery
23
-
24
- lq = LuxorQuery(blob_directory="enriched/parquet")
25
- prices = lq.get_prices("aapl us equity", "2024-01-01", "2024-12-31")
26
- print(prices.head())
27
- ```
28
- ## Submódulos
29
- | Módulo | Descrição rápida | Extras |
30
- | ---------------------- | ---------------------------------------- | ------------------------------------- |
31
- | `luxorasap.datareader` | Leitura de tabelas e séries no data lake | `pip install "luxorasap[datareader]"` |
32
- | `luxorasap.ingest` | Funções de carga padronizada para ADLS | `"luxorasap[ingest]"` |
33
- | `luxorasap.btgapi` | Wrapper REST para dados BTG | `"luxorasap[btgapi]"` |
34
-
35
- © Luxor Group – uso interno. Todos os direitos reservados.
@@ -1,80 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: luxorasap
3
- Version: 0.1.1
4
- Summary: Luxor’s unified toolbox for data ingestion, querying and analytics.
5
- Author-email: Luxor Group <backoffice@luxor.com.br>
6
- License: Proprietary – All rights reserved
7
- Classifier: Programming Language :: Python :: 3
8
- Classifier: License :: Other/Proprietary License
9
- Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.9
11
- Description-Content-Type: text/markdown
12
- Requires-Dist: pandas>=2.2
13
- Requires-Dist: numpy>=1.25
14
- Requires-Dist: loguru>=0.7
15
- Requires-Dist: python-dotenv>=1.0
16
- Requires-Dist: azure-storage-blob>=12.19
17
- Requires-Dist: pyarrow>=15.0
18
- Requires-Dist: requests>=2.32
19
- Requires-Dist: pydantic>=2.7
20
- Requires-Dist: scipy>=1.13
21
- Requires-Dist: openpyxl
22
- Provides-Extra: storage
23
- Requires-Dist: azure-storage-blob>=12.19; extra == "storage"
24
- Requires-Dist: pyarrow>=15.0; extra == "storage"
25
- Provides-Extra: dataframe
26
- Requires-Dist: pandas>=2.2; extra == "dataframe"
27
- Provides-Extra: datareader
28
- Requires-Dist: luxorasap[dataframe,storage]; extra == "datareader"
29
- Requires-Dist: numpy>=1.25; extra == "datareader"
30
- Requires-Dist: scipy>=1.13; extra == "datareader"
31
- Provides-Extra: ingest
32
- Requires-Dist: luxorasap[dataframe,storage]; extra == "ingest"
33
- Requires-Dist: pandas>=2.2; extra == "ingest"
34
- Provides-Extra: btgapi
35
- Requires-Dist: requests>=2.32; extra == "btgapi"
36
- Requires-Dist: pydantic>=2.7; extra == "btgapi"
37
- Provides-Extra: dev
38
- Requires-Dist: pytest>=8.2; extra == "dev"
39
- Requires-Dist: requests-mock>=1.11; extra == "dev"
40
- Requires-Dist: black>=24.4.0; extra == "dev"
41
- Requires-Dist: isort>=5.13; extra == "dev"
42
- Requires-Dist: bumpver>=2024.3; extra == "dev"
43
- Requires-Dist: pre-commit>=3.7; extra == "dev"
44
- Requires-Dist: build>=1.2; extra == "dev"
45
-
46
- # LuxorASAP
47
-
48
- **LuxorASAP** é o pacote-guarda-chuva que concentra as ferramentas internas de dados da Luxor Group:
49
- consulta estruturada ao data lake, cargas padronizadas para ADLS, wrappers de API, utilitários e muito mais.
50
-
51
- [![PyPI](https://img.shields.io/pypi/v/luxorasap.svg)](https://pypi.org/project/luxorasap/)
52
- ![Python](https://img.shields.io/pypi/pyversions/luxorasap)
53
-
54
- ---
55
-
56
- ## Instalação
57
-
58
- ```bash
59
- # pacote base
60
- pip install luxorasap
61
-
62
- # com o submódulo datareader
63
- pip install "luxorasap[datareader]"
64
- ```
65
- ## Uso rápido
66
- ```python
67
- from luxorasap.datareader import LuxorQuery
68
-
69
- lq = LuxorQuery(blob_directory="enriched/parquet")
70
- prices = lq.get_prices("aapl us equity", "2024-01-01", "2024-12-31")
71
- print(prices.head())
72
- ```
73
- ## Submódulos
74
- | Módulo | Descrição rápida | Extras |
75
- | ---------------------- | ---------------------------------------- | ------------------------------------- |
76
- | `luxorasap.datareader` | Leitura de tabelas e séries no data lake | `pip install "luxorasap[datareader]"` |
77
- | `luxorasap.ingest` | Funções de carga padronizada para ADLS | `"luxorasap[ingest]"` |
78
- | `luxorasap.btgapi` | Wrapper REST para dados BTG | `"luxorasap[btgapi]"` |
79
-
80
- © Luxor Group – uso interno. Todos os direitos reservados.
@@ -1,62 +0,0 @@
1
- import datetime as dt, io, json, zipfile
2
- import pandas as pd
3
- import pytest
4
-
5
- from luxorasap.btgapi.reports import (
6
- request_portfolio,
7
- await_report_ticket_result,
8
- process_zip_to_dfs,
9
- )
10
-
11
- _TICKET_URL = "https://funds.btgpactual.com/reports/Ticket"
12
- _POST_URL = "https://funds.btgpactual.com/reports/Portfolio"
13
-
14
- def test_request_portfolio_returns_ticket(requests_mock):
15
- requests_mock.post(_POST_URL, json={"ticket": "ABC"})
16
- tk = request_portfolio("tok", "FUND", dt.date(2025,1,1), dt.date(2025,1,31))
17
- assert tk == "ABC"
18
-
19
- def test_await_ticket_inline_zip(requests_mock, monkeypatch):
20
- # 1ª chamada: ainda processando 2ª: devolve ZIP binário
21
- # Explicando o que requests_mock faz:
22
- #
23
- requests_mock.get(_TICKET_URL, [
24
- {"json": {"result": "Processando"}},
25
- {"content": b"ZIP!"},
26
- ])
27
- monkeypatch.setattr("time.sleep", lambda *_: None)
28
- out = await_report_ticket_result("tok", "ABC", attempts=2, interval=0)
29
- assert out == b"ZIP!"
30
-
31
- def test_await_ticket_via_urldownload(requests_mock, monkeypatch):
32
- dl_url = "https://download/file.zip"
33
- # 1ª chamada ao ticket devolve JSON com UrlDownload
34
- requests_mock.get(_TICKET_URL, json={"result": json.dumps({"UrlDownload": dl_url})})
35
- requests_mock.get(dl_url, content=b"ZIP2", headers={"Content-Type": "application/zip"})
36
- monkeypatch.setattr("time.sleep", lambda *_: None)
37
- out = await_report_ticket_result("tok", "XYZ", attempts=1)
38
- assert out == b"ZIP2"
39
-
40
-
41
- def test_process_zip_to_dfs():
42
- buf = io.BytesIO()
43
- with zipfile.ZipFile(buf, "w") as zf:
44
- df = pd.DataFrame({"x": [1]})
45
- zf.writestr("data.csv", df.to_csv(index=False))
46
- dfs = process_zip_to_dfs(buf.getvalue())
47
- assert dfs["data.csv"].iloc[0, 0] == 1
48
-
49
-
50
- def test_process_zip_latin1_csv():
51
- import io, zipfile, pandas as pd
52
- from luxorasap.btgapi.reports import process_zip_to_dfs
53
-
54
- df_src = pd.DataFrame({"nome": ["ação", "æøå"]})
55
- csv_latin1 = df_src.to_csv(index=False, encoding="latin1")
56
-
57
- buf = io.BytesIO()
58
- with zipfile.ZipFile(buf, "w") as zf:
59
- zf.writestr("dados.csv", csv_latin1)
60
-
61
- dfs = process_zip_to_dfs(buf.getvalue())
62
- assert dfs["dados.csv"].equals(df_src)
File without changes