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.
@@ -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
+ [![CI](https://forgejo.atelier-26.net/quentin.boudinel/Flask-WooCommerce/badges/workflows/ci.yml/badge.svg)](https://forgejo.atelier-26.net/quentin.boudinel/Flask-WooCommerce/actions)
23
+ [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/downloads/)
24
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
25
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.3.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.