Flask-WooCommerce 0.1.0rc1__py3-none-any.whl
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.
- flask_woocommerce/__init__.py +143 -0
- flask_woocommerce/exceptions.py +11 -0
- flask_woocommerce/py.typed +0 -0
- flask_woocommerce/structs.py +44 -0
- flask_woocommerce-0.1.0rc1.dist-info/METADATA +169 -0
- flask_woocommerce-0.1.0rc1.dist-info/RECORD +8 -0
- flask_woocommerce-0.1.0rc1.dist-info/WHEEL +4 -0
- flask_woocommerce-0.1.0rc1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Flask extension for the WooCommerce REST API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import msgspec
|
|
9
|
+
from woocommerce import ( # type: ignore[import-untyped]
|
|
10
|
+
API as WooCommerceAPIClient, # noqa: N811
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from flask_woocommerce import exceptions
|
|
14
|
+
from flask_woocommerce.structs import Config
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from flask import Flask
|
|
18
|
+
|
|
19
|
+
__all__ = ("WooCommerce",)
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WooCommerce:
|
|
25
|
+
"""WooCommerce API client integrated as a Flask extension.
|
|
26
|
+
|
|
27
|
+
Reads its configuration from ``app.config["WOOCOMMERCE"]`` and validates
|
|
28
|
+
it against :class:`~flask_woocommerce.structs.Config`. Supports both
|
|
29
|
+
eager and deferred initialisation via the standard Flask extension pattern.
|
|
30
|
+
|
|
31
|
+
A single flat dict is treated as one client stored under ``"default"``.
|
|
32
|
+
A dict of dicts creates multiple named clients.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, app: Flask | None = None) -> None:
|
|
36
|
+
"""Create the extension, optionally binding it to *app*.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
app: Optional Flask application. If provided, :meth:`init_app` is
|
|
40
|
+
called immediately.
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
self.clients: dict[str, WooCommerceAPIClient] = {}
|
|
44
|
+
if app is None:
|
|
45
|
+
return
|
|
46
|
+
self.init_app(app)
|
|
47
|
+
|
|
48
|
+
def init_app(self, app: Flask) -> None:
|
|
49
|
+
"""Initialise the extension with a Flask application.
|
|
50
|
+
|
|
51
|
+
Reads the ``WOOCOMMERCE`` key from *app*'s config, validates it, and
|
|
52
|
+
creates the underlying API client(s).
|
|
53
|
+
|
|
54
|
+
If the config value is a flat dict containing a ``"url"`` key, a single
|
|
55
|
+
client is created under the name ``"default"``. Otherwise the value is
|
|
56
|
+
treated as a mapping of *name -> config-dict* and one client is created
|
|
57
|
+
per entry.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
app: The Flask application to bind to.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ConfigError: If the configuration is missing or invalid.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
logger.debug("Initializing Flask-WooCommerce extension")
|
|
67
|
+
|
|
68
|
+
raw_config = app.config.get("WOOCOMMERCE")
|
|
69
|
+
if not isinstance(raw_config, dict):
|
|
70
|
+
msg = "Missing or invalid WOOCOMMERCE configuration."
|
|
71
|
+
logger.error(msg)
|
|
72
|
+
raise exceptions.ConfigError(msg)
|
|
73
|
+
|
|
74
|
+
if "url" in raw_config:
|
|
75
|
+
logger.debug("Creating single WooCommerce client (default)")
|
|
76
|
+
self.clients["default"] = self._make_client(raw_config)
|
|
77
|
+
else:
|
|
78
|
+
logger.debug(
|
|
79
|
+
"Creating multiple WooCommerce clients: %s",
|
|
80
|
+
list(raw_config.keys()),
|
|
81
|
+
)
|
|
82
|
+
for name, client_config in raw_config.items():
|
|
83
|
+
logger.debug("Creating client: %s", name)
|
|
84
|
+
self.clients[name] = self._make_client(client_config)
|
|
85
|
+
|
|
86
|
+
logger.info(
|
|
87
|
+
"Flask-WooCommerce initialized with %d client(s)",
|
|
88
|
+
len(self.clients),
|
|
89
|
+
)
|
|
90
|
+
app.extensions["woocommerce"] = self
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def client(self) -> WooCommerceAPIClient:
|
|
94
|
+
"""The default WooCommerce API client.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The client stored under the ``"default"`` key.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ConfigError: If no default client has been registered.
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
return self.clients["default"]
|
|
105
|
+
except KeyError:
|
|
106
|
+
msg = (
|
|
107
|
+
"No default client configured. "
|
|
108
|
+
'Use wc.clients["<name>"] for named clients.'
|
|
109
|
+
)
|
|
110
|
+
raise exceptions.ConfigError(msg) from None
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def _make_client(raw: object) -> WooCommerceAPIClient:
|
|
114
|
+
"""Validate a raw config dict and return an API client.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
raw: A dict to validate against :class:`Config`.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A configured :class:`WooCommerceAPIClient` instance.
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
ConfigError: If validation fails.
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
config = msgspec.convert(raw, Config)
|
|
128
|
+
logger.debug("Configuration validated successfully")
|
|
129
|
+
except msgspec.ValidationError as validation_error:
|
|
130
|
+
logger.exception("Configuration validation failed")
|
|
131
|
+
msg = f"Invalid WooCommerce configuration: {validation_error}"
|
|
132
|
+
raise exceptions.ConfigError(msg) from validation_error
|
|
133
|
+
client_config = {
|
|
134
|
+
k: v
|
|
135
|
+
for k, v in msgspec.structs.asdict(config).items()
|
|
136
|
+
if v is not msgspec.UNSET
|
|
137
|
+
}
|
|
138
|
+
client = WooCommerceAPIClient(**client_config)
|
|
139
|
+
logger.info("Client created with config: %s", {
|
|
140
|
+
k: "<redacted>" if k in ("consumer_key", "consumer_secret") else v
|
|
141
|
+
for k, v in client_config.items()
|
|
142
|
+
}) # fmt: skip
|
|
143
|
+
return client
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Flask-WooCommerce exception classes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class WooCommerceError(Exception):
|
|
7
|
+
"""Base exception for Flask-WooCommerce."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConfigError(WooCommerceError):
|
|
11
|
+
"""Raised when the ``WOOCOMMERCE`` configuration is missing or invalid."""
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Flask-WooCommerce configuration structures."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import msgspec
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Config(msgspec.Struct):
|
|
11
|
+
"""WooCommerce API client configuration.
|
|
12
|
+
|
|
13
|
+
Validated from the ``WOOCOMMERCE`` key in the Flask application config.
|
|
14
|
+
Required fields must be present; optional fields are left unset by default
|
|
15
|
+
so that the upstream SDK uses its own defaults.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
url: Base URL of the WooCommerce store (e.g. ``https://example.com``).
|
|
19
|
+
consumer_key: WooCommerce REST API consumer key.
|
|
20
|
+
consumer_secret: WooCommerce REST API consumer secret.
|
|
21
|
+
wp_api: Enable the WP REST API integration.
|
|
22
|
+
version: API version (e.g. ``wc/v3``).
|
|
23
|
+
timeout: Request timeout in seconds.
|
|
24
|
+
verify_ssl: Verify SSL certificates.
|
|
25
|
+
query_string_auth: Use query string authentication instead of HTTP Basic.
|
|
26
|
+
user_agent: Custom ``User-Agent`` header value.
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
url: Annotated[
|
|
31
|
+
str,
|
|
32
|
+
msgspec.Meta(
|
|
33
|
+
pattern=r"^https?://[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+([:/].*)?$",
|
|
34
|
+
description="WooCommerce store URL (http or https)",
|
|
35
|
+
),
|
|
36
|
+
]
|
|
37
|
+
consumer_key: str
|
|
38
|
+
consumer_secret: str
|
|
39
|
+
wp_api: bool | msgspec.UnsetType = msgspec.UNSET
|
|
40
|
+
version: str | msgspec.UnsetType = msgspec.UNSET
|
|
41
|
+
timeout: float | msgspec.UnsetType = msgspec.UNSET
|
|
42
|
+
verify_ssl: bool | msgspec.UnsetType = msgspec.UNSET
|
|
43
|
+
query_string_auth: bool | msgspec.UnsetType = msgspec.UNSET
|
|
44
|
+
user_agent: str | msgspec.UnsetType = msgspec.UNSET
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Flask-WooCommerce
|
|
3
|
+
Version: 0.1.0rc1
|
|
4
|
+
Summary: A Flask extension for the WooCommerce REST API.
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: Quentin Boudinel
|
|
7
|
+
Author-email: quentin.boudinel@atelier-26.net
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: flask (>=3.1.2,<4.0.0)
|
|
16
|
+
Requires-Dist: msgspec (>=0.20.0,<0.21.0)
|
|
17
|
+
Requires-Dist: woocommerce (>=3.0.0,<4.0.0)
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# Flask-WooCommerce
|
|
21
|
+
|
|
22
|
+
[](https://forgejo.atelier-26.net/quentin.boudinel/Flask-WooCommerce/actions)
|
|
23
|
+
[](https://www.python.org/downloads/)
|
|
24
|
+
[](LICENSE)
|
|
25
|
+
[](https://github.com/astral-sh/ruff)
|
|
26
|
+
|
|
27
|
+
A Flask extension that integrates the [WooCommerce REST API](https://woocommerce.github.io/woocommerce-rest-api-docs/) into your application.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install flask-woocommerce
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### Configuration
|
|
38
|
+
|
|
39
|
+
Add your WooCommerce credentials to the Flask app config:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
app.config["WOOCOMMERCE"] = {
|
|
43
|
+
"url": "https://your-store.com",
|
|
44
|
+
"consumer_key": "ck_...",
|
|
45
|
+
"consumer_secret": "cs_...",
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Optional settings
|
|
50
|
+
|
|
51
|
+
| Key | Type | Description |
|
|
52
|
+
|-----|------|-------------|
|
|
53
|
+
| `wp_api` | `bool` | Enable the WP REST API integration |
|
|
54
|
+
| `version` | `str` | API version (e.g. `wc/v3`) |
|
|
55
|
+
| `timeout` | `float` | Request timeout in seconds |
|
|
56
|
+
| `verify_ssl` | `bool` | Verify SSL certificates |
|
|
57
|
+
| `query_string_auth` | `bool` | Use query string auth instead of HTTP Basic |
|
|
58
|
+
| `user_agent` | `str` | Custom `User-Agent` header value |
|
|
59
|
+
|
|
60
|
+
### Initialisation
|
|
61
|
+
|
|
62
|
+
Eager:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from flask import Flask
|
|
66
|
+
from flask_woocommerce import WooCommerce
|
|
67
|
+
|
|
68
|
+
app = Flask(__name__)
|
|
69
|
+
app.config["WOOCOMMERCE"] = { ... }
|
|
70
|
+
wc = WooCommerce(app)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Deferred (application factory pattern):
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
wc = WooCommerce()
|
|
77
|
+
|
|
78
|
+
def create_app():
|
|
79
|
+
app = Flask(__name__)
|
|
80
|
+
app.config["WOOCOMMERCE"] = { ... }
|
|
81
|
+
wc.init_app(app)
|
|
82
|
+
return app
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Making requests
|
|
86
|
+
|
|
87
|
+
Access the underlying API client via the `client` property:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
# Get all products
|
|
91
|
+
response = wc.client.get("products")
|
|
92
|
+
|
|
93
|
+
# Create a product
|
|
94
|
+
response = wc.client.post("products", {"name": "New Product", "regular_price": "9.99"})
|
|
95
|
+
|
|
96
|
+
# Update a product
|
|
97
|
+
response = wc.client.put("products/123", {"regular_price": "12.99"})
|
|
98
|
+
|
|
99
|
+
# Delete a product
|
|
100
|
+
response = wc.client.delete("products/123")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Multiple stores
|
|
104
|
+
|
|
105
|
+
To connect to more than one WooCommerce store, use a dict-of-dicts config:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
app.config["WOOCOMMERCE"] = {
|
|
109
|
+
"default": {
|
|
110
|
+
"url": "https://main-store.com",
|
|
111
|
+
"consumer_key": "ck_...",
|
|
112
|
+
"consumer_secret": "cs_...",
|
|
113
|
+
},
|
|
114
|
+
"wholesale": {
|
|
115
|
+
"url": "https://wholesale-store.com",
|
|
116
|
+
"consumer_key": "ck_...",
|
|
117
|
+
"consumer_secret": "cs_...",
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
wc = WooCommerce(app)
|
|
122
|
+
|
|
123
|
+
# Access clients by name
|
|
124
|
+
main_products = wc.client.get("products") # "default" client
|
|
125
|
+
wholesale_products = wc.clients["wholesale"].get("products")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Logging
|
|
129
|
+
|
|
130
|
+
Flask-WooCommerce uses Python's standard logging module to provide visibility into extension initialization and configuration. This can be helpful for debugging configuration issues.
|
|
131
|
+
|
|
132
|
+
Enable debug logging to see detailed information:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
import logging
|
|
136
|
+
|
|
137
|
+
# Enable debug logging for Flask-WooCommerce
|
|
138
|
+
logging.getLogger('flask_woocommerce').setLevel(logging.DEBUG)
|
|
139
|
+
|
|
140
|
+
# Or configure via Flask's logging
|
|
141
|
+
app.logger.setLevel(logging.DEBUG)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Log levels used:
|
|
145
|
+
- **DEBUG**: Configuration validation, client creation details
|
|
146
|
+
- **INFO**: Extension initialization summary
|
|
147
|
+
- **ERROR**: Configuration errors with full traceback
|
|
148
|
+
|
|
149
|
+
**Security note**: Sensitive credentials (consumer_key, consumer_secret) are automatically masked in debug logs.
|
|
150
|
+
|
|
151
|
+
## Example Application
|
|
152
|
+
|
|
153
|
+
A complete example application is available in the [`example/`](example/) directory. It demonstrates:
|
|
154
|
+
|
|
155
|
+
- Basic setup and configuration
|
|
156
|
+
- Making API requests
|
|
157
|
+
- Error handling
|
|
158
|
+
- Multiple endpoint examples
|
|
159
|
+
|
|
160
|
+
See [example/README.md](example/README.md) for setup instructions.
|
|
161
|
+
|
|
162
|
+
## Contributing
|
|
163
|
+
|
|
164
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
|
169
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
flask_woocommerce/__init__.py,sha256=ctlkfatdvCxJJypUS_RobRdM3zZqswm2SAoFVOJ7rNE,4729
|
|
2
|
+
flask_woocommerce/exceptions.py,sha256=i43MqVOfhYfMMRdcPqpnAxMHbIL00pA2g2n6ty4rSV0,282
|
|
3
|
+
flask_woocommerce/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
flask_woocommerce/structs.py,sha256=DjcAqYI__KIQxnWYUw2JS99IU5XsTIzC9HShSEZxGY8,1628
|
|
5
|
+
flask_woocommerce-0.1.0rc1.dist-info/METADATA,sha256=DiWlXo0651A8MVMc3AJ08NIBMCcH5qgS5Lf9vH6kMa0,4662
|
|
6
|
+
flask_woocommerce-0.1.0rc1.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
|
|
7
|
+
flask_woocommerce-0.1.0rc1.dist-info/licenses/LICENSE,sha256=Ikky_3YzgOUjlQpcD_VF-DIpYJrb7wgnugMLibfz2xM,1073
|
|
8
|
+
flask_woocommerce-0.1.0rc1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Quentin Boudinel
|
|
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.
|