msgspec-config 0.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.
- msgspec_config-0.1.0/LICENSE +21 -0
- msgspec_config-0.1.0/MANIFEST.in +1 -0
- msgspec_config-0.1.0/PKG-INFO +404 -0
- msgspec_config-0.1.0/README.md +379 -0
- msgspec_config-0.1.0/docs/assets/msgspec-config-logo.svg +54 -0
- msgspec_config-0.1.0/docs/index.html +7 -0
- msgspec_config-0.1.0/docs/msgspec_config.html +4187 -0
- msgspec_config-0.1.0/msgspec_config/__init__.py +26 -0
- msgspec_config-0.1.0/msgspec_config/base.py +612 -0
- msgspec_config-0.1.0/msgspec_config/fields.py +357 -0
- msgspec_config-0.1.0/msgspec_config/mapping.py +466 -0
- msgspec_config-0.1.0/msgspec_config/merge.py +76 -0
- msgspec_config-0.1.0/msgspec_config/sources/__init__.py +17 -0
- msgspec_config-0.1.0/msgspec_config/sources/api.py +77 -0
- msgspec_config-0.1.0/msgspec_config/sources/cli.py +492 -0
- msgspec_config-0.1.0/msgspec_config/sources/dotenv.py +205 -0
- msgspec_config-0.1.0/msgspec_config/sources/env.py +70 -0
- msgspec_config-0.1.0/msgspec_config/sources/json.py +68 -0
- msgspec_config-0.1.0/msgspec_config/sources/toml.py +57 -0
- msgspec_config-0.1.0/msgspec_config/sources/yaml.py +56 -0
- msgspec_config-0.1.0/msgspec_config/typing.py +224 -0
- msgspec_config-0.1.0/msgspec_config.egg-info/PKG-INFO +404 -0
- msgspec_config-0.1.0/msgspec_config.egg-info/SOURCES.txt +36 -0
- msgspec_config-0.1.0/msgspec_config.egg-info/dependency_links.txt +1 -0
- msgspec_config-0.1.0/msgspec_config.egg-info/requires.txt +4 -0
- msgspec_config-0.1.0/msgspec_config.egg-info/top_level.txt +1 -0
- msgspec_config-0.1.0/pyproject.toml +64 -0
- msgspec_config-0.1.0/setup.cfg +4 -0
- msgspec_config-0.1.0/tests/test_base.py +198 -0
- msgspec_config-0.1.0/tests/test_fields.py +299 -0
- msgspec_config-0.1.0/tests/test_functions.py +194 -0
- msgspec_config-0.1.0/tests/test_sources_api.py +179 -0
- msgspec_config-0.1.0/tests/test_sources_cli.py +208 -0
- msgspec_config-0.1.0/tests/test_sources_dotenv.py +192 -0
- msgspec_config-0.1.0/tests/test_sources_env.py +164 -0
- msgspec_config-0.1.0/tests/test_sources_json.py +99 -0
- msgspec_config-0.1.0/tests/test_sources_toml.py +77 -0
- msgspec_config-0.1.0/tests/test_sources_yaml.py +77 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Max Pareschi
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recursive-include docs *.svg *.html
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: msgspec-config
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Settings library using msgspec as a backend for validation and serialization.
|
|
5
|
+
Author-email: Max Pareschi <max.pareschi@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/maxpareschi/msgspec-config
|
|
7
|
+
Project-URL: Issues, https://github.com/maxpareschi/msgspec-config/issues
|
|
8
|
+
Project-URL: Docs, https://maxpareschi.github.io/msgspec-config
|
|
9
|
+
Keywords: Settings,CLI,Configuration,Validation,Serialization,msgspec
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
17
|
+
Requires-Python: >=3.13
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: msgspec>=0.20.0
|
|
21
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
22
|
+
Requires-Dist: rich>=14.3.1
|
|
23
|
+
Requires-Dist: rich-click>=1.9.6
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
<p align="center">
|
|
28
|
+
<img src="docs/assets/msgspec-config-logo.svg" width="35%" alt="msgspec-config">
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
# msgspec-config
|
|
32
|
+
|
|
33
|
+
Typed, multi-source configuration loading on top of `msgspec`.
|
|
34
|
+
|
|
35
|
+
`msgspec-config` is for applications that need:
|
|
36
|
+
- one typed model for configuration shape
|
|
37
|
+
- multiple config inputs (files, `.env`, environment, CLI, custom providers)
|
|
38
|
+
- deterministic precedence across all inputs
|
|
39
|
+
- strict validation/coercion without writing parsing glue
|
|
40
|
+
|
|
41
|
+
The core idea is simple: define one `DataModel`, attach ordered `DataSource`s, and instantiate the model.
|
|
42
|
+
|
|
43
|
+
## API Docs
|
|
44
|
+
Please visit the API docs at this project's github pages site:
|
|
45
|
+
<a href="https://maxpareschi.github.io/msgspec-config">https://maxpareschi.github.io/msgspec-config/</a>
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install msgspec-config
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uv add msgspec-config
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Tested on `Python>=3.13`, probably works also on `Python>=3.11`.
|
|
58
|
+
|
|
59
|
+
## Quick Start (Layered Config)
|
|
60
|
+
|
|
61
|
+
`config.toml`:
|
|
62
|
+
|
|
63
|
+
```toml
|
|
64
|
+
host = "toml-host"
|
|
65
|
+
port = 7000
|
|
66
|
+
[log]
|
|
67
|
+
level = "INFO"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`.env`:
|
|
71
|
+
|
|
72
|
+
```dotenv
|
|
73
|
+
APP_PORT=7500
|
|
74
|
+
APP_LOG_LEVEL=DEBUG
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from msgspec_config import (
|
|
79
|
+
APISource,
|
|
80
|
+
CliSource,
|
|
81
|
+
DataModel,
|
|
82
|
+
DotEnvSource,
|
|
83
|
+
EnvironSource,
|
|
84
|
+
JSONSource,
|
|
85
|
+
TomlSource,
|
|
86
|
+
datasources,
|
|
87
|
+
entry,
|
|
88
|
+
group,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class LogConfig(DataModel):
|
|
93
|
+
level: str = "WARN"
|
|
94
|
+
file_path: str = "/var/log/app.log"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@datasources(
|
|
98
|
+
TomlSource(toml_path="config.toml"),
|
|
99
|
+
DotEnvSource(dotenv_path=".env", env_prefix="APP"),
|
|
100
|
+
EnvironSource(env_prefix="APP"),
|
|
101
|
+
CliSource(),
|
|
102
|
+
)
|
|
103
|
+
class AppConfig(DataModel):
|
|
104
|
+
host: str = entry("127.0.0.1", min_length=1)
|
|
105
|
+
port: int = entry(8080, ge=1, le=65535)
|
|
106
|
+
debug: bool = False
|
|
107
|
+
log: LogConfig = group(collapsed=True)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
cfg = AppConfig(port=9000)
|
|
111
|
+
print(cfg.model_dump_json(indent=2))
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Precedence is deterministic and intentional:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
defaults < source_1 < source_2 < ... < source_n < kwargs
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
With the example above:
|
|
121
|
+
- model defaults are the baseline
|
|
122
|
+
- `TomlSource` overrides defaults
|
|
123
|
+
- `DotEnvSource` overrides TOML
|
|
124
|
+
- `EnvironSource` overrides `.env`
|
|
125
|
+
- `CliSource` overrides environment values
|
|
126
|
+
- constructor kwargs (`AppConfig(port=9000)`) win last
|
|
127
|
+
|
|
128
|
+
Rationale: this gives safe defaults in code, then progressive override points for deploy/runtime, while still keeping a final explicit override path in Python.
|
|
129
|
+
|
|
130
|
+
Important:
|
|
131
|
+
- `env_prefix` is mandatory for both `EnvironSource` and `DotEnvSource`.
|
|
132
|
+
- Empty/blank prefixes raise `ValueError`.
|
|
133
|
+
|
|
134
|
+
## Field Helpers (`entry` and `group`)
|
|
135
|
+
|
|
136
|
+
### `entry(...)`
|
|
137
|
+
|
|
138
|
+
Use `entry(...)` when you need validation metadata and/or safe mutable defaults.
|
|
139
|
+
|
|
140
|
+
Why it exists:
|
|
141
|
+
- attaches `msgspec.Meta(...)` constraints directly from field declaration
|
|
142
|
+
- converts mutable defaults (`list`, `dict`, `set`) into factories automatically
|
|
143
|
+
- supports extra UI/schema keys: `hidden_if`, `disabled_if`, `parent_group`, `ui_component`
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from msgspec_config import DataModel, entry
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ApiConfig(DataModel):
|
|
150
|
+
timeout_seconds: int = entry(30, ge=1, le=120, description="Request timeout")
|
|
151
|
+
tags: list[str] = entry([], description="Dynamic tags")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `group(...)`
|
|
155
|
+
|
|
156
|
+
Use `group(...)` for nested object/list/dict fields inferred from annotations.
|
|
157
|
+
|
|
158
|
+
Why it exists:
|
|
159
|
+
- creates safe defaults for nested structures without shared state
|
|
160
|
+
- adds optional UI/schema hints (`collapsed`, `mutable`)
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from msgspec_config import DataModel, group
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class Child(DataModel):
|
|
167
|
+
value: int = 1
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class Parent(DataModel):
|
|
171
|
+
child: Child = group(collapsed=True)
|
|
172
|
+
children: list[Child] = group(mutable=True)
|
|
173
|
+
by_name: dict[str, Child] = group(mutable=True)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Notes:
|
|
177
|
+
- object annotations used with `group()` must be zero-arg constructible
|
|
178
|
+
- `group()` is for object/list/dict-like fields, not primitive scalars
|
|
179
|
+
|
|
180
|
+
## Built-in Sources (Behavior)
|
|
181
|
+
|
|
182
|
+
All built-ins are importable from both `msgspec_config` and `msgspec_config.sources`.
|
|
183
|
+
|
|
184
|
+
When a source is used with `resolve(model=...)` (or through `@datasources(...)` on a
|
|
185
|
+
`DataModel`), field resolution accepts both canonical and encoded/alias names, and mapped
|
|
186
|
+
output keys are emitted using encoded names.
|
|
187
|
+
|
|
188
|
+
### `TomlSource` and `YamlSource`
|
|
189
|
+
|
|
190
|
+
- load mappings from files using `msgspec.toml.decode` / `msgspec.yaml.decode`
|
|
191
|
+
- if path is unset or missing, they return `{}` (treated as "source absent")
|
|
192
|
+
- parse/read failures raise `RuntimeError` with file context
|
|
193
|
+
|
|
194
|
+
### `JSONSource`
|
|
195
|
+
|
|
196
|
+
- decodes inline JSON (`json_data`) or loads JSON from `json_path`
|
|
197
|
+
- if both are set, `json_data` takes precedence
|
|
198
|
+
- if path is unset/missing, returns `{}`
|
|
199
|
+
- parse/read failures raise `RuntimeError` with context
|
|
200
|
+
|
|
201
|
+
### `DotEnvSource`
|
|
202
|
+
|
|
203
|
+
- parses dotenv syntax (`export`, quotes, inline comments)
|
|
204
|
+
- requires non-empty `env_prefix` (prefix scoping is mandatory)
|
|
205
|
+
- nested keys are mapped with `nested_separator` (default `_`)
|
|
206
|
+
- with a `model`, values are coerced to field types
|
|
207
|
+
- recognized keys that fail coercion are captured in source `__unmapped_kwargs__`
|
|
208
|
+
|
|
209
|
+
Example precedence inside one source:
|
|
210
|
+
|
|
211
|
+
```dotenv
|
|
212
|
+
APP_LOG={"level":"DEBUG"}
|
|
213
|
+
APP_LOG_LEVEL=WARN
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
`APP_LOG_LEVEL` overrides `APP_LOG.level`, regardless of line order.
|
|
217
|
+
|
|
218
|
+
### `EnvironSource`
|
|
219
|
+
|
|
220
|
+
Same mapping/coercion behavior as `DotEnvSource`, but reads from `os.environ`.
|
|
221
|
+
`env_prefix` is mandatory, and failed coercions/unmatched keys are captured in
|
|
222
|
+
source `__unmapped_kwargs__`.
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
EnvironSource(env_prefix="APP", nested_separator="__")
|
|
226
|
+
# APP_LOG__LEVEL=ERROR -> {"log": {"level": "ERROR"}}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### `CliSource`
|
|
230
|
+
|
|
231
|
+
Generates options from model fields (including nested fields).
|
|
232
|
+
|
|
233
|
+
Key behavior:
|
|
234
|
+
- nested fields become flags like `--log-level`
|
|
235
|
+
- bools support both positive and negative forms: `--debug` / `--no-debug`
|
|
236
|
+
- nested struct fields also accept JSON on the top-level flag:
|
|
237
|
+
- `--log '{"level":"DEBUG"}'`
|
|
238
|
+
- explicit nested flags override keys from that JSON
|
|
239
|
+
- unknown CLI args are stored on source runtime state in `__unmapped_kwargs__`
|
|
240
|
+
- set `kebab_case=False` to use dotted long flags (e.g. `--log.level`)
|
|
241
|
+
- CLI accepts canonical and encoded/alias field names, and maps parsed values to encoded field names
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
src = CliSource(cli_args=["--host", "api", "--unknown-flag"])
|
|
245
|
+
data = src.resolve(model=AppConfig)
|
|
246
|
+
print(data) # {"host": "api"}
|
|
247
|
+
print(src.__unmapped_kwargs__) # {"unknown-flag": True}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### `APISource`
|
|
251
|
+
|
|
252
|
+
- performs an HTTP `GET` request against `api_url`
|
|
253
|
+
- optional auth header via `header_name` + `header_value`
|
|
254
|
+
- optional `root_node` to unwrap wrapped payloads (for example `{"data": {...}}`)
|
|
255
|
+
- request or parse failures raise `RuntimeError` with endpoint context
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
src = APISource(
|
|
259
|
+
api_url="https://example.com/config",
|
|
260
|
+
header_name="Authorization",
|
|
261
|
+
header_value="Bearer <token>",
|
|
262
|
+
root_node="data",
|
|
263
|
+
)
|
|
264
|
+
data = src.resolve()
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Custom Source Example
|
|
268
|
+
|
|
269
|
+
When built-ins are not enough, implement `DataSource.load(...)`.
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from typing import Any
|
|
273
|
+
|
|
274
|
+
from msgspec_config import DataModel, DataSource, datasources
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class SecretsSource(DataSource):
|
|
278
|
+
def load(self, model: type[DataModel] | None = None) -> dict[str, Any]:
|
|
279
|
+
# Replace this with Vault/AWS/GCP/etc.
|
|
280
|
+
return {"host": "secrets-host", "port": 8443}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@datasources(SecretsSource())
|
|
284
|
+
class ServiceConfig(DataModel):
|
|
285
|
+
host: str = "localhost"
|
|
286
|
+
port: int = 8080
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Rationale: sources are deep-cloned per model instantiation, so source-local mutable state does not leak across `DataModel()` calls. `DataSource.resolve(...)` is the public finalized loader (reset + finalize); custom sources should override `load(...)`.
|
|
290
|
+
|
|
291
|
+
## Limitations
|
|
292
|
+
|
|
293
|
+
- Do not shadow `DataModel`/`DataSource` method names with fields; this is user responsibility and can break runtime behavior.
|
|
294
|
+
|
|
295
|
+
## DataModel Helpers
|
|
296
|
+
|
|
297
|
+
`DataModel` is a `msgspec.Struct` configured as keyword-only and with dict-like output support.
|
|
298
|
+
|
|
299
|
+
Useful methods:
|
|
300
|
+
- `from_data(data)` to create an instance from a Python mapping
|
|
301
|
+
- `from_json(json_str)` to create an instance from JSON bytes/string
|
|
302
|
+
- `model_dump()` to get the model converted in Python builtins
|
|
303
|
+
- `model_dump_json(indent=...)` for JSON output
|
|
304
|
+
- `model_json_schema(indent=...)` for JSON Schema export
|
|
305
|
+
- `get_datasources_payload(*sources, **kwargs)` to retrieve merged source payloads manually
|
|
306
|
+
- `get_unmapped_payload()` to lazily merge source runtime `__unmapped_kwargs__` in source order plus unknown constructor kwargs (merged last)
|
|
307
|
+
|
|
308
|
+
Notes:
|
|
309
|
+
- `from_data(...)` and `from_json(...)` ignore unknown keys.
|
|
310
|
+
- Unknown keyword arguments passed to `DataModel(...)` are available through
|
|
311
|
+
`get_unmapped_payload()`.
|
|
312
|
+
|
|
313
|
+
Example:
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
cfg = AppConfig.from_json('{"host":"example.com","port":8081}')
|
|
317
|
+
print(cfg.model_dump())
|
|
318
|
+
print(AppConfig.model_json_schema(indent=2))
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## API Summary
|
|
322
|
+
|
|
323
|
+
- `DataModel`: typed model base class with validation/serialization helpers
|
|
324
|
+
- `DataSource`: source base class (`load(model=...) -> raw mapping`, `resolve(model=...) -> finalized mapping`)
|
|
325
|
+
- `datasources(*sources)`: decorator that attaches ordered source templates
|
|
326
|
+
- `entry(...)`: field helper with validation metadata and safe mutable defaults
|
|
327
|
+
- `group(...)`: helper for grouped object/list/dict fields
|
|
328
|
+
- built-ins: `TomlSource`, `YamlSource`, `JSONSource`, `DotEnvSource`, `EnvironSource`, `CliSource`, `APISource`
|
|
329
|
+
|
|
330
|
+
## Development (Makefile + Commands)
|
|
331
|
+
|
|
332
|
+
The repository includes a `Makefile` to standardize common local tasks. Run targets from the project root.
|
|
333
|
+
|
|
334
|
+
Prerequisites:
|
|
335
|
+
- `uv`
|
|
336
|
+
- GNU Make (`make`)
|
|
337
|
+
- on Windows, use a GNU Make provider (for example Git Bash `make` or `mingw32-make`)
|
|
338
|
+
|
|
339
|
+
Typical workflow:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
make venv # install/update dependencies from lockfile
|
|
343
|
+
make ruff # format + lint autofix
|
|
344
|
+
make test # run tests
|
|
345
|
+
make docs # regenerate docs in ./docs
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Run the full local pipeline:
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
make all
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
`all` expands to:
|
|
355
|
+
|
|
356
|
+
```text
|
|
357
|
+
venv -> ruff -> test -> docs
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Makefile targets:
|
|
361
|
+
- `make venv`: `uv sync`
|
|
362
|
+
- `make docs`: `uv run pdoc -o ./docs --docformat google --favicon assets/msgspec-config-logo.svg --logo assets/msgspec-config-logo.svg --search -t ./docs --show-source msgspec_config`
|
|
363
|
+
- `make ruff`: `uv run ruff format .` and `uv run ruff check --fix .`
|
|
364
|
+
- `make test`: `uv run pytest`
|
|
365
|
+
- `make build`: `uv build --clear --no-sources`
|
|
366
|
+
- `make publish-testpypi`: runs `make build`, then `uv publish --index testpypi`
|
|
367
|
+
- `make publish-pypi`: runs `make build`, then `uv publish`
|
|
368
|
+
|
|
369
|
+
Equivalent direct commands (without `make`):
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
uv sync
|
|
373
|
+
uv run ruff format .
|
|
374
|
+
uv run ruff check --fix .
|
|
375
|
+
uv run pytest
|
|
376
|
+
uv run pdoc -o ./docs --docformat google --favicon assets/msgspec-config-logo.svg --logo assets/msgspec-config-logo.svg --search -t ./docs --show-source msgspec_config
|
|
377
|
+
uv build --clear --no-sources
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Release (uv)
|
|
381
|
+
|
|
382
|
+
Build clean artifacts:
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
make build
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Publish to TestPyPI first:
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
$env:UV_PUBLISH_TOKEN="pypi-<testpypi-token>"
|
|
392
|
+
make publish-testpypi
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Publish to PyPI:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
$env:UV_PUBLISH_TOKEN="pypi-<pypi-token>"
|
|
399
|
+
make publish-pypi
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Packaging policy:
|
|
403
|
+
- wheel: runtime package only (`msgspec_config`)
|
|
404
|
+
- sdist: includes source, tests, and docs metadata for downstream builds/tests
|