cnovatech-datamarket 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.
Files changed (68) hide show
  1. cnovatech_datamarket-0.1.0/.gitignore +26 -0
  2. cnovatech_datamarket-0.1.0/CHANGELOG.md +49 -0
  3. cnovatech_datamarket-0.1.0/LICENSE +21 -0
  4. cnovatech_datamarket-0.1.0/PKG-INFO +235 -0
  5. cnovatech_datamarket-0.1.0/README.md +203 -0
  6. cnovatech_datamarket-0.1.0/pyproject.toml +98 -0
  7. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/__init__.py +20 -0
  8. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/_client.py +145 -0
  9. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/_config.py +24 -0
  10. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/_version.py +9 -0
  11. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/errors.py +117 -0
  12. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/models/__init__.py +168 -0
  13. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/models/account.py +186 -0
  14. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/models/brands.py +51 -0
  15. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/models/common.py +56 -0
  16. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/models/history.py +74 -0
  17. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/models/products.py +243 -0
  18. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/models/stores.py +322 -0
  19. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/py.typed +0 -0
  20. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/resources/__init__.py +24 -0
  21. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/resources/_operations.py +507 -0
  22. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/resources/_pricing.py +32 -0
  23. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/resources/_validation.py +28 -0
  24. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/resources/account.py +105 -0
  25. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/resources/brands.py +57 -0
  26. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/resources/products.py +343 -0
  27. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/resources/stores.py +475 -0
  28. cnovatech_datamarket-0.1.0/src/cnovatech/datamarket/types.py +37 -0
  29. cnovatech_datamarket-0.1.0/tests/conftest.py +135 -0
  30. cnovatech_datamarket-0.1.0/tests/fixtures/README.md +31 -0
  31. cnovatech_datamarket-0.1.0/tests/fixtures/checkCatalogCoverage.json +45 -0
  32. cnovatech_datamarket-0.1.0/tests/fixtures/error_ean_not_found.json +7 -0
  33. cnovatech_datamarket-0.1.0/tests/fixtures/error_filter_required.json +7 -0
  34. cnovatech_datamarket-0.1.0/tests/fixtures/error_validation_error.json +12 -0
  35. cnovatech_datamarket-0.1.0/tests/fixtures/getCatalog.json +150 -0
  36. cnovatech_datamarket-0.1.0/tests/fixtures/getCompetitorProducts.json +107 -0
  37. cnovatech_datamarket-0.1.0/tests/fixtures/getProductByEan.json +142 -0
  38. cnovatech_datamarket-0.1.0/tests/fixtures/getProductPriceHistory.json +92 -0
  39. cnovatech_datamarket-0.1.0/tests/fixtures/getProductsByBrand.json +96 -0
  40. cnovatech_datamarket-0.1.0/tests/fixtures/getProfile.json +52 -0
  41. cnovatech_datamarket-0.1.0/tests/fixtures/getStoreAssortment.json +133 -0
  42. cnovatech_datamarket-0.1.0/tests/fixtures/getStoreCompetitiveness.json +59 -0
  43. cnovatech_datamarket-0.1.0/tests/fixtures/getStoreCompetitors.json +52 -0
  44. cnovatech_datamarket-0.1.0/tests/fixtures/getStoreMarketPosition.json +138 -0
  45. cnovatech_datamarket-0.1.0/tests/fixtures/getStoreOpportunities.json +130 -0
  46. cnovatech_datamarket-0.1.0/tests/fixtures/getStoresByBrand.json +108 -0
  47. cnovatech_datamarket-0.1.0/tests/fixtures/getUsage.json +31 -0
  48. cnovatech_datamarket-0.1.0/tests/fixtures/getUserLogs.json +108 -0
  49. cnovatech_datamarket-0.1.0/tests/fixtures/healthCheck.json +7 -0
  50. cnovatech_datamarket-0.1.0/tests/fixtures/listApiKeys.json +49 -0
  51. cnovatech_datamarket-0.1.0/tests/fixtures/listBrands.json +112 -0
  52. cnovatech_datamarket-0.1.0/tests/fixtures/listDepartments.json +140 -0
  53. cnovatech_datamarket-0.1.0/tests/fixtures/listStores.json +66 -0
  54. cnovatech_datamarket-0.1.0/tests/fixtures/openapi.json +1 -0
  55. cnovatech_datamarket-0.1.0/tests/test_account.py +110 -0
  56. cnovatech_datamarket-0.1.0/tests/test_brands.py +59 -0
  57. cnovatech_datamarket-0.1.0/tests/test_client.py +145 -0
  58. cnovatech_datamarket-0.1.0/tests/test_contract.py +52 -0
  59. cnovatech_datamarket-0.1.0/tests/test_find.py +123 -0
  60. cnovatech_datamarket-0.1.0/tests/test_live.py +44 -0
  61. cnovatech_datamarket-0.1.0/tests/test_models.py +112 -0
  62. cnovatech_datamarket-0.1.0/tests/test_operations.py +51 -0
  63. cnovatech_datamarket-0.1.0/tests/test_parity.py +105 -0
  64. cnovatech_datamarket-0.1.0/tests/test_pricing.py +34 -0
  65. cnovatech_datamarket-0.1.0/tests/test_products.py +310 -0
  66. cnovatech_datamarket-0.1.0/tests/test_secrets.py +45 -0
  67. cnovatech_datamarket-0.1.0/tests/test_stores.py +338 -0
  68. cnovatech_datamarket-0.1.0/tests/test_validation.py +35 -0
