python-picnic-api2 1.2.4__tar.gz → 1.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.
Files changed (28) hide show
  1. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/.github/dependabot.yml +8 -0
  2. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/.github/workflows/ci.yaml +2 -2
  3. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/.github/workflows/it.yaml +2 -2
  4. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/.github/workflows/release.yml +2 -2
  5. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/PKG-INFO +1 -1
  6. python_picnic_api2-1.3.1/README.md +102 -0
  7. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/integration_tests/test_client.py +11 -0
  8. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/pyproject.toml +1 -1
  9. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/src/python_picnic_api2/client.py +25 -9
  10. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/tests/test_client.py +17 -1
  11. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/uv.lock +1 -1
  12. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/.devcontainer/devcontainer.json +0 -0
  13. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/.env.example +0 -0
  14. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  15. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  16. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/.github/release.yml +0 -0
  17. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/.gitignore +0 -0
  18. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/LICENSE.md +0 -0
  19. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/README.rst +0 -0
  20. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/codecov.yml +0 -0
  21. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/integration_tests/__init__.py +0 -0
  22. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/integration_tests/test_helper.py +0 -0
  23. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/integration_tests/test_session.py +0 -0
  24. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/src/python_picnic_api2/__init__.py +0 -0
  25. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/src/python_picnic_api2/helper.py +0 -0
  26. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/src/python_picnic_api2/session.py +0 -0
  27. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/tests/__init__.py +0 -0
  28. {python_picnic_api2-1.2.4 → python_picnic_api2-1.3.1}/tests/test_session.py +0 -0
@@ -10,3 +10,11 @@ updates:
10
10
  directory: "/"
11
11
  schedule:
12
12
  interval: weekly
13
+ - package-ecosystem: "github-actions"
14
+ directory: "/"
15
+ schedule:
16
+ interval: "weekly"
17
+ - package-ecosystem: "pip"
18
+ directory: "/"
19
+ schedule:
20
+ interval: "weekly"
@@ -17,10 +17,10 @@ jobs:
17
17
  runs-on: ubuntu-latest
18
18
 
19
19
  steps:
20
- - uses: actions/checkout@v2
20
+ - uses: actions/checkout@v4
21
21
 
22
22
  - name: Set up Python ${{ matrix.python-version }}
23
- uses: actions/setup-python@v2
23
+ uses: actions/setup-python@v5
24
24
  with:
25
25
  python-version: ${{ matrix.python-version }}
26
26
 
@@ -11,10 +11,10 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
 
13
13
  steps:
14
- - uses: actions/checkout@v2
14
+ - uses: actions/checkout@v4
15
15
 
16
16
  - name: Set up Python 3.12
17
- uses: actions/setup-python@v2
17
+ uses: actions/setup-python@v5
18
18
  with:
19
19
  python-version: 3.12
20
20
 
@@ -4,10 +4,10 @@ jobs:
4
4
  build:
5
5
  runs-on: ubuntu-latest
6
6
  steps:
7
- - uses: actions/checkout@v2
7
+ - uses: actions/checkout@v4
8
8
 
9
9
  - name: Set up Python 3.11
10
- uses: actions/setup-python@v2
10
+ uses: actions/setup-python@v5
11
11
  with:
12
12
  python-version: 3.11
13
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-picnic-api2
3
- Version: 1.2.4
3
+ Version: 1.3.1
4
4
  Project-URL: homepage, https://github.com/codesalatdev/python-picnic-api
5
5
  Project-URL: repository, https://github.com/codesalatdev/python-picnic-api
6
6
  Author-email: Mike Brink <mjh.brink@icloud.com>, CodeSalat <pypi@codesalat.dev>
