lib-shopware6-api-base 3.0.2__tar.gz → 3.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.
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/CHANGELOG.md +11 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/PKG-INFO +4 -6
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/README.md +1 -2
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/pyproject.toml +3 -4
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/dependencies.py +4 -4
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/refractor.md +1 -1
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/refractor.py +1 -1
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/__init__conf__.py +1 -1
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/_http_common.py +6 -6
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/lib_shopware6_admin_client.py +87 -72
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/lib_shopware6_storefront_client.py +9 -9
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/conftest.py +3 -3
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/.devcontainer/devcontainer.json +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/.devcontainer/settings.json +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/.github/actions/extract-metadata/action.yml +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/.github/dependabot.yml +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/.github/workflows/cicd_docker.yml +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/.github/workflows/codeql-analysis.yml +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/.github/workflows/default_release_public.yml +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/.gitignore +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/CODE_OF_CONDUCT.md +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/CONTRIBUTING.md +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/INSTALL.md +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/ISSUE_TEMPLATE.md +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/LICENSE +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/PULL_REQUEST_TEMPLATE.md +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/example.env +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/mk +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/mk.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/notebooks/Quickstart.ipynb +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/__init__.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/__main__.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/_git.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/_metadata.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/_pip.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/_subprocess.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/_test_audit.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/_test_coverage.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/_test_steps.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/_utils.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/build.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/bump.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/bump_major.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/bump_minor.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/bump_patch.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/bump_version.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/clean.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/cli.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/dev.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/help.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/install.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/menu.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/push.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/release.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/run_cli.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/target_metadata.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/test.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/toml_config.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/scripts/version_current.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/.gitignore +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/__init__.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/__main__.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/_compat.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/conf_shopware6_api_base_classes.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/exit_codes.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/lib_shopware6_api_base.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/lib_shopware6_api_base_cli.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/lib_shopware6_api_base_criteria.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/lib_shopware6_api_base_criteria_aggregation.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/lib_shopware6_api_base_criteria_filter.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/lib_shopware6_api_base_criteria_sorting.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/lib_shopware6_api_base_helpers.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/py.typed +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/conf_test_docker.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/docker.env +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/local_testscripts/lib_bash_functions.sh +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/local_testscripts/run_clean.sh +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/local_testscripts/run_pytest.sh +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/local_testscripts/run_testloop.sh +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/local_testscripts/run_testloop_pytest_and_mypy_only_no_setup.sh +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/local_testscripts/run_testloop_windows.cmd +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/local_testscripts/shellcheck.sh +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/local_testscripts/testing_tools.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/test_api_base.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/test_cli.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/test_conf_classes.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/test_config.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/test_criteria.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/test_criteria_aggregation.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/test_criteria_filter.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/test_criteria_sorting.py +0 -0
- {lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/test_helpers.py +0 -0
|
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.1.0] - 2026-06-01
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **HTTP client migrated from `httpx` to `httpx2`** (the Pydantic-stewarded successor); minimum `httpx2>=2.2.0`. Storefront client and shared HTTP helpers swapped over directly (API-compatible surface). Dev tooling (`scripts/dependencies.py`) migrated too.
|
|
13
|
+
- Admin API OAuth2 (token fetch, refresh-token flow, Bearer auth, expiry handling) reimplemented directly on `httpx2`, replacing Authlib's `OAuth2Client`. The `self.token` shape is unchanged (`access_token`, `expires_in`, `expires_at`, and `refresh_token` for the password grant); public client APIs are unchanged.
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
|
|
17
|
+
- Dropped the `authlib` dependency — OAuth2 token handling is now performed in-library. As a side effect, `insecure_transport` is no longer required to talk to plain-HTTP endpoints (the previous `OAUTHLIB_INSECURE_TRANSPORT` guard is gone); the config field is retained for backward compatibility but is now a no-op.
|
|
18
|
+
|
|
8
19
|
## [3.0.2] - 2026-05-08
|
|
9
20
|
|
|
10
21
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lib_shopware6_api_base
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: python3 base API client for shopware6
|
|
5
5
|
Project-URL: Homepage, https://github.com/bitranox/lib_shopware6_api_base
|
|
6
6
|
Project-URL: Documentation, https://github.com/bitranox/lib_shopware6_api_base/blob/master/README.md
|
|
@@ -22,8 +22,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.14
|
|
23
23
|
Classifier: Typing :: Typed
|
|
24
24
|
Requires-Python: >=3.10
|
|
25
|
-
Requires-Dist:
|
|
26
|
-
Requires-Dist: httpx>=0.28.1
|
|
25
|
+
Requires-Dist: httpx2>=2.2.0
|
|
27
26
|
Requires-Dist: lib-cli-exit-tools>=2.2.4
|
|
28
27
|
Requires-Dist: orjson
|
|
29
28
|
Requires-Dist: pydantic-settings>=2.12.0
|
|
@@ -33,7 +32,7 @@ Provides-Extra: dev
|
|
|
33
32
|
Requires-Dist: bandit>=1.9.3; extra == 'dev'
|
|
34
33
|
Requires-Dist: build>=1.4.0; extra == 'dev'
|
|
35
34
|
Requires-Dist: codecov-cli>=11.2.6; extra == 'dev'
|
|
36
|
-
Requires-Dist:
|
|
35
|
+
Requires-Dist: httpx2>=2.2.0; extra == 'dev'
|
|
37
36
|
Requires-Dist: hypothesis>=6.151.4; extra == 'dev'
|
|
38
37
|
Requires-Dist: import-linter>=2.9; extra == 'dev'
|
|
39
38
|
Requires-Dist: jaraco-context>=6.1.0; extra == 'dev'
|
|
@@ -494,8 +493,7 @@ Automatically installed dependencies:
|
|
|
494
493
|
|
|
495
494
|
- `pydantic>=2.0.0` - Data validation
|
|
496
495
|
- `pydantic-settings>=2.0.0` - Settings management
|
|
497
|
-
- `
|
|
498
|
-
- `authlib>=1.6` - OAuth2 authentication
|
|
496
|
+
- `httpx2>=2.2.0` - HTTP client
|
|
499
497
|
- `rich-click` - CLI formatting
|
|
500
498
|
- `orjson` - Fast JSON serialization
|
|
501
499
|
- `lib_cli_exit_tools>=2.2.4` - CLI utilities
|
|
@@ -440,8 +440,7 @@ Automatically installed dependencies:
|
|
|
440
440
|
|
|
441
441
|
- `pydantic>=2.0.0` - Data validation
|
|
442
442
|
- `pydantic-settings>=2.0.0` - Settings management
|
|
443
|
-
- `
|
|
444
|
-
- `authlib>=1.6` - OAuth2 authentication
|
|
443
|
+
- `httpx2>=2.2.0` - HTTP client
|
|
445
444
|
- `rich-click` - CLI formatting
|
|
446
445
|
- `orjson` - Fast JSON serialization
|
|
447
446
|
- `lib_cli_exit_tools>=2.2.4` - CLI utilities
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lib_shopware6_api_base"
|
|
3
|
-
version = "3.0
|
|
3
|
+
version = "3.1.0"
|
|
4
4
|
description = "python3 base API client for shopware6"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -10,8 +10,7 @@ dependencies = [
|
|
|
10
10
|
"lib_cli_exit_tools>=2.2.4",
|
|
11
11
|
"rich-click",
|
|
12
12
|
"orjson",
|
|
13
|
-
"
|
|
14
|
-
"authlib>=1.6.11",
|
|
13
|
+
"httpx2>=2.2.0",
|
|
15
14
|
]
|
|
16
15
|
license = {text = "MIT"}
|
|
17
16
|
authors = [{ name = "bitranox", email = "bitranox@gmail.com" }]
|
|
@@ -50,7 +49,7 @@ dev = [
|
|
|
50
49
|
"textual>=7.5.0",
|
|
51
50
|
"import-linter>=2.9",
|
|
52
51
|
"rtoml>=0.13.0",
|
|
53
|
-
"
|
|
52
|
+
"httpx2>=2.2.0",
|
|
54
53
|
# Transitive CVE pins
|
|
55
54
|
"jaraco-context>=6.1.0", # CVE-2025-23949
|
|
56
55
|
"python-multipart>=0.0.27", # CVE-2025-24486, CVE-2026-40347, CVE-2026-42561
|
|
@@ -28,7 +28,7 @@ from dataclasses import dataclass
|
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
from typing import Any, cast
|
|
30
30
|
|
|
31
|
-
import
|
|
31
|
+
import httpx2
|
|
32
32
|
import orjson
|
|
33
33
|
|
|
34
34
|
from .toml_config import PyprojectConfig, load_pyproject_config
|
|
@@ -131,14 +131,14 @@ def _fetch_pypi_data(package_name: str) -> dict[str, Any] | None:
|
|
|
131
131
|
url = f"https://pypi.org/pypi/{normalized}/json"
|
|
132
132
|
|
|
133
133
|
try:
|
|
134
|
-
response =
|
|
134
|
+
response = httpx2.get(url, timeout=10.0)
|
|
135
135
|
response.raise_for_status()
|
|
136
136
|
return orjson.loads(response.content) # type: ignore[no-any-return]
|
|
137
|
-
except
|
|
137
|
+
except httpx2.HTTPStatusError as exc:
|
|
138
138
|
if exc.response.status_code == 404:
|
|
139
139
|
return None
|
|
140
140
|
return None
|
|
141
|
-
except (
|
|
141
|
+
except (httpx2.ConnectError, httpx2.TimeoutException, orjson.JSONDecodeError):
|
|
142
142
|
return None
|
|
143
143
|
|
|
144
144
|
|
|
@@ -48,7 +48,7 @@ After the roast loop completes (only MINOR issues remain), the pipeline runs the
|
|
|
48
48
|
| 1 | Data Architecture Enforcement | Run `/bx_data_architecture_enforcement` |
|
|
49
49
|
| 2 | Architecture Review | Review using skill `bx-python-clean-architecture` |
|
|
50
50
|
| 3 | LRU Cache Opportunities | Find caching opportunities, precompile regex |
|
|
51
|
-
| 4 | Proper Usage of Libraries | Ensure orjson, rtoml,
|
|
51
|
+
| 4 | Proper Usage of Libraries | Ensure orjson, rtoml, httpx2 usage |
|
|
52
52
|
| 5 | Edge Cases & Security | Review for edge cases, race conditions, security |
|
|
53
53
|
| 6 | Test Refractor | Run `/bx_refactor_tests_python` |
|
|
54
54
|
| 7 | Code Simplification | Simplify code, remove dead code, modernize |
|
|
@@ -679,7 +679,7 @@ Continue numbering for all issues found. Each issue MUST have a specific, action
|
|
|
679
679
|
(
|
|
680
680
|
"Proper Usage of Libraries",
|
|
681
681
|
"make sure that we use orjson, rtoml instead of stdlib. "
|
|
682
|
-
"make sure that we use
|
|
682
|
+
"make sure that we use httpx2 if applicable. "
|
|
683
683
|
"do not create compatibility shims, we dont care for backward compatibility. "
|
|
684
684
|
"write tests for any new code you create - aim for good test coverage. "
|
|
685
685
|
"dont ask back - decide yourself given all the systemprompts in claude.md. "
|
|
@@ -26,7 +26,7 @@ name = "lib_shopware6_api_base"
|
|
|
26
26
|
#: Human-readable summary shown in CLI help output.
|
|
27
27
|
title = "python3 base API client for shopware6"
|
|
28
28
|
#: Current release version pulled from ``pyproject.toml`` by automation.
|
|
29
|
-
version = "3.0
|
|
29
|
+
version = "3.1.0"
|
|
30
30
|
#: Repository homepage presented to users.
|
|
31
31
|
homepage = "https://github.com/bitranox/lib_shopware6_api_base"
|
|
32
32
|
#: Author attribution surfaced in CLI output.
|
|
@@ -6,7 +6,7 @@ from typing import Any
|
|
|
6
6
|
from urllib.parse import urljoin
|
|
7
7
|
|
|
8
8
|
# EXT
|
|
9
|
-
import
|
|
9
|
+
import httpx2
|
|
10
10
|
|
|
11
11
|
from .conf_shopware6_api_base_classes import ShopwareAPIError
|
|
12
12
|
from .lib_shopware6_api_base_criteria import Criteria
|
|
@@ -46,7 +46,7 @@ __all__ = [
|
|
|
46
46
|
]
|
|
47
47
|
|
|
48
48
|
# Module-level constants for HTTP settings
|
|
49
|
-
DEFAULT_REQUEST_TIMEOUT =
|
|
49
|
+
DEFAULT_REQUEST_TIMEOUT = httpx2.Timeout(30.0, connect=10.0)
|
|
50
50
|
MAX_RETRY_ATTEMPTS = 2
|
|
51
51
|
CONTENT_TYPE_JSON = "json"
|
|
52
52
|
HEADER_SW_ACCESS_KEY = "sw-access-key"
|
|
@@ -94,7 +94,7 @@ def get_payload_dict(payload: PayLoad) -> dict[str, Any]:
|
|
|
94
94
|
return payload # type: ignore
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
def handle_json_decode_error(response:
|
|
97
|
+
def handle_json_decode_error(response: httpx2.Response) -> dict[str, Any]:
|
|
98
98
|
"""Safely decode JSON response, returning empty dict on failure."""
|
|
99
99
|
try:
|
|
100
100
|
return dict(response.json())
|
|
@@ -103,18 +103,18 @@ def handle_json_decode_error(response: httpx.Response) -> dict[str, Any]:
|
|
|
103
103
|
return {}
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
def handle_http_error(exc:
|
|
106
|
+
def handle_http_error(exc: httpx2.HTTPStatusError) -> None:
|
|
107
107
|
"""Convert HTTP status errors to ShopwareAPIError with detailed message."""
|
|
108
108
|
detailed_error = f" : {exc.response.text}"
|
|
109
109
|
raise ShopwareAPIError(f"{exc}{detailed_error}") from exc
|
|
110
110
|
|
|
111
111
|
|
|
112
|
-
def log_request(request:
|
|
112
|
+
def log_request(request: httpx2.Request) -> None:
|
|
113
113
|
"""Log outgoing HTTP request for debugging."""
|
|
114
114
|
logger.debug("Request: %s %s", request.method, request.url)
|
|
115
115
|
|
|
116
116
|
|
|
117
|
-
def log_response(response:
|
|
117
|
+
def log_response(response: httpx2.Response) -> None:
|
|
118
118
|
"""Log incoming HTTP response for debugging."""
|
|
119
119
|
logger.debug("Response: %s %s", response.status_code, response.url)
|
|
120
120
|
|
|
@@ -5,13 +5,12 @@ import time
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
# EXT
|
|
8
|
-
import
|
|
8
|
+
import httpx2
|
|
9
9
|
import orjson
|
|
10
|
-
from authlib.integrations.base_client import TokenExpiredError
|
|
11
|
-
from authlib.integrations.httpx_client import OAuth2Client
|
|
12
10
|
|
|
13
11
|
from ._http_common import (
|
|
14
12
|
CONTENT_TYPE_JSON,
|
|
13
|
+
DEFAULT_REQUEST_TIMEOUT,
|
|
15
14
|
MAX_RETRY_ATTEMPTS,
|
|
16
15
|
PayLoad,
|
|
17
16
|
build_api_url,
|
|
@@ -53,7 +52,7 @@ class Shopware6AdminAPIClientBase:
|
|
|
53
52
|
"""
|
|
54
53
|
self.config = config
|
|
55
54
|
self.token: dict[str, Any] = {}
|
|
56
|
-
self.session:
|
|
55
|
+
self.session: httpx2.Client = httpx2.Client()
|
|
57
56
|
|
|
58
57
|
def request_get(
|
|
59
58
|
self, request_url: str, payload: PayLoad = None, update_header_fields: dict[str, str] | None = None
|
|
@@ -522,34 +521,16 @@ class Shopware6AdminAPIClientBase:
|
|
|
522
521
|
update_header_fields=update_header_fields,
|
|
523
522
|
)
|
|
524
523
|
retry = 0
|
|
525
|
-
except TokenExpiredError:
|
|
526
|
-
if self._is_refreshable_token(): # pragma: no cover
|
|
527
|
-
# this actually should never happen - just in case.
|
|
528
|
-
# Authlib handles token refresh via callback automatically
|
|
529
|
-
logger.warning(
|
|
530
|
-
"something went wrong - the token should have been automatically refreshed. getting a new token"
|
|
531
|
-
) # pragma: no cover
|
|
532
|
-
self._get_access_token_by_user_credentials() # pragma: no cover
|
|
533
|
-
else:
|
|
534
|
-
self._get_access_token_by_resource_owner()
|
|
535
|
-
self._get_session()
|
|
536
|
-
response = self._request(
|
|
537
|
-
http_method=http_method,
|
|
538
|
-
request_url=request_url,
|
|
539
|
-
payload=payload,
|
|
540
|
-
content_type=content_type,
|
|
541
|
-
additional_query_params=additional_query_params,
|
|
542
|
-
update_header_fields=update_header_fields,
|
|
543
|
-
)
|
|
544
|
-
retry = 0
|
|
545
524
|
except ShopwareAPIError as exc:
|
|
546
525
|
# retry: how often to retry - sometimes we get error code:9, status:401,
|
|
547
526
|
# The resource owner or authorization server denied the request,
|
|
548
527
|
# detail: Access token could not be verified.
|
|
549
|
-
# But it works if You try again, it seems to be an error in shopware API or race condition
|
|
528
|
+
# But it works if You try again, it seems to be an error in shopware API or race condition.
|
|
529
|
+
# Drop the token so the next iteration re-authenticates from scratch.
|
|
550
530
|
retry = retry - 1
|
|
551
531
|
if not retry:
|
|
552
532
|
raise exc
|
|
533
|
+
self.token = {}
|
|
553
534
|
|
|
554
535
|
if not retry:
|
|
555
536
|
break
|
|
@@ -570,7 +551,7 @@ class Shopware6AdminAPIClientBase:
|
|
|
570
551
|
content_type: str = CONTENT_TYPE_JSON,
|
|
571
552
|
additional_query_params: dict[str, Any] | None = None,
|
|
572
553
|
update_header_fields: dict[str, str] | None = None,
|
|
573
|
-
) ->
|
|
554
|
+
) -> httpx2.Response:
|
|
574
555
|
"""
|
|
575
556
|
makes a request, needs a "self.session" to be set up and authenticated
|
|
576
557
|
|
|
@@ -585,7 +566,7 @@ class Shopware6AdminAPIClientBase:
|
|
|
585
566
|
returns
|
|
586
567
|
response_dict: dictionary with the response as dict
|
|
587
568
|
|
|
588
|
-
see : https://www.
|
|
569
|
+
see : https://www.encode.io/httpx2/
|
|
589
570
|
|
|
590
571
|
"""
|
|
591
572
|
request_data: str
|
|
@@ -602,8 +583,9 @@ class Shopware6AdminAPIClientBase:
|
|
|
602
583
|
if not additional_query_params:
|
|
603
584
|
additional_query_params = {}
|
|
604
585
|
|
|
605
|
-
response:
|
|
586
|
+
response: httpx2.Response
|
|
606
587
|
headers = self._get_headers(content_type=content_type, update_header_fields=update_header_fields)
|
|
588
|
+
headers["Authorization"] = f"Bearer {self.token['access_token']}"
|
|
607
589
|
|
|
608
590
|
if http_method == HttpMethod.GET:
|
|
609
591
|
if additional_query_params:
|
|
@@ -641,6 +623,7 @@ class Shopware6AdminAPIClientBase:
|
|
|
641
623
|
elif http_method == HttpMethod.DELETE:
|
|
642
624
|
response = self.session.delete(
|
|
643
625
|
self._format_admin_api_url(request_url),
|
|
626
|
+
headers=headers,
|
|
644
627
|
params=additional_query_params,
|
|
645
628
|
follow_redirects=self.config.follow_redirects,
|
|
646
629
|
)
|
|
@@ -652,7 +635,7 @@ class Shopware6AdminAPIClientBase:
|
|
|
652
635
|
|
|
653
636
|
try:
|
|
654
637
|
response.raise_for_status() # type: ignore[possibly-undefined]
|
|
655
|
-
except
|
|
638
|
+
except httpx2.HTTPStatusError as exc:
|
|
656
639
|
detailed_error = f" : {exc.response.text}"
|
|
657
640
|
raise ShopwareAPIError(f"{exc}{detailed_error}") from exc
|
|
658
641
|
|
|
@@ -767,13 +750,11 @@ class Shopware6AdminAPIClientBase:
|
|
|
767
750
|
if not self.config.client_secret:
|
|
768
751
|
raise ShopwareAPIError("client_secret needed")
|
|
769
752
|
|
|
770
|
-
client
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
url=self._format_admin_api_url("oauth/token"),
|
|
776
|
-
grant_type="client_credentials",
|
|
753
|
+
# Shopware authenticates the confidential client via HTTP Basic auth
|
|
754
|
+
# (OAuth2 "client_secret_basic"); the body carries only the grant_type.
|
|
755
|
+
self.token = self._fetch_token(
|
|
756
|
+
{"grant_type": "client_credentials"},
|
|
757
|
+
auth=httpx2.BasicAuth(self.config.client_id, self.config.client_secret),
|
|
777
758
|
)
|
|
778
759
|
return self.token
|
|
779
760
|
|
|
@@ -784,7 +765,6 @@ class Shopware6AdminAPIClientBase:
|
|
|
784
765
|
- we recommend to only use this grant flow for client applications that should
|
|
785
766
|
perform administrative actions and require a user-based authentication
|
|
786
767
|
|
|
787
|
-
see : https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#legacy-application-flow
|
|
788
768
|
see : https://shopware.stoplight.io/docs/admin-api/ZG9jOjEwODA3NjQx-authentication-and-authorisation
|
|
789
769
|
setup at admin/settings/system/user: "access_id" and "access_secret"
|
|
790
770
|
|
|
@@ -840,18 +820,25 @@ class Shopware6AdminAPIClientBase:
|
|
|
840
820
|
if not self.config.password:
|
|
841
821
|
raise ShopwareAPIError("password needed")
|
|
842
822
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
823
|
+
# "administration" is Shopware's built-in public client for the password
|
|
824
|
+
# grant; it issues a refresh_token, so this flow is auto-refreshable.
|
|
825
|
+
self.token = self._fetch_token(
|
|
826
|
+
{
|
|
827
|
+
"grant_type": "password",
|
|
828
|
+
"client_id": "administration",
|
|
829
|
+
"username": self.config.username,
|
|
830
|
+
"password": self.config.password,
|
|
831
|
+
}
|
|
849
832
|
)
|
|
850
833
|
return self.token
|
|
851
834
|
|
|
852
835
|
def _get_session(self) -> None:
|
|
853
836
|
"""
|
|
854
|
-
|
|
837
|
+
ensure a valid (non-expired) access token is available before a request.
|
|
838
|
+
|
|
839
|
+
an expired token is refreshed via its refresh_token when available
|
|
840
|
+
(password grant), otherwise a fresh token is fetched (client credentials).
|
|
841
|
+
|
|
855
842
|
see : https://shopware.stoplight.io/docs/admin-api/ZG9jOjEwODA3NjQx-authentication-and-authorisation
|
|
856
843
|
|
|
857
844
|
|
|
@@ -879,42 +866,70 @@ class Shopware6AdminAPIClientBase:
|
|
|
879
866
|
"""
|
|
880
867
|
if not self.token:
|
|
881
868
|
self._get_token()
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
token=self.token,
|
|
888
|
-
token_endpoint=self._format_admin_api_url("oauth/token"),
|
|
889
|
-
update_token=self._token_saver,
|
|
890
|
-
)
|
|
891
|
-
else:
|
|
892
|
-
client_id = self.config.client_id
|
|
893
|
-
self.session = OAuth2Client(client_id=client_id, token=self.token)
|
|
869
|
+
elif self._token_is_expired():
|
|
870
|
+
if self._is_refreshable_token():
|
|
871
|
+
self._refresh_token()
|
|
872
|
+
else:
|
|
873
|
+
self._get_token()
|
|
894
874
|
|
|
895
|
-
def
|
|
896
|
-
self,
|
|
897
|
-
token: dict[str, Any],
|
|
898
|
-
refresh_token: str | None = None,
|
|
899
|
-
access_token: str | None = None,
|
|
900
|
-
) -> None:
|
|
875
|
+
def _fetch_token(self, data: dict[str, Any], auth: httpx2.Auth | None = None) -> dict[str, Any]:
|
|
901
876
|
"""
|
|
902
|
-
|
|
903
|
-
the "user_credentials" can not be refreshed
|
|
877
|
+
POST to the ``oauth/token`` endpoint and return the token dict.
|
|
904
878
|
|
|
905
|
-
|
|
879
|
+
``expires_at`` (absolute unix time) is derived from ``expires_in`` so that
|
|
880
|
+
token expiry can be checked locally without another round trip.
|
|
906
881
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
access_token: optional access token (Authlib callback parameter)
|
|
882
|
+
parameters:
|
|
883
|
+
data: the OAuth2 request body (grant_type and grant-specific fields)
|
|
884
|
+
auth: optional HTTP Basic client auth (used by the client_credentials grant)
|
|
911
885
|
|
|
912
886
|
returns
|
|
913
|
-
|
|
887
|
+
the token dict, with an added ``expires_at`` key
|
|
888
|
+
|
|
889
|
+
raises
|
|
890
|
+
ShopwareAPIError: if the token endpoint returns an error status
|
|
891
|
+
"""
|
|
892
|
+
response = self.session.post(
|
|
893
|
+
self._format_admin_api_url("oauth/token"),
|
|
894
|
+
json=data,
|
|
895
|
+
auth=auth if auth is not None else httpx2.USE_CLIENT_DEFAULT,
|
|
896
|
+
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
|
897
|
+
timeout=DEFAULT_REQUEST_TIMEOUT,
|
|
898
|
+
follow_redirects=self.config.follow_redirects,
|
|
899
|
+
)
|
|
900
|
+
try:
|
|
901
|
+
response.raise_for_status()
|
|
902
|
+
except httpx2.HTTPStatusError as exc:
|
|
903
|
+
detailed_error = f" : {exc.response.text}"
|
|
904
|
+
raise ShopwareAPIError(f"{exc}{detailed_error}") from exc
|
|
905
|
+
token = dict(response.json())
|
|
906
|
+
if "expires_in" in token:
|
|
907
|
+
token["expires_at"] = time.time() + int(token["expires_in"])
|
|
908
|
+
return token
|
|
914
909
|
|
|
910
|
+
def _refresh_token(self) -> dict[str, Any]:
|
|
911
|
+
"""
|
|
912
|
+
refresh the access token using the stored refresh_token (password grant).
|
|
913
|
+
the "client_credentials" grant has no refresh_token and is re-fetched instead.
|
|
914
|
+
"""
|
|
915
|
+
self.token = self._fetch_token(
|
|
916
|
+
{
|
|
917
|
+
"grant_type": "refresh_token",
|
|
918
|
+
"client_id": "administration",
|
|
919
|
+
"refresh_token": self.token["refresh_token"],
|
|
920
|
+
}
|
|
921
|
+
)
|
|
922
|
+
return self.token
|
|
923
|
+
|
|
924
|
+
def _token_is_expired(self) -> bool:
|
|
925
|
+
"""
|
|
926
|
+
True if the current token has an ``expires_at`` in the past.
|
|
927
|
+
a token without ``expires_at`` is treated as not expired.
|
|
915
928
|
"""
|
|
916
|
-
|
|
917
|
-
|
|
929
|
+
expires_at = self.token.get("expires_at")
|
|
930
|
+
if expires_at is None:
|
|
931
|
+
return False
|
|
932
|
+
return time.time() >= float(expires_at)
|
|
918
933
|
|
|
919
934
|
def _is_refreshable_token(self) -> bool:
|
|
920
935
|
"""
|
|
@@ -3,7 +3,7 @@ import logging
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
# EXT
|
|
6
|
-
import
|
|
6
|
+
import httpx2
|
|
7
7
|
import orjson
|
|
8
8
|
|
|
9
9
|
from ._http_common import (
|
|
@@ -290,7 +290,7 @@ class Shopware6StoreFrontClientBase:
|
|
|
290
290
|
request_url: str,
|
|
291
291
|
payload: PayLoad = None,
|
|
292
292
|
update_header_fields: dict[str, str] | None = None,
|
|
293
|
-
) ->
|
|
293
|
+
) -> httpx2.Response | None:
|
|
294
294
|
"""
|
|
295
295
|
makes a request, using the conf.store_api_sw_access_key for authentication
|
|
296
296
|
|
|
@@ -330,11 +330,11 @@ class Shopware6StoreFrontClientBase:
|
|
|
330
330
|
if isinstance(payload, Criteria):
|
|
331
331
|
payload = payload.get_dict()
|
|
332
332
|
storefront_api_url = self._build_storefront_api_url(endpoint=request_url)
|
|
333
|
-
response:
|
|
333
|
+
response: httpx2.Response
|
|
334
334
|
headers = self._get_headers(update_header_fields=update_header_fields)
|
|
335
335
|
|
|
336
336
|
if http_method == HttpMethod.GET:
|
|
337
|
-
response =
|
|
337
|
+
response = httpx2.request(
|
|
338
338
|
"GET",
|
|
339
339
|
storefront_api_url,
|
|
340
340
|
params=payload,
|
|
@@ -343,7 +343,7 @@ class Shopware6StoreFrontClientBase:
|
|
|
343
343
|
follow_redirects=self.config.follow_redirects,
|
|
344
344
|
)
|
|
345
345
|
elif http_method == HttpMethod.PATCH:
|
|
346
|
-
response =
|
|
346
|
+
response = httpx2.request(
|
|
347
347
|
"PATCH",
|
|
348
348
|
storefront_api_url,
|
|
349
349
|
content=orjson.dumps(payload).decode(),
|
|
@@ -352,7 +352,7 @@ class Shopware6StoreFrontClientBase:
|
|
|
352
352
|
follow_redirects=self.config.follow_redirects,
|
|
353
353
|
)
|
|
354
354
|
elif http_method == HttpMethod.POST:
|
|
355
|
-
response =
|
|
355
|
+
response = httpx2.request(
|
|
356
356
|
"POST",
|
|
357
357
|
storefront_api_url,
|
|
358
358
|
content=orjson.dumps(payload).decode(),
|
|
@@ -361,7 +361,7 @@ class Shopware6StoreFrontClientBase:
|
|
|
361
361
|
follow_redirects=self.config.follow_redirects,
|
|
362
362
|
)
|
|
363
363
|
elif http_method == HttpMethod.PUT:
|
|
364
|
-
response =
|
|
364
|
+
response = httpx2.request(
|
|
365
365
|
"PUT",
|
|
366
366
|
storefront_api_url,
|
|
367
367
|
content=orjson.dumps(payload).decode(),
|
|
@@ -370,7 +370,7 @@ class Shopware6StoreFrontClientBase:
|
|
|
370
370
|
follow_redirects=self.config.follow_redirects,
|
|
371
371
|
)
|
|
372
372
|
elif http_method == HttpMethod.DELETE:
|
|
373
|
-
response =
|
|
373
|
+
response = httpx2.request(
|
|
374
374
|
"DELETE",
|
|
375
375
|
storefront_api_url,
|
|
376
376
|
headers=headers,
|
|
@@ -385,7 +385,7 @@ class Shopware6StoreFrontClientBase:
|
|
|
385
385
|
|
|
386
386
|
try:
|
|
387
387
|
response.raise_for_status() # type: ignore[possibly-undefined]
|
|
388
|
-
except
|
|
388
|
+
except httpx2.HTTPStatusError as exc:
|
|
389
389
|
detailed_error = f" : {exc.response.text}"
|
|
390
390
|
raise ShopwareAPIError(f"{exc}{detailed_error}") from exc
|
|
391
391
|
return response
|
|
@@ -7,7 +7,7 @@ import sys
|
|
|
7
7
|
import time
|
|
8
8
|
from typing import TYPE_CHECKING
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import httpx2
|
|
11
11
|
import pytest
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
@@ -435,10 +435,10 @@ def _docker_engine_is_linux() -> bool:
|
|
|
435
435
|
def _is_docker_container_active() -> bool:
|
|
436
436
|
"""Check if the local docker container is running and responding."""
|
|
437
437
|
try:
|
|
438
|
-
response =
|
|
438
|
+
response = httpx2.get("http://localhost/admin", timeout=REQUEST_TIMEOUT, follow_redirects=True)
|
|
439
439
|
# Container is ready if we get a successful response or a redirect to login
|
|
440
440
|
return response.status_code in (200, 302, 403)
|
|
441
|
-
except (
|
|
441
|
+
except (httpx2.ConnectError, httpx2.ReadError, httpx2.TimeoutException):
|
|
442
442
|
return False
|
|
443
443
|
|
|
444
444
|
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/.devcontainer/devcontainer.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/.github/workflows/cicd_docker.yml
RENAMED
|
File without changes
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/.github/workflows/codeql-analysis.yml
RENAMED
|
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
|
|
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
|
|
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
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/.gitignore
RENAMED
|
File without changes
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/__init__.py
RENAMED
|
File without changes
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/__main__.py
RENAMED
|
File without changes
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/_compat.py
RENAMED
|
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
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/src/lib_shopware6_api_base/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/local_testscripts/run_clean.sh
RENAMED
|
File without changes
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/local_testscripts/run_pytest.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/local_testscripts/shellcheck.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/test_criteria_aggregation.py
RENAMED
|
File without changes
|
|
File without changes
|
{lib_shopware6_api_base-3.0.2 → lib_shopware6_api_base-3.1.0}/tests/test_criteria_sorting.py
RENAMED
|
File without changes
|
|
File without changes
|