python-picnic-api2 1.3.1__tar.gz → 1.3.2__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.
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/.github/ISSUE_TEMPLATE/config.yml +2 -2
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/.github/workflows/ci.yaml +2 -2
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/.github/workflows/it.yaml +2 -2
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/.github/workflows/release.yml +4 -4
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/PKG-INFO +1 -1
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/README.md +3 -1
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/integration_tests/test_client.py +6 -3
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/pyproject.toml +1 -1
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/src/python_picnic_api2/client.py +44 -12
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/src/python_picnic_api2/helper.py +52 -25
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/tests/test_client.py +122 -4
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/uv.lock +1 -1
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/.devcontainer/devcontainer.json +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/.env.example +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/.github/dependabot.yml +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/.github/release.yml +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/.gitignore +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/LICENSE.md +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/README.rst +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/codecov.yml +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/integration_tests/__init__.py +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/integration_tests/test_helper.py +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/integration_tests/test_session.py +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/src/python_picnic_api2/__init__.py +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/src/python_picnic_api2/session.py +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/tests/__init__.py +0 -0
- {python_picnic_api2-1.3.1 → python_picnic_api2-1.3.2}/tests/test_session.py +0 -0
|
@@ -7,8 +7,8 @@ contact_links:
|
|
|
7
7
|
url: https://www.home-assistant.io/help
|
|
8
8
|
about: We use GitHub for tracking bugs, check the Home Assistant website for resources on getting help.
|
|
9
9
|
- name: Feature Request
|
|
10
|
-
url: https://
|
|
11
|
-
about: Please use the Home Assistant
|
|
10
|
+
url: https://github.com/orgs/home-assistant/discussions/new?category=integration-enhancements&integration_name=picnic
|
|
11
|
+
about: Please use the Home Assistant Discussions for making feature requests.
|
|
12
12
|
- name: I'm unsure where to go
|
|
13
13
|
url: https://www.home-assistant.io/join-chat
|
|
14
14
|
about: If you are unsure where to go, then joining our chat is recommended; Just ask!
|
|
@@ -17,10 +17,10 @@ jobs:
|
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
|
|
19
19
|
steps:
|
|
20
|
-
- uses: actions/checkout@
|
|
20
|
+
- uses: actions/checkout@v5
|
|
21
21
|
|
|
22
22
|
- name: Set up Python ${{ matrix.python-version }}
|
|
23
|
-
uses: actions/setup-python@
|
|
23
|
+
uses: actions/setup-python@v6
|
|
24
24
|
with:
|
|
25
25
|
python-version: ${{ matrix.python-version }}
|
|
26
26
|
|
|
@@ -4,10 +4,10 @@ jobs:
|
|
|
4
4
|
build:
|
|
5
5
|
runs-on: ubuntu-latest
|
|
6
6
|
steps:
|
|
7
|
-
- uses: actions/checkout@
|
|
7
|
+
- uses: actions/checkout@v5
|
|
8
8
|
|
|
9
9
|
- name: Set up Python 3.11
|
|
10
|
-
uses: actions/setup-python@
|
|
10
|
+
uses: actions/setup-python@v6
|
|
11
11
|
with:
|
|
12
12
|
python-version: 3.11
|
|
13
13
|
|
|
@@ -40,10 +40,10 @@ jobs:
|
|
|
40
40
|
|
|
41
41
|
steps:
|
|
42
42
|
- name: Retrieve release distributions
|
|
43
|
-
uses: actions/download-artifact@
|
|
43
|
+
uses: actions/download-artifact@v5
|
|
44
44
|
with:
|
|
45
45
|
name: release-dists
|
|
46
46
|
path: dist/
|
|
47
47
|
|
|
48
48
|
- name: Publish release distributions to PyPI
|
|
49
|
-
uses: pypa/gh-action-pypi-publish@v1.
|
|
49
|
+
uses: pypa/gh-action-pypi-publish@v1.13.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-picnic-api2
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
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>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Python-Picnic-API
|
|
2
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
|
|
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 by the Home Assistant integration.**
|
|
4
|
+
|
|
5
|
+
**If you want to know why interacting with Picnic is getting harder than ever, check out their blogpost about architectural changes: https://blog.picnic.nl/adding-write-functionality-to-pages-with-self-service-apis-d09aa7dbc9c0**
|
|
4
6
|
|
|
5
7
|
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
8
|
|
|
@@ -52,8 +52,10 @@ def test_get_article():
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
def test_get_article_with_category_name():
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
response = picnic.get_article("s1018620", add_category=True)
|
|
56
|
+
assert isinstance(response, dict)
|
|
57
|
+
assert "category" in response
|
|
58
|
+
assert response["category"]["name"] == "H-Milch"
|
|
57
59
|
|
|
58
60
|
|
|
59
61
|
def test_get_article_by_gtin():
|
|
@@ -81,7 +83,8 @@ def test_add_product():
|
|
|
81
83
|
|
|
82
84
|
assert isinstance(response, dict)
|
|
83
85
|
assert "items" in response
|
|
84
|
-
assert any(
|
|
86
|
+
assert any(
|
|
87
|
+
item["id"] == "s1018620" for item in response["items"][0]["items"])
|
|
85
88
|
assert _get_amount(response, "s1018620") == 2
|
|
86
89
|
|
|
87
90
|
|
|
@@ -8,6 +8,7 @@ from .helper import (
|
|
|
8
8
|
_extract_search_results,
|
|
9
9
|
_tree_generator,
|
|
10
10
|
_url_generator,
|
|
11
|
+
find_nodes_by_content,
|
|
11
12
|
)
|
|
12
13
|
from .session import PicnicAPISession, PicnicAuthError
|
|
13
14
|
|
|
@@ -16,8 +17,8 @@ GLOBAL_GATEWAY_URL = "https://gateway-prod.global.picnicinternational.com"
|
|
|
16
17
|
DEFAULT_COUNTRY_CODE = "NL"
|
|
17
18
|
DEFAULT_API_VERSION = "15"
|
|
18
19
|
_HEADERS = {
|
|
19
|
-
"x-picnic-agent": "30100;1.
|
|
20
|
-
"x-picnic-did": "
|
|
20
|
+
"x-picnic-agent": "30100;1.206.1-#15408",
|
|
21
|
+
"x-picnic-did": "598F770380CA54B6",
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
|
|
@@ -100,24 +101,44 @@ class PicnicAPI:
|
|
|
100
101
|
def get_cart(self):
|
|
101
102
|
return self._get("/cart")
|
|
102
103
|
|
|
103
|
-
def get_article(self, article_id: str,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
path = f"/pages/product-details-page-root?id={article_id}"
|
|
104
|
+
def get_article(self, article_id: str, add_category=False):
|
|
105
|
+
path = f"/pages/product-details-page-root?id={article_id}" + \
|
|
106
|
+
"&show_category_action=true"
|
|
107
107
|
data = self._get(path, add_picnic_headers=True)
|
|
108
108
|
article_details = []
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
|
|
110
|
+
root_container = find_nodes_by_content(
|
|
111
|
+
data, {"id": "product-details-page-root-main-container"}, max_nodes=1)
|
|
112
|
+
if len(root_container) == 0:
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
article_details = root_container[0]["pml"]["component"]["children"]
|
|
112
116
|
|
|
113
117
|
if len(article_details) == 0:
|
|
114
118
|
return None
|
|
115
119
|
|
|
120
|
+
article = {}
|
|
121
|
+
if add_category:
|
|
122
|
+
cat_node = find_nodes_by_content(
|
|
123
|
+
data, {"id": "category-button"}, max_nodes=1)
|
|
124
|
+
if len(cat_node) == 0:
|
|
125
|
+
raise KeyError(
|
|
126
|
+
f"Could not extract category from article with id {article_id}")
|
|
127
|
+
category_regex = re.compile(
|
|
128
|
+
"app\\.picnic:\\/\\/categories\\/(\\d+)\\/l2\\/(\\d+)\\/l3\\/(\\d+)")
|
|
129
|
+
cat_ids = category_regex.match(
|
|
130
|
+
cat_node[0]["pml"]["component"]["onPress"]["target"]).groups()
|
|
131
|
+
article["category"] = self.get_category_by_ids(
|
|
132
|
+
int(cat_ids[1]), int(cat_ids[2]))
|
|
133
|
+
|
|
116
134
|
color_regex = re.compile(r"#\(#\d{6}\)")
|
|
117
|
-
producer = re.sub(color_regex, "", str(
|
|
118
|
-
|
|
135
|
+
producer = re.sub(color_regex, "", str(
|
|
136
|
+
article_details[1].get("markdown", "")))
|
|
137
|
+
article_name = re.sub(color_regex, "", str(
|
|
138
|
+
article_details[0]["markdown"]))
|
|
119
139
|
|
|
120
|
-
article
|
|
140
|
+
article["name"] = f"{producer} {article_name}"
|
|
141
|
+
article["id"] = article_id
|
|
121
142
|
|
|
122
143
|
return article
|
|
123
144
|
|
|
@@ -170,6 +191,17 @@ class PicnicAPI:
|
|
|
170
191
|
def get_categories(self, depth: int = 0):
|
|
171
192
|
return self._get(f"/my_store?depth={depth}")["catalog"]
|
|
172
193
|
|
|
194
|
+
def get_category_by_ids(self, l2_id: int, l3_id: int):
|
|
195
|
+
path = "/pages/L2-category-page-root" + \
|
|
196
|
+
f"?category_id={l2_id}&l3_category_id={l3_id}"
|
|
197
|
+
data = self._get(path, add_picnic_headers=True)
|
|
198
|
+
nodes = find_nodes_by_content(
|
|
199
|
+
data, {"id": f"vertical-article-tiles-sub-header-{l3_id}"}, max_nodes=1)
|
|
200
|
+
if len(nodes) == 0:
|
|
201
|
+
raise KeyError("Could not find category with specified IDs")
|
|
202
|
+
return {"l2_id": l2_id, "l3_id": l3_id,
|
|
203
|
+
"name": nodes[0]["pml"]["component"]["accessibilityLabel"]}
|
|
204
|
+
|
|
173
205
|
def print_categories(self, depth: int = 0):
|
|
174
206
|
tree = "\n".join(_tree_generator(self.get_categories(depth=depth)))
|
|
175
207
|
print(tree)
|
|
@@ -85,39 +85,66 @@ def get_image(id: str, size="regular", suffix="webp"):
|
|
|
85
85
|
return f"{IMAGE_BASE_URL}/{id}/{size}.{suffix}"
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
def find_nodes_by_content(node, filter, max_nodes: int = 10):
|
|
89
|
+
nodes = []
|
|
90
|
+
|
|
91
|
+
if len(nodes) >= 10:
|
|
92
|
+
return nodes
|
|
93
|
+
|
|
94
|
+
def is_dict_included(node_dict, filter_dict):
|
|
95
|
+
for k, v in filter_dict.items():
|
|
96
|
+
if k not in node_dict:
|
|
97
|
+
return False
|
|
98
|
+
if isinstance(v, dict) and isinstance(node_dict[k], dict):
|
|
99
|
+
if not is_dict_included(node_dict[k], v):
|
|
100
|
+
return False
|
|
101
|
+
elif node_dict[k] != v and v is not None:
|
|
102
|
+
return False
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
if is_dict_included(node, filter):
|
|
106
|
+
nodes.append(node)
|
|
107
|
+
|
|
108
|
+
if isinstance(node, dict):
|
|
109
|
+
for _, v in node.items():
|
|
110
|
+
if isinstance(v, dict):
|
|
111
|
+
nodes.extend(find_nodes_by_content(v, filter, max_nodes))
|
|
112
|
+
continue
|
|
113
|
+
if isinstance(v, list):
|
|
114
|
+
for item in v:
|
|
115
|
+
if isinstance(v, dict | list):
|
|
116
|
+
nodes.extend(find_nodes_by_content(
|
|
117
|
+
item, filter, max_nodes))
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
return nodes
|
|
121
|
+
|
|
122
|
+
|
|
88
123
|
def _extract_search_results(raw_results, max_items: int = 10):
|
|
89
124
|
"""Extract search results from the nested dictionary structure returned by
|
|
90
125
|
Picnic search. Number of max items can be defined to reduce excessive nested
|
|
91
126
|
search"""
|
|
92
|
-
search_results = []
|
|
93
127
|
|
|
94
128
|
LOGGER.debug(f"Extracting search results from {raw_results}")
|
|
95
129
|
|
|
96
|
-
def find_articles(node):
|
|
97
|
-
if len(search_results) >= max_items:
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
-
content = node.get("content", {})
|
|
101
|
-
if content.get("type") == "SELLING_UNIT_TILE" and "sellingUnit" in content:
|
|
102
|
-
selling_unit = content["sellingUnit"]
|
|
103
|
-
sole_article_ids = SOLE_ARTICLE_ID_PATTERN.findall(json.dumps(node))
|
|
104
|
-
sole_article_id = sole_article_ids[0] if sole_article_ids else None
|
|
105
|
-
result_entry = {
|
|
106
|
-
**selling_unit,
|
|
107
|
-
"sole_article_id": sole_article_id,
|
|
108
|
-
}
|
|
109
|
-
LOGGER.debug(f"Found article {result_entry}")
|
|
110
|
-
search_results.append(result_entry)
|
|
111
|
-
|
|
112
|
-
for child in node.get("children", []):
|
|
113
|
-
find_articles(child)
|
|
114
|
-
|
|
115
|
-
if "child" in node:
|
|
116
|
-
find_articles(node.get("child"))
|
|
117
|
-
|
|
118
130
|
body = raw_results.get("body", {})
|
|
119
|
-
|
|
131
|
+
nodes = find_nodes_by_content(body.get("child", {}), {
|
|
132
|
+
"type": "SELLING_UNIT_TILE", "sellingUnit": {}})
|
|
120
133
|
|
|
121
|
-
|
|
134
|
+
search_results = []
|
|
135
|
+
for node in nodes:
|
|
136
|
+
selling_unit = node["sellingUnit"]
|
|
137
|
+
sole_article_ids = SOLE_ARTICLE_ID_PATTERN.findall(
|
|
138
|
+
json.dumps(node))
|
|
139
|
+
sole_article_id = sole_article_ids[0] if sole_article_ids else None
|
|
140
|
+
result_entry = {
|
|
141
|
+
**selling_unit,
|
|
142
|
+
"sole_article_id": sole_article_id,
|
|
143
|
+
}
|
|
144
|
+
LOGGER.debug(f"Found article {result_entry}")
|
|
145
|
+
search_results.append(result_entry)
|
|
146
|
+
|
|
147
|
+
LOGGER.debug(
|
|
148
|
+
f"Found {len(search_results)}/{max_items} products after extraction")
|
|
122
149
|
|
|
123
150
|
return [{"items": search_results}]
|
|
@@ -8,8 +8,8 @@ from python_picnic_api2.client import DEFAULT_URL
|
|
|
8
8
|
from python_picnic_api2.session import PicnicAuthError
|
|
9
9
|
|
|
10
10
|
PICNIC_HEADERS = {
|
|
11
|
-
"x-picnic-agent": "30100;1.
|
|
12
|
-
"x-picnic-did": "
|
|
11
|
+
"x-picnic-agent": "30100;1.206.1-#15408",
|
|
12
|
+
"x-picnic-did": "598F770380CA54B6",
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
|
|
@@ -108,12 +108,106 @@ class TestClient(unittest.TestCase):
|
|
|
108
108
|
)
|
|
109
109
|
|
|
110
110
|
def test_get_article(self):
|
|
111
|
-
self.
|
|
111
|
+
self.session_mock().get.return_value = self.MockResponse(
|
|
112
|
+
{"body": {"child": {"child": {"children": [{
|
|
113
|
+
"id": "product-details-page-root-main-container",
|
|
114
|
+
"pml": {
|
|
115
|
+
"component": {
|
|
116
|
+
"children": [
|
|
117
|
+
{
|
|
118
|
+
"markdown": "#(#333333)Goede start halvarine#(#333333)",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"markdown": "Blue Band",
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}]}}}},
|
|
128
|
+
200
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
article = self.client.get_article("p3f2qa")
|
|
132
|
+
self.session_mock().get.assert_called_with(
|
|
133
|
+
"https://storefront-prod.nl.picnicinternational.com/api/15/pages/product-details-page-root?id=p3f2qa&show_category_action=true",
|
|
134
|
+
headers=PICNIC_HEADERS,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
self.assertEqual(
|
|
138
|
+
article, {'name': 'Blue Band Goede start halvarine', 'id': 'p3f2qa'})
|
|
139
|
+
|
|
140
|
+
def test_get_article_with_category(self):
|
|
141
|
+
self.session_mock().get.return_value = self.MockResponse(
|
|
142
|
+
{"body": {"child": {"child": {"children": [{
|
|
143
|
+
"id": "product-details-page-root-main-container",
|
|
144
|
+
"pml": {
|
|
145
|
+
"component": {
|
|
146
|
+
"children": [
|
|
147
|
+
{
|
|
148
|
+
"markdown": "#(#333333)Goede start halvarine#(#333333)",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"markdown": "Blue Band",
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"id": "category-button",
|
|
160
|
+
"pml": {"component": {"onPress": {"target": "app.picnic://categories/1000/l2/2000/l3/3000"}}}
|
|
161
|
+
}]}}}},
|
|
162
|
+
200
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
category_patch = patch(
|
|
166
|
+
"python_picnic_api2.client.PicnicAPI.get_category_by_ids")
|
|
167
|
+
category_patch.start().return_value = {
|
|
168
|
+
"l2_id": 2000, "l3_id": 3000, "name": "Test"}
|
|
169
|
+
|
|
170
|
+
article = self.client.get_article("p3f2qa", True)
|
|
171
|
+
|
|
172
|
+
category_patch.stop()
|
|
112
173
|
self.session_mock().get.assert_called_with(
|
|
113
|
-
"https://storefront-prod.nl.picnicinternational.com/api/15/pages/product-details-page-root?id=p3f2qa",
|
|
174
|
+
"https://storefront-prod.nl.picnicinternational.com/api/15/pages/product-details-page-root?id=p3f2qa&show_category_action=true",
|
|
114
175
|
headers=PICNIC_HEADERS,
|
|
115
176
|
)
|
|
116
177
|
|
|
178
|
+
self.assertEqual(
|
|
179
|
+
article, {'name': 'Blue Band Goede start halvarine', 'id': 'p3f2qa',
|
|
180
|
+
"category": {"l2_id": 2000, "l3_id": 3000, "name": "Test"}})
|
|
181
|
+
|
|
182
|
+
def test_get_article_with_unsupported_structure(self):
|
|
183
|
+
self.session_mock().get.return_value = self.MockResponse(
|
|
184
|
+
{"body": {"child": {"child": {"children": [{
|
|
185
|
+
"id": "unsupported-root-container",
|
|
186
|
+
"pml": {
|
|
187
|
+
"component": {
|
|
188
|
+
"children": [
|
|
189
|
+
{
|
|
190
|
+
"markdown": "#(#333333)Goede start halvarine#(#333333)",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"markdown": "Blue Band",
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}]}}}},
|
|
200
|
+
200
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
article = self.client.get_article("p3f2qa")
|
|
204
|
+
self.session_mock().get.assert_called_with(
|
|
205
|
+
"https://storefront-prod.nl.picnicinternational.com/api/15/pages/product-details-page-root?id=p3f2qa&show_category_action=true",
|
|
206
|
+
headers=PICNIC_HEADERS,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
assert article is None
|
|
210
|
+
|
|
117
211
|
def test_get_article_by_gtin(self):
|
|
118
212
|
self.client.get_article_by_gtin("123456789")
|
|
119
213
|
self.session_mock().get.assert_called_with(
|
|
@@ -220,6 +314,30 @@ class TestClient(unittest.TestCase):
|
|
|
220
314
|
{"type": "CATEGORY", "id": "purchases", "name": "Besteld"},
|
|
221
315
|
)
|
|
222
316
|
|
|
317
|
+
def test_get_category_by_ids(self):
|
|
318
|
+
self.session_mock().get.return_value = self.MockResponse(
|
|
319
|
+
{"children": [
|
|
320
|
+
{
|
|
321
|
+
"id": "vertical-article-tiles-sub-header-22193",
|
|
322
|
+
"pml": {
|
|
323
|
+
"component": {
|
|
324
|
+
"accessibilityLabel": "Halvarine"
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
]},
|
|
329
|
+
200
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
category = self.client.get_category_by_ids(1000, 22193)
|
|
333
|
+
self.session_mock().get.assert_called_with(
|
|
334
|
+
f"{self.expected_base_url}/pages/L2-category-page-root" +
|
|
335
|
+
"?category_id=1000&l3_category_id=22193", headers=PICNIC_HEADERS
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
self.assertDictEqual(
|
|
339
|
+
category, {"name": "Halvarine", "l2_id": 1000, "l3_id": 22193})
|
|
340
|
+
|
|
223
341
|
def test_get_auth_exception(self):
|
|
224
342
|
self.session_mock().get.return_value = self.MockResponse(
|
|
225
343
|
{"error": {"code": "AUTH_ERROR"}}, 400
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|