Skip to content

Commit 8544cc2

Browse files
authored
v0.38: Introducing EnvWizard (v1) (#235)
* add `v1/env.py` * update env.py * minor updates * minor updates * minor updates * minor updates * minor updates * planned for #206 * add Env Precedence and Secrets Dir + Dotenv Support for `v1` of `EnvWizard` * minor changes * cache secrets and dotenv * add helper methods * Add `v1_field_to_env_load` and support `Alias(env=...)` for `EnvWizard` * Add `Env(...)` to support setting env vars for fields in `EnvWizard` subclasses * minor fix * Add `v1_pre_decoder` so `EnvWizard` can decode JSON or comma-separated strings into `dict`/`list` * Add tests and bugfix * add E2E tests * bugfix and functional updates * Add tests and bugfix * add EnvWizard E2E tests * bugfix and functional updates * Add tests * should fix #206 * v1: Decode `date` and `datetime` as UTC by default (via `fromtimestamp`) * update tests * update tests * update tests * EnvWizard: update tests and support bytes/bytearray/dataclass (when already same type) * update tests * update logging * fix for Windows: provide `tz` extra to install `tzdata` for `ZoneInfo` * v1: Fix loader/dumper codegen and subclass inheritance issues * Support `kw_only` dataclass fields in loaders * Fix dumper codegen when dataclass defines no fields * Fix incorrect caching of `to_dict` on subclasses * Ensure EnvWizard passes `kw_only=True` to dataclass * Fix FunctionBuilder globals not being merged * Accept bytes/bytearray inputs unchanged in loaders * Pin default `from_dict` and `to_dict` on subclasses (prevents lazy codegen inheritance poisoning; fixes #209) * Refresh dataclass metadata when new annotations are present * clean up imports * minor changes * v1: Rename env.py to _env.py * v1: Add tests for better coverage * v1: Support `__post_init__()` in `EnvWizard.__init__()` codegen * minor changes * v1: Support `AliasPath()` in `EnvWizard.__init__()` codegen * fix for Python 3.9 * add type checks on CI as per #234 * fix tests on Python 3.9 * add type checks on CI as per #234 * rename to underscored * add docs * minor changes * update HISTORY.rst * optimize imports * optimize imports
1 parent b939485 commit 8544cc2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+4491
-305
lines changed

.github/workflows/dev.yml

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ concurrency:
1919

2020
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
2121
jobs:
22-
# This workflow contains a single job called "build"
22+
# This workflow contains two jobs called "test" and "typecheck"
2323
test:
2424
# The type of runner that the job will run on
2525
timeout-minutes: 30
@@ -54,3 +54,31 @@ jobs:
5454

5555
- name: list files
5656
run: ls -l .
57+
58+
typecheck:
59+
runs-on: ubuntu-latest
60+
timeout-minutes: 15
61+
steps:
62+
- name: Checkout
63+
uses: actions/checkout@v4
64+
with:
65+
persist-credentials: false
66+
67+
- name: Setup python
68+
uses: actions/setup-python@v5
69+
with:
70+
python-version: '3.12'
71+
72+
- name: Install typecheck deps
73+
run: |
74+
python -m pip install --upgrade pip
75+
pip install mypy
76+
77+
- name: Syntax check
78+
run: |
79+
python -m compileall -q dataclass_wizard
80+
81+
- name: mypy
82+
run: |
83+
mypy dataclass_wizard
84+
continue-on-error: true

.pre-commit-config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@ repos:
66
name: rstcheck (README.rst)
77
args: ["--report-level=warning"]
88
files: ^README\.rst$
9+
- repo: https://github.com/pre-commit/pre-commit-hooks
10+
rev: v5.0.0
11+
hooks:
12+
- id: check-ast
13+
files: \.py$

HISTORY.rst

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,60 @@
22
History
33
=======
44

5+
0.38.0 (2025-12-26)
6+
-------------------
7+
8+
EnvWizard v1 (opt-in)
9+
~~~~~~~~~~~~~~~~~~~~~
10+
11+
EnvWizard v1 introduces a new, explicit environment-loading engine with
12+
predictable behavior and expanded feature support. v1 is **opt-in only**
13+
and does not affect existing users unless enabled.
14+
15+
Highlights:
16+
17+
- Explicit environment precedence (env / dotenv / secrets)
18+
- First-class nested dataclass support for env loading
19+
- Separate alias models for load vs dump
20+
- Improved error diagnostics and debug logging
21+
- Full support for dotenv files and secrets directories
22+
23+
New v1 features:
24+
25+
- Environment precedence is now configurable and explicit
26+
- Support for nested ``EnvWizard`` dataclasses
27+
- New aliasing model:
28+
- ``v1_field_to_env_load`` (load-only)
29+
- ``v1_field_to_alias_dump`` (dump-only)
30+
- Added ``Env(...)`` and ``Alias(env=...)`` helpers for field-level env configuration
31+
- Added ``v1_pre_decoder`` to decode JSON or delimited strings into ``dict`` / ``list``
32+
- Cached secrets and dotenv paths for improved performance
33+
- v1 supports ``__post_init__()`` in generated ``EnvWizard.__init__``
34+
35+
Helpers and APIs:
36+
37+
- Added ``env_config`` for optional ``TypedDict``-style typing of ``__env__``
38+
- ``EnvWizard.dict()`` (v0) is now ``EnvWizard.raw_dict()`` in v1
39+
- Optimized helpers: ``as_bool``, ``as_int``, ``as_str``
40+
- Added helpers: ``as_list``, ``as_dict``
41+
42+
Internal Changes and Fixes
43+
~~~~~~~~~~~~~~~~~~~~~~~~~~
44+
45+
- Fixed invalid ``.pyi`` output in ``register_type`` (thanks to :user:`GRcharles`, :pr:`234`)
46+
- Added syntax checks to pre-commit and type checks to CI (:pr:`234`)
47+
- Fixed lazy codegen inheritance poisoning (:issue:`209`)
48+
- v1: Decode ``date`` / ``datetime`` as UTC by default (:issue:`206`)
49+
- Improved Windows timezone handling via ``tz`` extra (``tzdata`` / ``ZoneInfo``)
50+
- Improved caching behavior for ``Union`` loaders
51+
- Fixed multiple codegen and caching edge cases:
52+
- ``to_dict`` caching on subclasses
53+
- empty dataclass dumpers
54+
- ``kw_only`` field handling
55+
- FunctionBuilder globals merging
56+
- Added extensive v1 test coverage (90%+)
57+
- No breaking changes without explicit v1 opt-in
58+
559
0.37.0 (2025-12-20)
660
-------------------
761

dataclass_wizard/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,29 +116,31 @@
116116
'IS_NOT',
117117
'IS_TRUTHY',
118118
'IS_FALSY',
119+
# Logging
120+
'LOG',
119121
]
120122

