faker-grocery 0.3.1__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,170 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [0.3.1] - 2026-09-20
11
+
12
+ ### Fixed
13
+ - `python -m faker_grocery` demo: the "Aisles" / "Aisles in department X" sections could print the same aisle twice (e.g. small departments like `pets`, with only 2 aisles, or `snacks et confiseries` with 11, have a real chance of repeats across 5 independent draws - a birthday-paradox effect, not a bug in the random generation itself). The demo now samples distinct aisles/departments (`distinct_sample()` helper using `random_elements(..., unique=True)`, capped to the pool size so it can't crash on a 1-aisle department). `fake.aisle()`/`fake.department()` themselves are unchanged: independent draws that can repeat are the correct, intended behavior for actual data generation.
14
+
15
+ ---
16
+
17
+ ## [0.3.0] - 2026-09-20
18
+
19
+ ### Added
20
+ - Closed test coverage gaps found in an audit of `GroceryStoreProvider`'s public API: `aisle_name()`, `aisle_id()` without filter, integer (not just numeric-string) `department=`/`aisle=` filters, `product(aisle=...)`, `grocery_item(aisle=..., department=...)`, `quantity(max_quantity=...)`, `shopping_datetime(date=<datetime.datetime>)`, unknown-aisle error handling, and cross-language name resolution (e.g. filtering with an English department name while the provider language is `fr`).
21
+ - Added `departments`/`aisles` property shape checks (counts, expected keys) as regression tests for the catalog data itself.
22
+ - **Internal consistency / data integrity tests**, not just plausibility checks in isolation: every product's `base_price` falls within its department's declared price range (full 49,688-row scan), every product's `department_id` agrees with its aisle's declared department, `product_record()`'s embedded aisle/department names agree with an independent catalog lookup, and a department-restricted `grocery_list()`'s items are fully coherent (department, unit and price all agree with the requested department, `quantity * unit_price == total_price`).
23
+ - **`unit(department=...)`**: units are now tied to what's plausible for a department (e.g. `L`/bottle/can for beverages, `kg`/bunch for produce, tube for personal care) via a new `department_units` catalog table, instead of being picked from one global list independently of the product. Fixes incoherent combinations such as "9 bottle x Bread" or "10 jar x Trash Bags" that a purely random unit could previously produce.
24
+ - Added `can` and `tube` as new unit keys.
25
+ - 25 new test cases overall (35 -> 60).
26
+
27
+ ### Changed
28
+ - `grocery_item()` now calls `unit(department=...)` with the product's own department, so its `unit` field is always coherent with the product instead of independently random.
29
+ - Replaced the `units: {language: [unit, ...]}` data table with `unit_labels: {unit_key: {language: label}}` + `department_units: {department_id: [unit_key, ...]}`. **Breaking for direct data access**: code reading `provider._data["units"]` directly must switch to `unit_labels`/`department_units`; the public `unit()` method is backwards compatible (still returns a localized string, now with an optional `department` filter).
30
+ - Bumped version from 0.2.0 to 0.3.0.
31
+
32
+ ### Fixed
33
+ - Documented (via `test_locale_property`) that `fake.locale` on the `Faker` instance is shadowed by Faker's own built-in `locale` attribute; `GroceryStoreProvider.locale` is only reachable on the provider instance directly (`GroceryStoreProvider(fake).locale`), not through the `Faker`/`fake.*` proxy. Not a bug, but a naming collision worth knowing about.
34
+
35
+ ---
36
+
37
+ ## [0.2.0] - 2026-09-20
38
+
39
+ ### Added
40
+ - **Probable product prices**: every product now has a stable `base_price` derived from its department's typical price range (seeded on the product id, so it stays stable across data rebuilds).
41
+ - `price(product=<id>)` returns that product's base price jittered by a random **±5%**.
42
+ - `product_record()` now also returns `base_price` (stable catalog reference) and `price` (this instance, jittered).
43
+ - `grocery_item()` prices its product the same way instead of drawing a fresh uniform-random price from the whole department range.
44
+ - **Family-aware shopping baskets**: `grocery_list()` gained a `family_size` parameter (default `2`, a couple) that scales:
45
+ - the default item-count range, when `min_items`/`max_items` are not given explicitly;
46
+ - the plausible quantity per item (bigger households buy more of a given article), via `grocery_item(family_size=...)` and `quantity(max_quantity=...)`.
47
+ - The returned dict now includes a `family_size` field.
48
+ - **Optional shopping date/time**: new `shopping_datetime(date=None, open_hour=8, close_hour=20)` method returning a plausible trip datetime (random day within the last year, or a given date, combined with a random time within store opening hours).
49
+ - `grocery_list(with_datetime=True)` (or passing `date=...`) adds a `shopping_datetime` field to the result.
50
+ - 12 new test cases covering price jitter, family-size scaling and shopping date/time generation.
51
+
52
+ ### Changed
53
+ - `products` rows in `faker_grocery/data/grocery_data.json` gained a `base_price` field: `[id, aisle_id, department_id, base_price, name_<lang0>, name_<lang1>, ...]`.
54
+ - `scripts/build_data.py` now derives each product's `base_price` from its department's price range (triangular distribution skewed towards the cheaper end).
55
+ - Bumped version from 0.1.0 to 0.2.0 in `setup.py` and `faker_grocery/__version__`.
56
+ - Updated README.md and examples with the new pricing and basket features.
57
+
58
+ ---
59
+
60
+ ## [0.1.0] - 2026-09-20
61
+
62
+ ### Added
63
+ - Initial release of `faker-grocery`.
64
+ - `GroceryStoreProvider` built from the Instacart Market Basket Analysis open dataset: 21 departments, 134 aisles, ~49,700 products.
65
+ - Support for 2 languages: `en` (source data) and `fr` (departments/aisles hand-translated, product names translated via a curated ~700-word word-substitution dictionary).
66
+ - `department()`, `department_id()`, `department_name()`, `aisle()`, `aisle_id()`, `aisle_name()` with optional filtering by department (id or localized name).
67
+ - `product()`, `product_record()`, `product_name()` with optional filtering by aisle and/or department.
68
+ - `price()`, `quantity()`, `unit()` helpers.
69
+ - `grocery_item()` and `grocery_list(min_items, max_items, department=None)` for randomized shopping list generation.
70
+ - `scripts/build_data.py` to (re)build the single-JSON catalog from `DATAS/*.csv` and the translation files in `scripts/translations/`, designed for adding further languages (`it`, `es`, `pt`, `de` planned).
71
+ - Test suite (`tests/test_grocery_store.py`), demo (`python -m faker_grocery`) and complete usage example (`examples/basic_usage.py`).
72
+
73
+ ---
74
+
75
+ ## Usage Guidelines
76
+
77
+ ### How to Use This Changelog
78
+
79
+ 1. **Before releasing a new version**:
80
+ - Move all changes from `[Unreleased]` section to the new version section
81
+ - Add the release date
82
+ - Update the version in `setup.py` and `faker_grocery/__init__.py` (`__version__`)
83
+ - If the catalog data changed (`DATAS/*.csv` or `scripts/translations/*.json`), re-run `python scripts/build_data.py` before releasing
84
+
85
+ 2. **When making changes**:
86
+ - Add entries to the `[Unreleased]` section under the appropriate category
87
+ - Use past tense for entries (e.g., "Added" not "Add")
88
+ - Group similar changes together
89
+
90
+ 3. **Categories**:
91
+ - `Added`: New features
92
+ - `Changed`: Changes to existing functionality
93
+ - `Fixed`: Bug fixes
94
+ - `Removed`: Features that were removed
95
+ - `Deprecated`: Features that will be removed in future versions
96
+ - `Security`: Security-related changes
97
+
98
+ ### Versioning Rules
99
+
100
+ Follow [Semantic Versioning](https://semver.org/):
101
+
102
+ - **MAJOR version** (X.0.0): When you make incompatible API changes
103
+ - **MINOR version** (0.X.0): When you add functionality in a backwards-compatible manner
104
+ - **PATCH version** (0.0.X): When you make backwards-compatible bug fixes
105
+
106
+ ### Example Workflow
107
+
108
+ ```bash
109
+ # 1. Make your changes
110
+ # 2. Update CHANGELOG.md in [Unreleased] section
111
+ # 3. Commit changes
112
+ git add .
113
+ git commit -m "Add new feature X"
114
+
115
+ # 4. When ready to release:
116
+ # - Create new version section in CHANGELOG.md
117
+ # - Update version in setup.py and faker_grocery/__init__.py
118
+ # - Commit
119
+ git add CHANGELOG.md setup.py faker_grocery/__init__.py
120
+ git commit -m "Bump version to 0.3.0"
121
+
122
+ # 5. Tag the release
123
+ git tag -a v0.3.0 -m "Release v0.3.0"
124
+ git push origin v0.3.0
125
+
126
+ # 6. Publish to PyPI
127
+ twine upload dist/*
128
+ ```
129
+
130
+ ### Changelog Entry Examples
131
+
132
+ ```markdown
133
+ ## [0.3.0] - YYYY-MM-DD
134
+
135
+ ### Added
136
+ - Added support for Italian catalog data (`it`)
137
+ - Added `nutriscore` field to product records
138
+
139
+ ### Changed
140
+ - Widened the price range for the "alcohol" department
141
+
142
+ ### Fixed
143
+ - Fixed `aisle(department=...)` raising for department ids passed as `int`
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Template for New Entries
149
+
150
+ ```markdown
151
+ ## [X.Y.Z] - YYYY-MM-DD
152
+
153
+ ### Added
154
+ -
155
+
156
+ ### Changed
157
+ -
158
+
159
+ ### Fixed
160
+ -
161
+
162
+ ### Removed
163
+ -
164
+
165
+ ### Deprecated
166
+ -
167
+
168
+ ### Security
169
+ -
170
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jctranc
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,4 @@
1
+ include README.md
2
+ include CHANGELOG.md
3
+ include LICENSE
4
+ recursive-include faker_grocery/data *.json
@@ -0,0 +1,234 @@
1
+ Metadata-Version: 2.4
2
+ Name: faker-grocery
3
+ Version: 0.3.1
4
+ Summary: A custom Faker provider for simulating supermarket/hypermarket products, aisles and departments
5
+ Author: jctranc
6
+ License: MIT
7
+ Keywords: faker grocery supermarket hypermarket test data
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Environment :: Console
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.7
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: faker>=20.0.0
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest; extra == "test"
24
+ Dynamic: author
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: keywords
29
+ Dynamic: license
30
+ Dynamic: license-file
31
+ Dynamic: provides-extra
32
+ Dynamic: requires-dist
33
+ Dynamic: requires-python
34
+ Dynamic: summary
35
+
36
+ # Faker Grocery Store Provider
37
+
38
+ **A custom [Faker](https://faker.readthedocs.io/) provider for generating realistic fake data for supermarkets and hypermarkets: departments, aisles, products, prices and randomized shopping lists.**
39
+
40
+ The catalog is built from the [Instacart Market Basket Analysis](https://www.kaggle.com/c/instacart-market-basket-analysis) open dataset: **21 departments**, **134 aisles** and **~49,700 real products**.
41
+
42
+ ---
43
+
44
+ ## 🌍 Supported languages
45
+
46
+ | Language | Locale examples | Departments / Aisles | Product names |
47
+ |----------|------------------|-----------------------|----------------|
48
+ | `en` | `en_US` | Original English names | Original English names |
49
+ | `fr` | `fr_FR` | Hand-translated | Approximate word-substitution translation |
50
+
51
+ > **Default language**: `en` (used for any locale not listed above, e.g. as a fallback).
52
+
53
+ Planned extensions (data not built yet): `it`, `es`, `pt`, `de`. See [Adding a new language](#-adding-a-new-language) below.
54
+
55
+ Product names in `fr` are translated word-by-word from the source English names using a curated ~700-word grocery vocabulary dictionary (see `scripts/translations/word_dict_fr.json`). Words missing from the dictionary (mostly brand names) are left untranslated, and grammar/word order is not reordered. This is approximate, "faker-grade" data meant for tests and demos, not a professional localization.
56
+
57
+ ---
58
+
59
+ ## 📦 Installation
60
+
61
+ ```bash
62
+ cd faker_grocery
63
+ pip install -e .
64
+ ```
65
+
66
+ ---
67
+
68
+ ## 🚀 Usage
69
+
70
+ ```python
71
+ from faker import Faker
72
+ from faker_grocery import GroceryStoreProvider
73
+
74
+ fake = Faker("fr_FR") # or "en_US"
75
+ fake.add_provider(GroceryStoreProvider)
76
+ ```
77
+
78
+ ### 🏬 Departments & aisles
79
+
80
+ ```python
81
+ fake.department() # "produits laitiers et œufs"
82
+ fake.department_id() # 16
83
+ fake.department_name(16) # "produits laitiers et œufs"
84
+
85
+ fake.aisle() # "yaourt"
86
+ fake.aisle(department="produits laitiers et œufs") # restricted to that department
87
+ fake.aisle(department=16) # same, by id
88
+ fake.aisle_id(department=16) # 120
89
+ fake.aisle_name(120) # "yaourt"
90
+ ```
91
+
92
+ ### 🛒 Products
93
+
94
+ ```python
95
+ fake.product() # "Yaourt Grec Vanille"
96
+ fake.product(aisle="yaourt") # restricted to an aisle (name or id)
97
+ fake.product(department=16) # restricted to a department (name or id)
98
+
99
+ fake.product_record()
100
+ # {
101
+ # 'id': 12345,
102
+ # 'name': 'Yaourt Grec Vanille',
103
+ # 'aisle_id': 120,
104
+ # 'aisle': 'yaourt',
105
+ # 'department_id': 16,
106
+ # 'department': 'produits laitiers et œufs',
107
+ # }
108
+
109
+ fake.product_name(12345) # look up a specific product id
110
+ ```
111
+
112
+ ### 💰 Prices, quantities, units
113
+
114
+ Every product has a stable catalog **`base_price`** (its "probable price"),
115
+ generated once from its department's typical range. Any time you actually
116
+ price a product — via `price(product=...)`, `product_record()` or
117
+ `grocery_item()` — you get that base price jittered by a random **±5%**, the
118
+ way real shelf prices drift a little from one day/store to the next.
119
+
120
+ ```python
121
+ fake.price() # 4.77 (random department's typical range)
122
+ fake.price(department=16) # price within the dairy/eggs range
123
+ fake.price(min_price=10.0, max_price=20.0)
124
+ fake.price(product=12345) # that product's base_price, jittered +/-5%
125
+
126
+ record = fake.product_record()
127
+ record["base_price"] # 2.10 (stable catalog reference)
128
+ record["price"] # 2.04 (this instance, +/-5% jitter)
129
+
130
+ fake.quantity() # 3 (1 to 10)
131
+ fake.quantity(max_quantity=5) # 1 to 5
132
+
133
+ fake.unit() # "pièce(s)", "kg", "bouteille", ... (any unit)
134
+ fake.unit(department=7) # restricted to units plausible for that department
135
+ # (e.g. "L"/"bouteille"/"canette" for beverages,
136
+ # "kg"/"botte" for produce - not "9 bottle x Bread")
137
+ ```
138
+
139
+ ### 📝 Grocery items & shopping lists
140
+
141
+ ```python
142
+ fake.grocery_item()
143
+ # {
144
+ # 'product': 'Yaourt Grec Vanille',
145
+ # 'product_id': 12345,
146
+ # 'aisle': 'yaourt',
147
+ # 'department': 'produits laitiers et œufs',
148
+ # 'quantity': 3,
149
+ # 'unit': 'pot', # always plausible for the product's department
150
+ # 'unit_price': 2.15, # base_price jittered +/-5%
151
+ # 'total_price': 6.45,
152
+ # }
153
+
154
+ # family_size defaults to 2 (a couple). It scales the default item-count
155
+ # range and the plausible quantity of each item (bigger households buy more).
156
+ shopping_list = fake.grocery_list()
157
+ # {
158
+ # 'items': [ ... grocery_item dicts ... ],
159
+ # 'item_count': 9,
160
+ # 'family_size': 2,
161
+ # 'total': 42.31,
162
+ # }
163
+
164
+ fake.grocery_list(family_size=5) # bigger default basket
165
+ fake.grocery_list(min_items=3, max_items=8) # override the item-count range explicitly
166
+ fake.grocery_list(department="fruits et légumes") # restrict the whole list to one department
167
+
168
+ # Optionally attach a plausible shopping date/time (random within the last
169
+ # year, during store opening hours by default 8:00-20:00):
170
+ shopping_list = fake.grocery_list(with_datetime=True)
171
+ shopping_list["shopping_datetime"] # datetime.datetime(2026, 3, 12, 17, 42, 8)
172
+
173
+ # Or pin it to a specific date (random time on that day):
174
+ import datetime
175
+ fake.grocery_list(date=datetime.date(2025, 12, 24))
176
+
177
+ # Standalone helper used internally by grocery_list():
178
+ fake.shopping_datetime() # random date (last year) + time (8h-20h)
179
+ fake.shopping_datetime(date=datetime.date(2025, 12, 24))
180
+ fake.shopping_datetime(open_hour=9, close_hour=19)
181
+ ```
182
+
183
+ ---
184
+
185
+ ## 🎯 Demo
186
+
187
+ ```bash
188
+ python -m faker_grocery
189
+ ```
190
+
191
+ For a complete demo with more examples per feature and both locales, see [`examples/basic_usage.py`](examples/basic_usage.py).
192
+
193
+ ---
194
+
195
+ ## 🧱 Data layout
196
+
197
+ A single JSON file, `faker_grocery/data/grocery_data.json`, holds the whole catalog:
198
+
199
+ - `departments`: `{department_id: {language: name}}`
200
+ - `aisles`: `{aisle_id: {language: name, department_id: int}}` (each aisle belongs to exactly one department)
201
+ - `products`: compact rows `[id, aisle_id, department_id, base_price, name_<lang0>, name_<lang1>, ...]`
202
+ - `price_ranges`: `{department_id: [min_price, max_price]}`
203
+ - `unit_labels`: `{unit_key: {language: label}}` (e.g. `"bottle": {"en": "bottle", "fr": "bouteille"}`)
204
+ - `department_units`: `{department_id: [unit_key, ...]}` - which units are plausible for each department, so a generated item's unit always matches what the product actually is (no "9 bottle x Bread")
205
+ - `meta`: supported/default/planned languages, source, field layout
206
+
207
+ > `DATAS/*.csv` (the raw Instacart source: `aisles.csv`, `departments.csv`, `products.csv`) is **not** shipped in the installed package - only `grocery_data.json` is (see `MANIFEST.in`/`package_data` in `setup.py`), and using the library never touches `DATAS/`. It **is** kept versioned in the repository, though: `scripts/build_data.py` always rebuilds the catalog from these CSVs (never from `grocery_data.json` itself), so they're the only way to regenerate the data at all - including to add a new language later (see below).
208
+
209
+ ## 🌐 Adding a new language
210
+
211
+ The file is generated by [`scripts/build_data.py`](scripts/build_data.py) from the raw CSVs in `DATAS/` (`aisles.csv`, `departments.csv`, `products.csv`) plus the translation files in `scripts/translations/`. To add a language (e.g. `it`):
212
+
213
+ 1. Create `scripts/translations/departments_it.json` and `aisles_it.json` (21 and 134 hand-translated entries).
214
+ 2. Create `scripts/translations/word_dict_it.json` (a grocery vocabulary dictionary for word-substitution product name translation — see `word_dict_fr.json` for the format and scope).
215
+ 3. Add `"it"` to `TRANSLATED_LANGUAGES` in `scripts/build_data.py`.
216
+ 4. Re-run:
217
+ ```bash
218
+ python scripts/build_data.py
219
+ ```
220
+
221
+ ---
222
+
223
+ ## 🧪 Tests
224
+
225
+ ```bash
226
+ pip install -e ".[test]"
227
+ pytest
228
+ ```
229
+
230
+ ---
231
+
232
+ ## 📜 License
233
+
234
+ MIT License.
@@ -0,0 +1,199 @@
1
+ # Faker Grocery Store Provider
2
+
3
+ **A custom [Faker](https://faker.readthedocs.io/) provider for generating realistic fake data for supermarkets and hypermarkets: departments, aisles, products, prices and randomized shopping lists.**
4
+
5
+ The catalog is built from the [Instacart Market Basket Analysis](https://www.kaggle.com/c/instacart-market-basket-analysis) open dataset: **21 departments**, **134 aisles** and **~49,700 real products**.
6
+
7
+ ---
8
+
9
+ ## 🌍 Supported languages
10
+
11
+ | Language | Locale examples | Departments / Aisles | Product names |
12
+ |----------|------------------|-----------------------|----------------|
13
+ | `en` | `en_US` | Original English names | Original English names |
14
+ | `fr` | `fr_FR` | Hand-translated | Approximate word-substitution translation |
15
+
16
+ > **Default language**: `en` (used for any locale not listed above, e.g. as a fallback).
17
+
18
+ Planned extensions (data not built yet): `it`, `es`, `pt`, `de`. See [Adding a new language](#-adding-a-new-language) below.
19
+
20
+ Product names in `fr` are translated word-by-word from the source English names using a curated ~700-word grocery vocabulary dictionary (see `scripts/translations/word_dict_fr.json`). Words missing from the dictionary (mostly brand names) are left untranslated, and grammar/word order is not reordered. This is approximate, "faker-grade" data meant for tests and demos, not a professional localization.
21
+
22
+ ---
23
+
24
+ ## 📦 Installation
25
+
26
+ ```bash
27
+ cd faker_grocery
28
+ pip install -e .
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 🚀 Usage
34
+
35
+ ```python
36
+ from faker import Faker
37
+ from faker_grocery import GroceryStoreProvider
38
+
39
+ fake = Faker("fr_FR") # or "en_US"
40
+ fake.add_provider(GroceryStoreProvider)
41
+ ```
42
+
43
+ ### 🏬 Departments & aisles
44
+
45
+ ```python
46
+ fake.department() # "produits laitiers et œufs"
47
+ fake.department_id() # 16
48
+ fake.department_name(16) # "produits laitiers et œufs"
49
+
50
+ fake.aisle() # "yaourt"
51
+ fake.aisle(department="produits laitiers et œufs") # restricted to that department
52
+ fake.aisle(department=16) # same, by id
53
+ fake.aisle_id(department=16) # 120
54
+ fake.aisle_name(120) # "yaourt"
55
+ ```
56
+
57
+ ### 🛒 Products
58
+
59
+ ```python
60
+ fake.product() # "Yaourt Grec Vanille"
61
+ fake.product(aisle="yaourt") # restricted to an aisle (name or id)
62
+ fake.product(department=16) # restricted to a department (name or id)
63
+
64
+ fake.product_record()
65
+ # {
66
+ # 'id': 12345,
67
+ # 'name': 'Yaourt Grec Vanille',
68
+ # 'aisle_id': 120,
69
+ # 'aisle': 'yaourt',
70
+ # 'department_id': 16,
71
+ # 'department': 'produits laitiers et œufs',
72
+ # }
73
+
74
+ fake.product_name(12345) # look up a specific product id
75
+ ```
76
+
77
+ ### 💰 Prices, quantities, units
78
+
79
+ Every product has a stable catalog **`base_price`** (its "probable price"),
80
+ generated once from its department's typical range. Any time you actually
81
+ price a product — via `price(product=...)`, `product_record()` or
82
+ `grocery_item()` — you get that base price jittered by a random **±5%**, the
83
+ way real shelf prices drift a little from one day/store to the next.
84
+
85
+ ```python
86
+ fake.price() # 4.77 (random department's typical range)
87
+ fake.price(department=16) # price within the dairy/eggs range
88
+ fake.price(min_price=10.0, max_price=20.0)
89
+ fake.price(product=12345) # that product's base_price, jittered +/-5%
90
+
91
+ record = fake.product_record()
92
+ record["base_price"] # 2.10 (stable catalog reference)
93
+ record["price"] # 2.04 (this instance, +/-5% jitter)
94
+
95
+ fake.quantity() # 3 (1 to 10)
96
+ fake.quantity(max_quantity=5) # 1 to 5
97
+
98
+ fake.unit() # "pièce(s)", "kg", "bouteille", ... (any unit)
99
+ fake.unit(department=7) # restricted to units plausible for that department
100
+ # (e.g. "L"/"bouteille"/"canette" for beverages,
101
+ # "kg"/"botte" for produce - not "9 bottle x Bread")
102
+ ```
103
+
104
+ ### 📝 Grocery items & shopping lists
105
+
106
+ ```python
107
+ fake.grocery_item()
108
+ # {
109
+ # 'product': 'Yaourt Grec Vanille',
110
+ # 'product_id': 12345,
111
+ # 'aisle': 'yaourt',
112
+ # 'department': 'produits laitiers et œufs',
113
+ # 'quantity': 3,
114
+ # 'unit': 'pot', # always plausible for the product's department
115
+ # 'unit_price': 2.15, # base_price jittered +/-5%
116
+ # 'total_price': 6.45,
117
+ # }
118
+
119
+ # family_size defaults to 2 (a couple). It scales the default item-count
120
+ # range and the plausible quantity of each item (bigger households buy more).
121
+ shopping_list = fake.grocery_list()
122
+ # {
123
+ # 'items': [ ... grocery_item dicts ... ],
124
+ # 'item_count': 9,
125
+ # 'family_size': 2,
126
+ # 'total': 42.31,
127
+ # }
128
+
129
+ fake.grocery_list(family_size=5) # bigger default basket
130
+ fake.grocery_list(min_items=3, max_items=8) # override the item-count range explicitly
131
+ fake.grocery_list(department="fruits et légumes") # restrict the whole list to one department
132
+
133
+ # Optionally attach a plausible shopping date/time (random within the last
134
+ # year, during store opening hours by default 8:00-20:00):
135
+ shopping_list = fake.grocery_list(with_datetime=True)
136
+ shopping_list["shopping_datetime"] # datetime.datetime(2026, 3, 12, 17, 42, 8)
137
+
138
+ # Or pin it to a specific date (random time on that day):
139
+ import datetime
140
+ fake.grocery_list(date=datetime.date(2025, 12, 24))
141
+
142
+ # Standalone helper used internally by grocery_list():
143
+ fake.shopping_datetime() # random date (last year) + time (8h-20h)
144
+ fake.shopping_datetime(date=datetime.date(2025, 12, 24))
145
+ fake.shopping_datetime(open_hour=9, close_hour=19)
146
+ ```
147
+
148
+ ---
149
+
150
+ ## 🎯 Demo
151
+
152
+ ```bash
153
+ python -m faker_grocery
154
+ ```
155
+
156
+ For a complete demo with more examples per feature and both locales, see [`examples/basic_usage.py`](examples/basic_usage.py).
157
+
158
+ ---
159
+
160
+ ## 🧱 Data layout
161
+
162
+ A single JSON file, `faker_grocery/data/grocery_data.json`, holds the whole catalog:
163
+
164
+ - `departments`: `{department_id: {language: name}}`
165
+ - `aisles`: `{aisle_id: {language: name, department_id: int}}` (each aisle belongs to exactly one department)
166
+ - `products`: compact rows `[id, aisle_id, department_id, base_price, name_<lang0>, name_<lang1>, ...]`
167
+ - `price_ranges`: `{department_id: [min_price, max_price]}`
168
+ - `unit_labels`: `{unit_key: {language: label}}` (e.g. `"bottle": {"en": "bottle", "fr": "bouteille"}`)
169
+ - `department_units`: `{department_id: [unit_key, ...]}` - which units are plausible for each department, so a generated item's unit always matches what the product actually is (no "9 bottle x Bread")
170
+ - `meta`: supported/default/planned languages, source, field layout
171
+
172
+ > `DATAS/*.csv` (the raw Instacart source: `aisles.csv`, `departments.csv`, `products.csv`) is **not** shipped in the installed package - only `grocery_data.json` is (see `MANIFEST.in`/`package_data` in `setup.py`), and using the library never touches `DATAS/`. It **is** kept versioned in the repository, though: `scripts/build_data.py` always rebuilds the catalog from these CSVs (never from `grocery_data.json` itself), so they're the only way to regenerate the data at all - including to add a new language later (see below).
173
+
174
+ ## 🌐 Adding a new language
175
+
176
+ The file is generated by [`scripts/build_data.py`](scripts/build_data.py) from the raw CSVs in `DATAS/` (`aisles.csv`, `departments.csv`, `products.csv`) plus the translation files in `scripts/translations/`. To add a language (e.g. `it`):
177
+
178
+ 1. Create `scripts/translations/departments_it.json` and `aisles_it.json` (21 and 134 hand-translated entries).
179
+ 2. Create `scripts/translations/word_dict_it.json` (a grocery vocabulary dictionary for word-substitution product name translation — see `word_dict_fr.json` for the format and scope).
180
+ 3. Add `"it"` to `TRANSLATED_LANGUAGES` in `scripts/build_data.py`.
181
+ 4. Re-run:
182
+ ```bash
183
+ python scripts/build_data.py
184
+ ```
185
+
186
+ ---
187
+
188
+ ## 🧪 Tests
189
+
190
+ ```bash
191
+ pip install -e ".[test]"
192
+ pytest
193
+ ```
194
+
195
+ ---
196
+
197
+ ## 📜 License
198
+
199
+ MIT License.