Skip to content

Commit b9cc534

Browse files
0.2.6: Fixed an issue where the default yaml file would not be created properly when using aliases in container
1 parent 4ee9a87 commit b9cc534

File tree

9 files changed

+107
-21
lines changed

9 files changed

+107
-21
lines changed

.github/workflows/publish-pypi.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ jobs:
99
runs-on: ubuntu-latest
1010

1111
steps:
12-
- uses: actions/checkout@v2
12+
- uses: actions/checkout@v3
1313
with:
1414
ref: master
1515
- name: Set up Python 3.10
16-
uses: actions/setup-python@v2
16+
uses: actions/setup-python@v4
1717
with:
1818
python-version: '3.10'
1919

.github/workflows/run_tox.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ jobs:
88
strategy:
99
max-parallel: 4
1010
matrix:
11-
python-version: ['3.8', '3.9', '3.10']
11+
python-version: ['3.8', '3.9', '3.10', '3.11']
1212

1313
steps:
14-
- uses: actions/checkout@v1
14+
- uses: actions/checkout@v3
1515
- name: Set up Python ${{ matrix.python-version }}
16-
uses: actions/setup-python@v2
16+
uses: actions/setup-python@v4
1717
with:
1818
python-version: ${{ matrix.python-version }}
1919
- name: Install dependencies