121123
import logging
122124

123125
from .bases_meta import LoadMeta, DumpMeta, EnvMeta, register_type
124-
from .constants import PACKAGE_NAME
125126
from .dumpers import DumpMixin, setup_default_dumper
126-
from .loaders import LoadMixin, setup_default_loader
127+
from .environ.wizard import EnvWizard
127128
from .loader_selection import asdict, fromlist, fromdict
129+
from .loaders import LoadMixin, setup_default_loader
130+
from .log import LOG
128131
from .models import (env_field, json_field, json_key, path_field, skip_if_field,
129132
KeyPath, Container,
130133
Pattern, DatePattern, TimePattern, DateTimePattern,
131134
CatchAll, SkipIf, SkipIfNone,
132135
EQ, NE, LT, LE, GT, GE, IS, IS_NOT, IS_TRUTHY, IS_FALSY)
133-
from .environ.wizard import EnvWizard
134136
from .property_wizard import property_wizard
135137
from .serial_json import DataclassWizard, JSONWizard, JSONPyWizard, JSONSerializable
136138
from .wizard_mixins import JSONListWizard, JSONFileWizard, TOMLWizard, YAMLWizard
137139

138140

139141
# Set up logging to ``/dev/null`` like a library is supposed to.
140142
# http://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library
141-
logging.getLogger(PACKAGE_NAME).addHandler(logging.NullHandler())
143+
LOG.addHandler(logging.NullHandler())
142144

143145
# Setup the default type hooks to use when converting `str` (json) or a Python
144146
# `dict` object to a `dataclass` instance.

dataclass_wizard/abstractions.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
"""
22
Contains implementations for Abstract Base Classes
33
"""
4+
from __future__ import annotations
45
import json
56

67
from abc import ABC, abstractmethod
78
from dataclasses import dataclass, InitVar, Field
8-
from typing import Type, TypeVar, Dict, Generic
9+
from typing import Type, TypeVar, Dict, Generic, TYPE_CHECKING
910

1011
from .models import Extras
11-
from .v1.models import Extras as V1Extras, TypeInfo
12+
1213
from .type_def import T, TT
1314

1415

1516
# Create a generic variable that can be 'AbstractJSONWizard', or any subclass.
1617
W = TypeVar('W', bound='AbstractJSONWizard')
1718

1819

20+
if TYPE_CHECKING:
21+
from .v1.models import Extras as V1Extras, TypeInfo
22+
23+
1924
class AbstractEnvWizard(ABC):
2025
"""
2126
Abstract class that defines the methods a sub-class must implement at a
@@ -271,6 +276,13 @@ def transform_json_field(string: str) -> str:
271276
(which will ideally be snake-cased).
272277
"""
273278

279+
@staticmethod
280+
@abstractmethod
281+
def is_none(tp: TypeInfo, extras: V1Extras) -> str:
282+
"""
283+
Generate the condition to determine if a value is None.
284+
"""
285+
274286
@staticmethod
275287
@abstractmethod
276288
def default_load_to(tp: TypeInfo, extras: V1Extras) -> str:

dataclass_wizard/abstractions.pyi

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,13 @@ class AbstractLoaderGenerator(ABC):
442442
(which will ideally be snake-cased).
443443
"""
444444

445+
@staticmethod
446+
@abstractmethod
447+
def is_none(tp: TypeInfo, extras: V1Extras) -> str:
448+
"""
449+
Generate the condition to determine if a value is None.
450+
"""
451+
445452
@staticmethod
446453
@abstractmethod
447454
def default_load_to(tp: TypeInfo, extras: V1Extras) -> str:

0 commit comments

Comments
 (0)