@@ -0,0 +1,102 @@
1
+ # Python-Picnic-API
2
+
3
+ **This library is undergoing rapid changes as is the Picnic API itself. It is mainly intended for use within Home Assistant, but there are integration tests running regularly checking for failures in features not used bu the Home Assistant integration**
4
+
5
+ Fork of the Unofficial Python wrapper for the [Picnic](https://picnic.app) API. While not all API methods have been implemented yet, you'll find most of what you need to build a working application are available.
6
+
7
+ This library is not affiliated with Picnic and retrieves data from the endpoints of the mobile application. **Use at your own risk.**
8
+
9
+ ## Credits
10
+
11
+ A big thanks to @MikeBrink for building the first versions of this library.
12
+
13
+ @maartenpaul and @thijmen-j continously provided fixes that were then merged into this fork.
14
+
15
+ ## Getting started
16
+
17
+ The easiest way to install is directly from pip:
18
+
19
+ ```bash
20
+ $ pip install python-picnic-api2
21
+ ```
22
+
23
+ Then create a new instance of `PicnicAPI` and login using your credentials:
24
+
25
+ ```python
26
+ from python_picnic_api2 import PicnicAPI
27
+
28
+ picnic = PicnicAPI(username='username', password='password', country_code="NL")
29
+ ```
30
+
31
+ The country_code parameter defaults to `NL`, but you have to change it if you live in a different country than the Netherlands (ISO 3166-1 Alpha-2). This obviously only works for countries that picnic services.
32
+
33
+ ## Searching for an article
34
+
35
+ ```python
36
+ picnic.search('coffee')
37
+ ```
38
+
39
+ ```python
40
+ [{'items': [{'id': 's1019822', 'name': 'Lavazza Caffè Crema e Aroma Bohnen', 'decorators': [], 'display_price': 1799, 'image_id': 'aecbf7d3b018025ec78daf5a1099b6842a860a2e3faeceec777c13d708ce442c', 'max_count': 99, 'unit_quantity': '1kg', 'sole_article_id': None}, ... ]}]
41
+ ```
42
+
43
+ ## Get article by ID
44
+
45
+ ```python
46
+ picnic.get_article("s1019822")
47
+ ```
48
+ ```python
49
+ {'name': 'Lavazza Caffè Crema e Aroma Bohnen', 'id': 's1019822'}
50
+ ```
51
+
52
+ ## Get article by GTIN (EAN)
53
+ ```python
54
+ picnic.get_article_by_gtin("8000070025400")
55
+ ```
56
+ ```python
57
+ {'name': 'Lavazza Caffè Crema e Aroma Bohnen', 'id': 's1019822'}
58
+ ```
59
+
60
+ ## Check cart
61
+
62
+ ```python
63
+ picnic.get_cart()
64
+ ```
65
+
66
+ ```python
67
+ {'type': 'ORDER', 'id': 'shopping_cart', 'items': [{'type': 'ORDER_LINE', 'id': '1470', 'items': [{'type': 'ORDER_ARTICLE', 'id': 's1019822', 'name': 'Lavazza Caffè Crema e Aroma Bohnen',...
68
+ ```
69
+
70
+ ## Manipulating your cart
71
+ All of these methods will return the shopping cart.
72
+
73
+ ```python
74
+ # Add product with ID "s1019822" 2x
75
+ picnic.add_product("s1019822", 2)
76
+
77
+ # Remove product with ID "s1019822" 1x
78
+ picnic.remove_product("s1019822")
79
+
80
+ # Clear your cart
81
+ picnic.clear_cart()
82
+ ```
83
+
84
+ ## See upcoming deliveries
85
+
86
+ ```python
87
+ picnic.get_current_deliveries()
88
+ ```
89
+
90
+ ```python
91
+ [{'delivery_id': 'XXYYZZ', 'creation_time': '2025-04-28T08:08:41.666+02:00', 'slot': {'slot_id': 'XXYYZZ', 'hub_id': '...
92
+ ```
93
+
94
+ ## See available delivery slots
95
+
96
+ ```python
97
+ picnic.get_delivery_slots()
98
+ ```
99
+
100
+ ```python
101
+ {'delivery_slots': [{'slot_id': 'XXYYZZ', 'hub_id': 'YYY', 'fc_id': 'FCX', 'window_start': '2025-04-29T17:15:00.000+02:00', 'window_end': '2025-04-29T19:15:00.000+02:00'...
102
+ ```
@@ -56,6 +56,17 @@ def test_get_article_with_category_name():
56
56
  picnic.get_article("s1018620", add_category_name=True)
57
57
 
58
58
 
59
+ def test_get_article_by_gtin():
60
+ response = picnic.get_article_by_gtin("4311501044209")
61
+ assert response["id"] == "s1018620"
62
+ assert response["name"] == "Gut&Günstig H-Milch 3,5%"
63
+
64
+
65
+ def test_get_article_by_gtin_unknown():
66
+ response = picnic.get_article_by_gtin("4311501040000")
67
+ assert response is None
68
+
69
+
59
70
  def test_get_cart():
60
71
  response = picnic.get_cart()
61
72
  assert isinstance(response, dict)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-picnic-api2"
3
- version = "1.2.4"
3
+ version = "1.3.1"
4
4
  description = ""
5
5
  readme = "README.rst"
6
6
  license = {text = "Apache-2.0"}
@@ -15,6 +15,10 @@ DEFAULT_URL = "https://storefront-prod.{}.picnicinternational.com/api/{}"
15
15
  GLOBAL_GATEWAY_URL = "https://gateway-prod.global.picnicinternational.com"
16
16
  DEFAULT_COUNTRY_CODE = "NL"
17
17
  DEFAULT_API_VERSION = "15"
18
+ _HEADERS = {
19
+ "x-picnic-agent": "30100;1.15.272-15295;",
20
+ "x-picnic-did": "3C417201548B2E3B",
21
+ }
18
22
 
19
23
 
20
24
  class PicnicAPI:
@@ -47,14 +51,7 @@ class PicnicAPI:
47
51
  url = self._base_url + path
48
52
 
49
53
  # Make the request, add special picnic headers if needed
50
- headers = (
51
- {
52
- "x-picnic-agent": "30100;1.15.272-15295;",
53
- "x-picnic-did": "3C417201548B2E3B",
54
- }
55
- if add_picnic_headers
56
- else None
57
- )
54
+ headers = _HEADERS if add_picnic_headers else None
58
55
  response = self.session.get(url, headers=headers).json()
59
56
 
60
57
  if self._contains_auth_error(response):
@@ -117,7 +114,7 @@ class PicnicAPI:
117
114
  return None
118
115
 
119
116
  color_regex = re.compile(r"#\(#\d{6}\)")
120
- producer = re.sub(color_regex, "", str(article_details[1]["markdown"]))
117
+ producer = re.sub(color_regex, "", str(article_details[1].get("markdown", "")))
121
118
  article_name = re.sub(color_regex, "", str(article_details[0]["markdown"]))
122
119
 
123
120
  article = {"name": f"{producer} {article_name}", "id": article_id}
@@ -177,5 +174,24 @@ class PicnicAPI:
177
174
  tree = "\n".join(_tree_generator(self.get_categories(depth=depth)))
178
175
  print(tree)
179
176
 
177
+ def get_article_by_gtin(self, etan: str, maxRedirects: int = 5):
178
+ # Finds the article ID for a gtin/ean (barcode).
179
+
180
+ url = "https://picnic.app/" + self._country_code.lower() + "/qr/gtin/" + etan
181
+ while maxRedirects > 0:
182
+ if url == "http://picnic.app/nl/link/store/storefront":
183
+ # gtin unknown
184
+ return None
185
+ r = self.session.get(url, headers=_HEADERS, allow_redirects=False)
186
+ maxRedirects -= 1
187
+ if ";id=" in r.url:
188
+ # found the article id
189
+ return self.get_article(r.url.split(";id=", 1)[1])
190
+ if "Location" not in r.headers:
191
+ # article id not found but also no futher redirect
192
+ return None
193
+ url = r.headers["Location"]
194
+ return None
195
+
180
196
 
181
197
  __all__ = ["PicnicAPI"]
@@ -23,7 +23,8 @@ class TestClient(unittest.TestCase):
23
23
  return self.json_data
24
24
 
25
25
  def setUp(self) -> None:
26
- self.session_patcher = patch("python_picnic_api2.client.PicnicAPISession")
26
+ self.session_patcher = patch(
27
+ "python_picnic_api2.client.PicnicAPISession")
27
28
  self.session_mock = self.session_patcher.start()
28
29
  self.client = PicnicAPI(username="test@test.nl", password="test")
29
30
  self.expected_base_url = DEFAULT_URL.format("nl", "15")
@@ -106,6 +107,21 @@ class TestClient(unittest.TestCase):
106
107
  headers=PICNIC_HEADERS,
107
108
  )
108
109
 
110
+ def test_get_article(self):
111
+ self.client.get_article("p3f2qa")
112
+ self.session_mock().get.assert_called_with(
113
+ "https://storefront-prod.nl.picnicinternational.com/api/15/pages/product-details-page-root?id=p3f2qa",
114
+ headers=PICNIC_HEADERS,
115
+ )
116
+
117
+ def test_get_article_by_gtin(self):
118
+ self.client.get_article_by_gtin("123456789")
119
+ self.session_mock().get.assert_called_with(
120
+ "https://picnic.app/nl/qr/gtin/123456789",
121
+ headers=PICNIC_HEADERS,
122
+ allow_redirects=False,
123
+ )
124
+
109
125
  def test_get_cart(self):
110
126
  self.client.get_cart()
111
127
  self.session_mock().get.assert_called_with(
@@ -197,7 +197,7 @@ wheels = [
197
197
 
198
198
  [[package]]
199
199
  name = "python-picnic-api2"
200
- version = "1.2.4"
200
+ version = "1.3.1"
201
201
  source = { editable = "." }
202
202
  dependencies = [
203
203
  { name = "requests" },