readme.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ sub.cancel()
157157
```
158158

159159
# Changelog
160+
#### 0.2.6 (21.12.2022)
161+
- Fixed an issue where the default yaml file would not be created properly when using aliases in container
162+
160163
#### 0.2.5 (21.10.2022)
161164
- Marked package as PEP 561 compatible (py.typed)
162165

requirements_setup.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pydantic >= 1.9.0, < 2.0
22
ruamel.yaml >= 0.17, < 0.18
3-
typing-extensions >= 4.1, < 5
3+
typing-extensions >= 4.4, < 5

src/easyconfig/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.2.5'
1+
__version__ = '0.2.6'

src/easyconfig/yaml/align.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,34 @@
11
from io import StringIO
2-
from typing import Union
2+
from typing import Any, Optional, Tuple, Union
3+
4+
from ruamel.yaml import CommentToken
35

46
from easyconfig.yaml import yaml_rt
57

68

9+
def get_column(obj: Tuple[Any, Any, Optional[CommentToken], Any]):
10+
if (token := obj[2]) is None:
11+
return 0
12+
return token.column
13+
14+
715
def align_comments(d, extra_indent=0):
816

917
# Only process when it's a data structure -> dict or list
1018
is_dict = isinstance(d, dict)
11-
if not is_dict and not isinstance(d, list):
19+
is_list = isinstance(d, list)
20+
if not is_dict and not is_list:
1221
return None
1322

1423
comments = d.ca.items.values()
24+
1525
if comments:
16-
max_col = max(map(lambda x: x[2].column, comments), default=0)
26+
max_col = max(map(get_column, comments), default=0)
1727
indent_value = max_col + extra_indent
1828
for comment in comments:
1929
token = comment[2]
30+
if token is None:
31+
continue
2032
token.column = indent_value
2133

2234
# workaround for multiline eol comments

src/easyconfig/yaml/from_model.py

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,44 @@
1+
from enum import Enum
2+
13
from pydantic import BaseModel
24
from pydantic.fields import ModelField
35

46
from easyconfig.__const__ import ARG_NAME_IN_FILE, MISSING
5-
from easyconfig.yaml import CommentedMap
7+
from easyconfig.yaml import CommentedMap, CommentedSeq
8+
9+
NoneType = type(None)
10+
11+
12+
def _get_yaml_value(obj, parent_model: BaseModel, skip_none=True):
13+
# Sometimes enum is used with int/str
14+
if isinstance(obj, Enum):
15+
return _get_yaml_value(obj.value, parent_model=parent_model, skip_none=skip_none)
16+
17+
# yaml native datatypes
18+
if isinstance(obj, (int, float, str, bool, bytes, NoneType)):
19+
return obj
20+
21+
if isinstance(obj, BaseModel):
22+
return cmap_from_model(obj, skip_none=skip_none)
23+
24+
if isinstance(obj, (list, tuple, set, frozenset)):
25+
seq = CommentedSeq()
26+
for value in obj:
27+
seq.append(_get_yaml_value(value, parent_model=parent_model, skip_none=skip_none))
28+
return seq
29+
30+
if isinstance(obj, dict):
31+
ret = CommentedMap()
32+
for key, value in obj.items():
33+
yaml_key = _get_yaml_value(key, parent_model=parent_model, skip_none=skip_none)
34+
ret[yaml_key] = _get_yaml_value(value, parent_model=parent_model, skip_none=skip_none)
35+
return ret
36+
37+
# YAML can't serialize all data pydantic types natively, so we use the json serializer of the model
38+
# This works since a valid json is always a valid YAML. It's not nice but it's something!
39+
model_cfg = parent_model.__config__
40+
str_val = model_cfg.json_dumps(obj, default=parent_model.__json_encoder__)
41+
return model_cfg.json_loads(str_val)
642

743

844
def cmap_from_model(model: BaseModel, skip_none=True) -> CommentedMap:
@@ -20,15 +56,8 @@ def cmap_from_model(model: BaseModel, skip_none=True) -> CommentedMap:
2056
if not field_info.extra.get(ARG_NAME_IN_FILE, True):
2157
continue
2258

23-
if isinstance(value, BaseModel):
24-
cmap[yaml_key] = cmap_from_model(value)
25-
else:
26-
# YAML can't serialize all data pydantic types natively, so we use the json serializer of the model
27-
# This works since a valid json is always a valid YAML. It's not nice but it's something!
28-
mode_cfg = model.__config__
29-
_json_value = mode_cfg.json_dumps(
30-
{'obj': value}, default=model.__json_encoder__)
31-
cmap[yaml_key] = mode_cfg.json_loads(_json_value)['obj']
59+
# get yaml representation
60+
cmap[yaml_key] = _get_yaml_value(value, parent_model=model)
3261

3362
if not description:
3463
continue

tests/test_app_creation.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
from enum import Enum
2+
from typing import List
3+
14
import pytest
25
from pydantic import BaseModel, Field, ValidationError
36

47
from easyconfig import create_app_config
58
from easyconfig.errors import DefaultNotSet, ExtraKwArgsNotAllowed
9+
from easyconfig.models import BaseModel as EasyBaseModel
610

711

812
def test_simple():
@@ -58,3 +62,19 @@ class SimpleModelErr(BaseModel):
5862
create_app_config(SimpleModelErr(aaa=99))
5963

6064
assert str(e.value) == 'Extra kwargs for field "a" of SimpleModelErr are not allowed: in__file'
65+
66+
67+
def test_list_of_models():
68+
69+
class MyEnum(str, Enum):
70+
A = 'aa'
71+
72+
class SimpleModel(EasyBaseModel):
73+
a: int = Field(5, alias='aaa', in_file=False)
74+
b: int = Field(6)
75+
c: MyEnum = MyEnum.A
76+
77+
class EncapModel(EasyBaseModel):
78+
c: List[SimpleModel] = []
79+
80+
create_app_config(EncapModel(c=[SimpleModel(), SimpleModel(), ]))

tests/yaml/test_model_to_yaml.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import datetime
2-
from typing import Optional
2+
from enum import Enum
3+
from typing import List, Optional
34

45
from pydantic import BaseModel, Field
56

@@ -115,3 +116,24 @@ class SimpleModel(BaseModel):
115116
'b: 3 # \n' \
116117
'# This is\n' \
117118
'# value b\n'
119+
120+
121+
def test_alias_not_in_file():
122+
123+
class MyEnum(str, Enum):
124+
A = 'aa'
125+
126+
class SimpleModel(BaseModel):
127+
a: int = Field(5, alias='aaa', in_file=False)
128+
b: int = Field(6, description='Description value b')
129+
c: MyEnum = MyEnum.A
130+
131+
class EncapModel(BaseModel):
132+
my_list: List[SimpleModel] = []
133+
134+
assert dump_yaml(cmap_from_model(EncapModel(my_list=[SimpleModel(), SimpleModel(b=5), ]))) == \
135+
'my_list:\n' \
136+
'- b: 6 # Description value b\n' \
137+
' c: aa\n' \
138+
'- b: 5 # Description value b\n' \
139+
' c: aa\n'

0 commit comments

Comments
 (0)