@@ -0,0 +1,26 @@
1
+ # Environments and secrets
2
+ .venv/
3
+ .venv-*/
4
+ .env
5
+ .env.*
6
+ !.env.example
7
+
8
+ # Build output
9
+ dist/
10
+ build/
11
+ *.egg-info/
12
+
13
+ # Tool caches
14
+ .pytest_cache/
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+ .coverage
18
+ coverage.xml
19
+ htmlcov/
20
+ __pycache__/
21
+ *.pyc
22
+
23
+ # Editors and OS
24
+ .idea/
25
+ .vscode/
26
+ .DS_Store
@@ -0,0 +1,49 @@
1
+ # Changelog
2
+
3
+ All notable changes to `cnovatech-datamarket` are documented here. The format
4
+ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
+ project uses [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-11-06
10
+
11
+ First release. Rehearsed on TestPyPI as 0.1.0.dev1.
12
+
13
+ ### Added
14
+
15
+ - `DataMarket` and `AsyncDataMarket` clients with configuration from arguments
16
+ or `DATAMARKET_API_KEY` / `DATAMARKET_BASE_URL`, connection pooling, context
17
+ manager support and a `repr` that masks the key. `max_retries` and
18
+ `max_units_per_request` are enforced by the shared core. A client can be
19
+ shared between threads; responses, pages and exceptions can be pickled.
20
+ - `health()` on both clients: unauthenticated, free of quota cost, and
21
+ tolerant of changes in the unversioned health body.
22
+ - Resources covering every read endpoint of the API: `products` (`catalog`,
23
+ `departments`, `brands`, `by_brand`, `get`, `price_history`, `coverage`),
24
+ `stores` (`list`, `find`, `assortment`, `competitors`, `competitiveness`,
25
+ `compare`, `market_position`, `opportunities`), `brands.stores` and
26
+ `account` (`profile`, `usage`, `logs`, `api_keys`). Each method states its
27
+ quota cost and estimates it before sending. `coverage()` takes any
28
+ iterable of codes; store identifiers may be `uuid.UUID` objects.
29
+ - Paginated responses (`CatalogPage`, `AssortmentPage`, ...) with
30
+ `next_page()`, `iter_pages()` and `iter_all()` capped by `max_pages` or
31
+ `max_units`, plus `estimate_remaining_cost()`.
32
+ - `stores.find(name)`: reads up to five pages of results until an exact name
33
+ matches; `StoreNotFoundError` and `AmbiguousStoreError` with the
34
+ candidates otherwise. A blank name is refused before anything is sent.
35
+ - `to_dataframe()` on every list response, with the `pandas` extra. An
36
+ envelope field that shares its name with a row field keeps its own column,
37
+ prefixed by the response's name (`product_image_url`).
38
+ - `client.usage` (units spent through the client, last balance, refreshed
39
+ by quota errors too) and `client.rate_limit` (per-minute limit from the
40
+ API's headers).
41
+ - `cnovatech.datamarket.errors` with `DataMarketError` as the root of every
42
+ exception the SDK raises, plus `EANNotFoundError`, `StoreNotFoundError`,
43
+ `FilterRequiredError`, `AmbiguousStoreError` and `BudgetExceededError`.
44
+ Connection errors and timeouts carry the `correlation_id` of the request.
45
+ - `cnovatech.datamarket.models`, one module per resource, and
46
+ `cnovatech.datamarket.types` with the input enumerations.
47
+ - `__version__`, read from the installed package metadata.
48
+ - Guides under `docs/`: quota and costs, pagination, errors and recipes.
49
+ - Support for Python 3.10 to 3.15.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cnovatech
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.5
2
+ Name: cnovatech-datamarket
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Cnovatech Data Market API: price, availability and assortment intelligence for Brazilian supermarkets.
5
+ Project-URL: Homepage, https://datamarket.cnovatech.com.br
6
+ Project-URL: Documentation, https://datamarket.cnovatech.com.br/redoc
7
+ Project-URL: Repository, https://github.com/cnovatech-br/sdks
8
+ Project-URL: Changelog, https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/CHANGELOG.md
9
+ Author-email: Cnovatech <suporte@cnovatech.com.br>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: api,assortment,cnovatech,data market,datamarket,ean,pricing,retail,sdk,supermarket
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Natural Language :: English
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
24
+ Classifier: Topic :: Office/Business
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: cnovatech-sdk-core<0.2,>=0.1.0
29
+ Provides-Extra: pandas
30
+ Requires-Dist: cnovatech-sdk-core[pandas]; extra == 'pandas'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # cnovatech-datamarket
34
+
35
+ Official Python SDK for the [Cnovatech Data Market](https://datamarket.cnovatech.com.br)
36
+ API: daily prices, availability and assortment of more than 150,000 products
37
+ across 240+ online supermarkets in 23 Brazilian states.
38
+
39
+ - Synchronous and asynchronous clients with the same surface
40
+ - Every response carries its quota cost; pagination can be capped by cost
41
+ - Retries only where a repeat cannot be billed twice
42
+ - Typed end to end: pydantic models, `py.typed`, `mypy --strict`
43
+
44
+ > **Status: 0.x.** Every read endpoint of the API is implemented, validated
45
+ > against production and tested offline against recorded responses. Until 1.0
46
+ > a minor release may change the public surface; every such change is listed
47
+ > in the
48
+ > [changelog](https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/CHANGELOG.md).
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ pip install cnovatech-datamarket
54
+ ```
55
+
56
+ Add `[pandas]` for `to_dataframe()` on list responses. Python 3.10 or newer.
57
+
58
+ ## Quick start
59
+
60
+ ```python
61
+ from cnovatech.datamarket import DataMarket
62
+
63
+ client = DataMarket() # reads DATAMARKET_API_KEY
64
+
65
+ product = client.products.get("7894900011517", state="SP")
66
+
67
+ product.product_name # "Refrigerante Coca-Cola 2L"
68
+ product.market.median_price # 9.49
69
+ product.offers[0].store_name # "Supermercado Modelo"
70
+ product.offers[0].price # 8.99
71
+ product.offers[0].available # True
72
+
73
+ product.usage.query_cost # 1
74
+ product.usage.remaining # 994
75
+ ```
76
+
77
+ Get a key at <https://datamarket.cnovatech.com.br>. `client.health()` checks
78
+ the API without a key and without cost.
79
+
80
+ The first lookup of a product the API has not cached yet can take up to 90
81
+ seconds; after that it answers in under a second. The default timeout allows
82
+ for it.
83
+
84
+ ## Quota
85
+
86
+ Every call is billed in quota units, and every response says what it cost:
87
+
88
+ ```python
89
+ page = client.products.catalog(search="azeite", limit=50)
90
+ page.usage.query_cost # 1
91
+ page.usage.remaining # 993
92
+ client.usage.spent # 2, through this client
93
+ ```
94
+
95
+ Paginated responses can be walked page by page or item by item, with a ceiling
96
+ on how much to spend:
97
+
98
+ ```python
99
+ position = client.stores.market_position(store_id, limit=1000) # 8 units per page
100
+ position.meta.pages # 4
101
+ position.estimate_remaining_cost() # 24, for the three pages not fetched yet
102
+
103
+ items = list(position.iter_all(max_units=40))
104
+ items[0].product_name # "Azeite Extra Virgem 500ml"
105
+ items[0].price_position # "above"
106
+ ```
107
+
108
+ `iter_all` never requests again the page you already paid for. It stops quietly
109
+ at `max_pages` and raises `BudgetExceededError` before a page that would cross
110
+ `max_units`. To refuse any single call above a budget, build the client with
111
+ `DataMarket(max_units_per_request=20)`.
112
+
113
+ ## Stores and competitors
114
+
115
+ ```python
116
+ store = client.stores.find("Supermercado Modelo", state="SP") # 1 unit
117
+ store.store_id # "0f8c7d2e-6b1a-4f3e-9c5d-2a7b8e1f4c63"
118
+
119
+ competitors = client.stores.competitors(store.store_id, limit=5) # 1 unit each
120
+ competitors.data[0].store_name # "Mercado Vizinho"
121
+ competitors.data[0].overlap_pct # 62.5, share of the assortment in common
122
+
123
+ comparison = client.stores.compare(store.store_id, competitors.data[0].store_id)
124
+ comparison.data[0].product_name # "Azeite Extra Virgem 500ml"
125
+ comparison.data[0].price_position # "above"
126
+ comparison.data[0].price_diff_pct # 12.3
127
+ ```
128
+
129
+ ## Errors
130
+
131
+ ```python
132
+ from cnovatech.datamarket import DataMarket, errors
133
+
134
+ client = DataMarket()
135
+
136
+ try:
137
+ product = client.products.get("0000000000000")
138
+ except errors.EANNotFoundError as exc:
139
+ exc.hint # what the API suggests doing next
140
+ except errors.QuotaExceededError as exc:
141
+ exc.code # "monthly_limit_exceeded"
142
+ exc.upgrade_url # where to change plan
143
+ except errors.APITimeoutError:
144
+ ... # may have been billed; check client.account.usage() before repeating
145
+ except errors.DataMarketError as exc:
146
+ str(exc) # "HTTP 503 service_unavailable: ... [correlation_id=...]"; never the key
147
+ ```
148
+
149
+ Connection errors, 502/503 and the per-minute 429 are retried with backoff.
150
+ Read timeouts, 504 and quota errors never are: the request may already have
151
+ been processed and billed.
152
+
153
+ ## Async
154
+
155
+ ```python
156
+ import asyncio
157
+
158
+ from cnovatech.datamarket import AsyncDataMarket
159
+
160
+
161
+ async def main() -> None:
162
+ async with AsyncDataMarket() as client:
163
+ product = await client.products.get("7894900011517")
164
+ product.market.median_price # 9.49
165
+
166
+
167
+ asyncio.run(main())
168
+ ```
169
+
170
+ ## pandas
171
+
172
+ ```python
173
+ assortment = client.stores.assortment(store.store_id, limit=1000)
174
+ frame = assortment.to_dataframe() # one row per item; usage and meta as columns
175
+ ```
176
+
177
+ ## Configuration
178
+
179
+ | Argument | Environment variable | Default |
180
+ |---|---|---|
181
+ | `api_key` | `DATAMARKET_API_KEY` | required, except for `health()` |
182
+ | `base_url` | `DATAMARKET_BASE_URL` | `https://datamarket.cnovatech.com.br` |
183
+ | `timeout` | | 150 s read timeout; pass `httpx.Timeout` to set every phase. Applies per request, also through your own `http_client` |
184
+ | `max_retries` | | 2 |
185
+ | `max_units_per_request` | | no limit |
186
+ | `http_client` | | your own `httpx.Client`, for proxies, certificates or a shared pool; never closed, redirects never followed |
187
+
188
+ ## Guides
189
+
190
+ - [Quota and costs](https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/docs/quota-and-costs.md): the tariff, what each response reports, estimates and caps.
191
+ - [Pagination](https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/docs/pagination.md): pages, walks, `max_pages` and `max_units`.
192
+ - [Errors](https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/docs/errors.md): the hierarchy, what is retried, how to handle each case.
193
+ - [Recipes](https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/docs/recipes.md): coverage of a list, competitors, exports to pandas, when to use `get` and when to export.
194
+
195
+ ## Logging
196
+
197
+ The SDK logs through the standard `logging` module under the `cnovatech`
198
+ namespace and installs no handler of its own. At DEBUG it records each request's
199
+ method, path, status and duration; retries are logged at INFO, and a WARNING is
200
+ emitted when the remaining quota drops below 10%. Keys, headers and bodies are
201
+ never logged. The `httpx` logger, if the application enables it at INFO, prints
202
+ the full URL with its query string; the key is sent as a header and is not in it.
203
+
204
+ ```python
205
+ import logging
206
+
207
+ logging.basicConfig(level=logging.INFO)
208
+ logging.getLogger("cnovatech").setLevel(logging.DEBUG)
209
+ ```
210
+
211
+ ## Resources
212
+
213
+ | Resource | Methods | Cost |
214
+ |---|---|---|
215
+ | `client.products` | `catalog`, `departments`, `brands`, `by_brand`, `get`, `price_history` | 1 unit |
216
+ | | `coverage` | ceil(codes / 250) units; up to 500 codes per call |
217
+ | `client.stores` | `list`, `find` | 1 unit |
218
+ | | `competitors`, `competitiveness` | 1 unit per competitor |
219
+ | | `assortment`, `compare`, `market_position`, `opportunities` | ceil(rows / 250) × 2 units per page |
220
+ | `client.brands` | `stores` | 1 unit |
221
+ | `client.account` | `profile`, `usage`, `logs`, `api_keys` | free |
222
+ | `client` | `health`, `usage`, `rate_limit`, `close` | free |
223
+
224
+ Each method's docstring states its cost. The API reference is at
225
+ <https://datamarket.cnovatech.com.br/redoc>.
226
+
227
+ ## Support
228
+
229
+ - Issues and feature requests: <https://github.com/cnovatech-br/sdks/issues>
230
+ - Email: <suporte@cnovatech.com.br>
231
+
232
+ ## License
233
+
234
+ [MIT](https://github.com/cnovatech-br/sdks/blob/main/LICENSE). The API terms
235
+ of use are at <https://cnovatech.com.br/termos-de-uso>.
@@ -0,0 +1,203 @@
1
+ # cnovatech-datamarket
2
+
3
+ Official Python SDK for the [Cnovatech Data Market](https://datamarket.cnovatech.com.br)
4
+ API: daily prices, availability and assortment of more than 150,000 products
5
+ across 240+ online supermarkets in 23 Brazilian states.
6
+
7
+ - Synchronous and asynchronous clients with the same surface
8
+ - Every response carries its quota cost; pagination can be capped by cost
9
+ - Retries only where a repeat cannot be billed twice
10
+ - Typed end to end: pydantic models, `py.typed`, `mypy --strict`
11
+
12
+ > **Status: 0.x.** Every read endpoint of the API is implemented, validated
13
+ > against production and tested offline against recorded responses. Until 1.0
14
+ > a minor release may change the public surface; every such change is listed
15
+ > in the
16
+ > [changelog](https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/CHANGELOG.md).
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install cnovatech-datamarket
22
+ ```
23
+
24
+ Add `[pandas]` for `to_dataframe()` on list responses. Python 3.10 or newer.
25
+
26
+ ## Quick start
27
+
28
+ ```python
29
+ from cnovatech.datamarket import DataMarket
30
+
31
+ client = DataMarket() # reads DATAMARKET_API_KEY
32
+
33
+ product = client.products.get("7894900011517", state="SP")
34
+
35
+ product.product_name # "Refrigerante Coca-Cola 2L"
36
+ product.market.median_price # 9.49
37
+ product.offers[0].store_name # "Supermercado Modelo"
38
+ product.offers[0].price # 8.99
39
+ product.offers[0].available # True
40
+
41
+ product.usage.query_cost # 1
42
+ product.usage.remaining # 994
43
+ ```
44
+
45
+ Get a key at <https://datamarket.cnovatech.com.br>. `client.health()` checks
46
+ the API without a key and without cost.
47
+
48
+ The first lookup of a product the API has not cached yet can take up to 90
49
+ seconds; after that it answers in under a second. The default timeout allows
50
+ for it.
51
+
52
+ ## Quota
53
+
54
+ Every call is billed in quota units, and every response says what it cost:
55
+
56
+ ```python
57
+ page = client.products.catalog(search="azeite", limit=50)
58
+ page.usage.query_cost # 1
59
+ page.usage.remaining # 993
60
+ client.usage.spent # 2, through this client
61
+ ```
62
+
63
+ Paginated responses can be walked page by page or item by item, with a ceiling
64
+ on how much to spend:
65
+
66
+ ```python
67
+ position = client.stores.market_position(store_id, limit=1000) # 8 units per page
68
+ position.meta.pages # 4
69
+ position.estimate_remaining_cost() # 24, for the three pages not fetched yet
70
+
71
+ items = list(position.iter_all(max_units=40))
72
+ items[0].product_name # "Azeite Extra Virgem 500ml"
73
+ items[0].price_position # "above"
74
+ ```
75
+
76
+ `iter_all` never requests again the page you already paid for. It stops quietly
77
+ at `max_pages` and raises `BudgetExceededError` before a page that would cross
78
+ `max_units`. To refuse any single call above a budget, build the client with
79
+ `DataMarket(max_units_per_request=20)`.
80
+
81
+ ## Stores and competitors
82
+
83
+ ```python
84
+ store = client.stores.find("Supermercado Modelo", state="SP") # 1 unit
85
+ store.store_id # "0f8c7d2e-6b1a-4f3e-9c5d-2a7b8e1f4c63"
86
+
87
+ competitors = client.stores.competitors(store.store_id, limit=5) # 1 unit each
88
+ competitors.data[0].store_name # "Mercado Vizinho"
89
+ competitors.data[0].overlap_pct # 62.5, share of the assortment in common
90
+
91
+ comparison = client.stores.compare(store.store_id, competitors.data[0].store_id)
92
+ comparison.data[0].product_name # "Azeite Extra Virgem 500ml"
93
+ comparison.data[0].price_position # "above"
94
+ comparison.data[0].price_diff_pct # 12.3
95
+ ```
96
+
97
+ ## Errors
98
+
99
+ ```python
100
+ from cnovatech.datamarket import DataMarket, errors
101
+
102
+ client = DataMarket()
103
+
104
+ try:
105
+ product = client.products.get("0000000000000")
106
+ except errors.EANNotFoundError as exc:
107
+ exc.hint # what the API suggests doing next
108
+ except errors.QuotaExceededError as exc:
109
+ exc.code # "monthly_limit_exceeded"
110
+ exc.upgrade_url # where to change plan
111
+ except errors.APITimeoutError:
112
+ ... # may have been billed; check client.account.usage() before repeating
113
+ except errors.DataMarketError as exc:
114
+ str(exc) # "HTTP 503 service_unavailable: ... [correlation_id=...]"; never the key
115
+ ```
116
+
117
+ Connection errors, 502/503 and the per-minute 429 are retried with backoff.
118
+ Read timeouts, 504 and quota errors never are: the request may already have
119
+ been processed and billed.
120
+
121
+ ## Async
122
+
123
+ ```python
124
+ import asyncio
125
+
126
+ from cnovatech.datamarket import AsyncDataMarket
127
+
128
+
129
+ async def main() -> None:
130
+ async with AsyncDataMarket() as client:
131
+ product = await client.products.get("7894900011517")
132
+ product.market.median_price # 9.49
133
+
134
+
135
+ asyncio.run(main())
136
+ ```
137
+
138
+ ## pandas
139
+
140
+ ```python
141
+ assortment = client.stores.assortment(store.store_id, limit=1000)
142
+ frame = assortment.to_dataframe() # one row per item; usage and meta as columns
143
+ ```
144
+
145
+ ## Configuration
146
+
147
+ | Argument | Environment variable | Default |
148
+ |---|---|---|
149
+ | `api_key` | `DATAMARKET_API_KEY` | required, except for `health()` |
150
+ | `base_url` | `DATAMARKET_BASE_URL` | `https://datamarket.cnovatech.com.br` |
151
+ | `timeout` | | 150 s read timeout; pass `httpx.Timeout` to set every phase. Applies per request, also through your own `http_client` |
152
+ | `max_retries` | | 2 |
153
+ | `max_units_per_request` | | no limit |
154
+ | `http_client` | | your own `httpx.Client`, for proxies, certificates or a shared pool; never closed, redirects never followed |
155
+
156
+ ## Guides
157
+
158
+ - [Quota and costs](https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/docs/quota-and-costs.md): the tariff, what each response reports, estimates and caps.
159
+ - [Pagination](https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/docs/pagination.md): pages, walks, `max_pages` and `max_units`.
160
+ - [Errors](https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/docs/errors.md): the hierarchy, what is retried, how to handle each case.
161
+ - [Recipes](https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/docs/recipes.md): coverage of a list, competitors, exports to pandas, when to use `get` and when to export.
162
+
163
+ ## Logging
164
+
165
+ The SDK logs through the standard `logging` module under the `cnovatech`
166
+ namespace and installs no handler of its own. At DEBUG it records each request's
167
+ method, path, status and duration; retries are logged at INFO, and a WARNING is
168
+ emitted when the remaining quota drops below 10%. Keys, headers and bodies are
169
+ never logged. The `httpx` logger, if the application enables it at INFO, prints
170
+ the full URL with its query string; the key is sent as a header and is not in it.
171
+
172
+ ```python
173
+ import logging
174
+
175
+ logging.basicConfig(level=logging.INFO)
176
+ logging.getLogger("cnovatech").setLevel(logging.DEBUG)
177
+ ```
178
+
179
+ ## Resources
180
+
181
+ | Resource | Methods | Cost |
182
+ |---|---|---|
183
+ | `client.products` | `catalog`, `departments`, `brands`, `by_brand`, `get`, `price_history` | 1 unit |
184
+ | | `coverage` | ceil(codes / 250) units; up to 500 codes per call |
185
+ | `client.stores` | `list`, `find` | 1 unit |
186
+ | | `competitors`, `competitiveness` | 1 unit per competitor |
187
+ | | `assortment`, `compare`, `market_position`, `opportunities` | ceil(rows / 250) × 2 units per page |
188
+ | `client.brands` | `stores` | 1 unit |
189
+ | `client.account` | `profile`, `usage`, `logs`, `api_keys` | free |
190
+ | `client` | `health`, `usage`, `rate_limit`, `close` | free |
191
+
192
+ Each method's docstring states its cost. The API reference is at
193
+ <https://datamarket.cnovatech.com.br/redoc>.
194
+
195
+ ## Support
196
+
197
+ - Issues and feature requests: <https://github.com/cnovatech-br/sdks/issues>
198
+ - Email: <suporte@cnovatech.com.br>
199
+
200
+ ## License
201
+
202
+ [MIT](https://github.com/cnovatech-br/sdks/blob/main/LICENSE). The API terms
203
+ of use are at <https://cnovatech.com.br/termos-de-uso>.
@@ -0,0 +1,98 @@
1
+ [project]
2
+ name = "cnovatech-datamarket"
3
+ version = "0.1.0"
4
+ description = "Official Python SDK for the Cnovatech Data Market API: price, availability and assortment intelligence for Brazilian supermarkets."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [{ name = "Cnovatech", email = "suporte@cnovatech.com.br" }]
10
+ keywords = [
11
+ "cnovatech", "datamarket", "data market", "pricing", "retail",
12
+ "supermarket", "assortment", "ean", "sdk", "api",
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Natural Language :: English",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Programming Language :: Python :: 3.15",
26
+ "Topic :: Office/Business",
27
+ "Topic :: Software Development :: Libraries :: Python Modules",
28
+ "Typing :: Typed",
29
+ ]
30
+ # The core and this package are released together; the pin follows the core's
31
+ # minor series and moves with every release.
32
+ dependencies = [
33
+ "cnovatech-sdk-core>=0.1.0,<0.2",
34
+ ]
35
+
36
+ [project.optional-dependencies]
37
+ pandas = ["cnovatech-sdk-core[pandas]"]
38
+
39
+ [project.urls]
40
+ Homepage = "https://datamarket.cnovatech.com.br"
41
+ Documentation = "https://datamarket.cnovatech.com.br/redoc"
42
+ Repository = "https://github.com/cnovatech-br/sdks"
43
+ Changelog = "https://github.com/cnovatech-br/sdks/blob/main/datamarket-python/CHANGELOG.md"
44
+
45
+ [tool.uv.sources]
46
+ cnovatech-sdk-core = { workspace = true }
47
+
48
+ [build-system]
49
+ requires = ["hatchling>=1.27,<2"]
50
+ build-backend = "hatchling.build"
51
+
52
+ # `src/cnovatech/` has no __init__.py on purpose: it is a PEP 420 namespace
53
+ # shared with `cnovatech-sdk-core` and the other product SDKs.
54
+ [tool.hatch.build.targets.wheel]
55
+ packages = ["src/cnovatech"]
56
+
57
+ [tool.hatch.build.targets.sdist]
58
+ include = ["src", "tests", "README.md", "CHANGELOG.md", "LICENSE", "pyproject.toml"]
59
+
60
+ [tool.mypy]
61
+ python_version = "3.10"
62
+ files = ["src", "tests"]
63
+ mypy_path = "src:../core/src:tests"
64
+ strict = true
65
+ warn_unreachable = true
66
+ namespace_packages = true
67
+ explicit_package_bases = true
68
+ plugins = ["pydantic.mypy"]
69
+
70
+ [tool.pydantic-mypy]
71
+ init_forbid_extra = true
72
+ init_typed = true
73
+ warn_required_dynamic_aliases = true
74
+
75
+ [tool.pytest.ini_options]
76
+ testpaths = ["tests"]
77
+ asyncio_mode = "auto"
78
+ asyncio_default_fixture_loop_scope = "function"
79
+ addopts = "-q --strict-markers --strict-config"
80
+ markers = [
81
+ "live: calls the real API; needs --live and only touches free endpoints.",
82
+ ]
83
+ filterwarnings = ["error"]
84
+
85
+ [tool.coverage.run]
86
+ source = ["src/cnovatech/datamarket"]
87
+ branch = true
88
+
89
+ [tool.coverage.report]
90
+ fail_under = 90
91
+ show_missing = true
92
+ exclude_also = [
93
+ "if TYPE_CHECKING:",
94
+ "if sys.version_info",
95
+ "@overload",
96
+ "raise NotImplementedError",
97
+ "\\.\\.\\.$",
98
+ ]
@@ -0,0 +1,20 @@
1
+ """Official Python SDK for the Cnovatech Data Market API.
2
+
3
+ Typical use::
4
+
5
+ from cnovatech.datamarket import DataMarket
6
+
7
+ with DataMarket() as client: # reads DATAMARKET_API_KEY
8
+ product = client.products.get("7894900011517", state="SP")
9
+ print(product.market.median_price, product.usage.remaining)
10
+
11
+ ``AsyncDataMarket`` offers the same surface with coroutines.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from . import errors, models, types
17
+ from ._client import AsyncDataMarket, DataMarket
18
+ from ._version import __version__
19
+
20
+ __all__ = ["AsyncDataMarket", "DataMarket", "__version__", "errors", "models", "types"]