diff --git a/.hgtags b/.hgtags
index e4d13e3..5836cb7 100644
--- a/.hgtags
+++ b/.hgtags
@@ -231,3 +231,4 @@ c0da0ba934877fdfe63bee77ec12a7d2341f5398 0.18.1
a35908655d678b8463ee6198869a0708b3446e06 0.18.2
e32fbfcda1a48d808542670d91f1e84d14f69956 0.18.3
08d87cada1f6e5fedde079b55536061e4fe246a0 0.18.4
+eb3ecf31085135283908fc8449befebbc1fff4b3 0.18.5
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 5662c98..ebafea4 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -6,7 +6,8 @@ build:
python: "3.11"
jobs:
pre_build:
- - pip install ryd>=0.8.2
+ - pip install ryd>=0.9.2
+ - ryd --version -v
- ryd convert --generate-mkdocs-config mkdocs.yaml _doc
python:
diff --git a/CHANGES b/CHANGES
index 2fc13eb..de0d020 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,10 @@
+[0.18.6, 2024-02-07]:
+- fixed an issue with dataclass loading when the fields were collections (bug found
+ as a result of a question by [FibroMyAlgebra](https://stackoverflow.com/users/6855070/fibromyalgebra)
+ on [StackOverflow](https://stackoverflow.com/a/77485786/1307905))
+- fixed an issue loading dataclasses with `InitVar` fields when `from __future__ import
+ annotations` was used to delay evaluation of typing.
+
[0.18.5, 2023-11-03]:
- there is some indication that dependent packages have been pinned to use specific
(tested) and just install the latest even in Python versions that have end-of-life
diff --git a/LICENSE b/LICENSE
index 5fdca40..8777c9c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
- Copyright (c) 2014-2023 Anthon van der Neut, Ruamel bvba
+ Copyright (c) 2014-2024 Anthon van der Neut, Ruamel bvba
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index bf3d8cc..8e24a45 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,13 @@
+
# ruamel.yaml
`ruamel.yaml` is a YAML 1.2 loader/dumper package for Python.
+
As announced, in 0.18.0, the old PyYAML functions have been deprecated.
(`scan`, `parse`, `compose`, `load`, `emit`, `serialize`, `dump` and their variants
(`_all`, `safe_`, `round_trip_`, etc)). If you only read this after your program has
@@ -120,6 +122,7 @@ the API is stable enough to make the transition.
+
[![image](https://readthedocs.org/projects/yaml/badge/?version=latest)](https://yaml.readthedocs.org/en/latest?badge=latest)[![image](https://bestpractices.coreinfrastructure.org/projects/1128/badge)](https://bestpractices.coreinfrastructure.org/projects/1128)
[![image](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree/_doc/_static/license.svg?format=raw)](https://opensource.org/licenses/MIT)
[![image](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree/_doc/_static/pypi.svg?format=raw)](https://pypi.org/project/ruamel.yaml/)
@@ -128,6 +131,11 @@ the API is stable enough to make the transition.
# ChangeLog
+0.18.6 (2024-02-07):
+
+- fixed an issue with dataclass loading when the fields were collections (bug found as a result of a question by [FibroMyAlgebra](https://stackoverflow.com/users/6855070/fibromyalgebra) on [StackOverflow](https://stackoverflow.com/a/77485786/1307905))
+- fixed an issue loading dataclasses with `InitVar` fields when `from __future__ import annotations` was used to delay evaluation of typing.
+
0.18.5 (2023-11-03):
- there is some indication that dependent packages have been pinned to use specific (tested) and just install the latest even in Python versions that have end-of-life
@@ -390,6 +398,7 @@ scalar to start before the `#` column of a following comment.
Effectively making the comment part of the scalar in the output.
(reported by [Bence Nagy](https://sourceforge.net/u/underyx/))
+
------------------------------------------------------------------------
For older changes see the file
diff --git a/__init__.py b/__init__.py
index f8cfa43..cf9a01d 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,13 +1,14 @@
-# coding: utf-8
+
+from __future__ import annotations
if False: # MYPY
from typing import Dict, Any # NOQA
_package_data = dict(
full_package_name='ruamel.yaml',
- version_info=(0, 18, 5),
- __version__='0.18.5',
- version_timestamp='2023-11-03 08:54:26',
+ version_info=(0, 18, 6),
+ __version__='0.18.6',
+ version_timestamp='2024-02-07 07:43:33',
author='Anthon van der Neut',
author_email='a.van.der.neut@ruamel.eu',
description='ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order', # NOQA
diff --git a/_doc/_static/pypi.svg b/_doc/_static/pypi.svg
index 868101a..28c535d 100644
--- a/_doc/_static/pypi.svg
+++ b/_doc/_static/pypi.svg
@@ -1 +1 @@
- pypi pypi 0.18.5 0.18.5
+ pypi pypi 0.18.6 0.18.6
diff --git a/_test/test_dataclass.py b/_test/test_dataclass.py
index ac5ec56..bcce1ba 100644
--- a/_test/test_dataclass.py
+++ b/_test/test_dataclass.py
@@ -1,4 +1,5 @@
+from __future__ import annotations
from dataclasses import dataclass, fields, InitVar # NOQA
from textwrap import dedent
@@ -133,3 +134,47 @@ def __post_init__(self, xyz: Union[str, None]) -> None:
dc2 = yaml.load(yaml_str)
assert dc2.xyz == 'hello'
assert dc2.klm == 55 + len('hello')
+
+ def test_collection_field(self) -> None:
+ # https://stackoverflow.com/a/77485786/1307905
+ import ruamel.yaml
+ from dataclasses import dataclass
+
+ @dataclass
+ class Msg:
+
+ id: int
+ desc: str
+ fields: list[Field]
+
+ def __post_init__(self) -> None:
+ idx: int = 0
+ for field in self.fields: # why is this empty??
+ field.index = idx
+ idx += field.size
+
+ @dataclass
+ class Field:
+ id: int
+ name: str
+ units: str
+ size: int
+ index: int = -1
+
+ yaml = ruamel.yaml.YAML()
+ yaml.register_class(Msg)
+ yaml.register_class(Field)
+
+ msg: Msg = yaml.load("""\
+ !Msg
+ id: 1
+ desc: status
+ fields:
+ - !Field
+ id: 1
+ name: Temp
+ units: degC
+ size: 2
+ """)
+
+ assert msg.fields[0].index != -1
diff --git a/_test/test_datetime.py b/_test/test_datetime.py
index 388b96b..b68d8e5 100644
--- a/_test/test_datetime.py
+++ b/_test/test_datetime.py
@@ -19,8 +19,10 @@
"""
+import sys
import copy
import pytest # type: ignore # NOQA
+from datetime import datetime as DateTime, timezone as TimeZone, timedelta as TimeDelta
from roundtrip import round_trip, dedent, round_trip_load, round_trip_dump # type: ignore # NOQA
@@ -138,6 +140,21 @@ def test_issue_45(self) -> None:
dt: 2016-08-19T22:45:47Z
""")
+ def test_issue_366(self) -> None:
+ import ruamel.yaml
+ import io
+
+ round_trip("""
+ [2021-02-01 22:34:48.696868-03:00]
+ """)
+ yaml = ruamel.yaml.YAML()
+ dd = DateTime(2021, 2, 1, 22, 34, 48, 696868, TimeZone(TimeDelta(hours=-3), name=''))
+ buf = io.StringIO()
+ yaml.dump(dd, buf)
+ assert buf.getvalue() == '2021-02-01 22:34:48.696868-03:00\n...\n'
+ rd = yaml.load(buf.getvalue())
+ assert rd == dd
+
def test_deepcopy_datestring(self) -> None:
# reported by Quuxplusone, http://stackoverflow.com/a/41577841/1307905
x = dedent("""\
@@ -158,3 +175,19 @@ def test_fraction_overflow(self) -> None:
- 2022-01-02T12:35:00
""")
round_trip(inp, exp)
+
+ def Xtest_tzinfo(self) -> None:
+ import ruamel.yaml
+
+ yaml = ruamel.yaml.YAML()
+ dts = '2011-10-02T16:45:00.930619+01:00'
+ d = yaml.load(dts)
+ print('d', repr(d), d._yaml)
+ yaml.dump(dict(x=d), sys.stdout)
+ print('----')
+ # dx = DateTime.fromisoformat(dts)
+ # print('dx', dx, repr(dx))
+ dd = DateTime(2011, 10, 2, 16, 45, 00, 930619, TimeZone(TimeDelta(hours=1, minutes=0), name='+01:00')) # NOQA
+ yaml.dump([dd], sys.stdout)
+ print('dd', dd, dd.tzinfo)
+ raise AssertionError()
diff --git a/_test/test_version.py b/_test/test_version.py
index a664e49..e508f32 100644
--- a/_test/test_version.py
+++ b/_test/test_version.py
@@ -164,3 +164,17 @@ def test_01(self) -> None:
def test_so_45681626(self) -> None:
# was not properly parsing
round_trip_load('{"in":{},"out":{}}')
+
+
+class TestVersionComparison:
+ def test_vc(self) -> None:
+ from ruamel.yaml.docinfo import Version
+
+ assert Version(1, 1) <= Version(2, 0)
+ assert Version(1, 1) <= Version(1, 2)
+ assert Version(1, 1) <= Version(1, 1)
+ assert Version(1, 3) == Version(1, 3)
+ assert Version(1, 2) > Version(1, 1)
+ assert Version(2, 0) > Version(1, 1)
+ assert Version(2, 0) >= Version(1, 1)
+ assert Version(1, 2) >= Version(1, 2)
diff --git a/anchor.py b/anchor.py
index 1eb1480..61119af 100644
--- a/anchor.py
+++ b/anchor.py
@@ -1,6 +1,8 @@
-# coding: utf-8
-from typing import Any, Dict, Optional, List, Union, Optional, Iterator # NOQA
+from __future__ import annotations
+
+if False: # MYPY
+ from typing import Any, Dict, Optional, List, Union, Optional, Iterator # NOQA
anchor_attrib = '_yaml_anchor'
diff --git a/comments.py b/comments.py
index 843b329..9a23135 100644
--- a/comments.py
+++ b/comments.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
"""
stuff to deal with comments and formatting on dict/list/ordereddict/set
@@ -18,7 +19,8 @@
from collections.abc import MutableSet, Sized, Set, Mapping
-from typing import Any, Dict, Optional, List, Union, Optional, Iterator # NOQA
+if False: # MYPY
+ from typing import Any, Dict, Optional, List, Union, Optional, Iterator # NOQA
# fmt: off
__all__ = ['CommentedSeq', 'CommentedKeySeq',
diff --git a/compat.py b/compat.py
index 9786fae..46d0c3b 100644
--- a/compat.py
+++ b/compat.py
@@ -1,4 +1,3 @@
-# coding: utf-8
from __future__ import annotations
@@ -7,18 +6,23 @@
import sys
import os
import io
-import traceback
from abc import abstractmethod
import collections.abc
+from ruamel.yaml.docinfo import Version # NOQA
# fmt: off
-from typing import Any, Dict, Optional, List, Union, BinaryIO, IO, Text, Tuple # NOQA
-from typing import Optional # NOQA
-try:
- from typing import SupportsIndex as SupportsIndex # in order to reexport for mypy
-except ImportError:
- SupportsIndex = int # type: ignore
+if False: # MYPY
+ from typing import Any, Dict, Optional, List, Union, BinaryIO, IO, Text, Tuple # NOQA
+ from typing import Optional # NOQA
+ try:
+ from typing import SupportsIndex as SupportsIndex # in order to reexport for mypy
+ except ImportError:
+ SupportsIndex = int # type: ignore
+
+ StreamType = Any
+ StreamTextType = StreamType
+ VersionType = Union[str , Tuple[int, int] , List[int] , Version , None]
# fmt: on
_DEFAULT_YAML_VERSION = (1, 2)
@@ -51,11 +55,6 @@ def insert(self, pos: int, key: Any, value: Any) -> None:
StringIO = io.StringIO
BytesIO = io.BytesIO
-StreamType = Any
-
-StreamTextType = StreamType
-from ruamel.yaml.docinfo import Version # NOQA
-VersionType = Union[str , Tuple[int, int] , List[int] , Version , None]
builtins_module = 'builtins'
@@ -117,6 +116,8 @@ def __init__(self, file_name: Any = None) -> None:
self._file_name = file_name
def __call__(self, *args: Any, **kw: Any) -> None:
+ import traceback
+
if not bool(_debug):
return
out = sys.stdout if self._file_name is None else open(self._file_name, 'a')
diff --git a/composer.py b/composer.py
index 3802d94..ca4031a 100644
--- a/composer.py
+++ b/composer.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
import warnings
@@ -17,7 +18,8 @@
)
from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode
-from typing import Any, Dict, Optional, List # NOQA
+if False: # MYPY
+ from typing import Any, Dict, Optional, List # NOQA
__all__ = ['Composer', 'ComposerError']
diff --git a/configobjwalker.py b/configobjwalker.py
index 28318f1..236666c 100644
--- a/configobjwalker.py
+++ b/configobjwalker.py
@@ -1,10 +1,12 @@
-# coding: utf-8
+
+from __future__ import annotations
import warnings
from ruamel.yaml.util import configobj_walker as new_configobj_walker
-from typing import Any
+if False: # MYPY
+ from typing import Any
def configobj_walker(cfg: Any) -> Any:
diff --git a/constructor.py b/constructor.py
index e4f6f16..a25ef20 100644
--- a/constructor.py
+++ b/constructor.py
@@ -1,7 +1,8 @@
-# coding: utf-8
+
+from __future__ import annotations
import datetime
-import base64
+from datetime import timedelta as TimeDelta
import binascii
import sys
import types
@@ -34,7 +35,8 @@
from ruamel.yaml.timestamp import TimeStamp
from ruamel.yaml.util import timestamp_regexp, create_timestamp
-from typing import Any, Dict, List, Set, Iterator, Union, Optional # NOQA
+if False: # MYPY
+ from typing import Any, Dict, List, Set, Iterator, Union, Optional # NOQA
__all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor',
@@ -511,6 +513,8 @@ def construct_yaml_float(self, node: Any) -> float:
return sign * float(value_s)
def construct_yaml_binary(self, node: Any) -> Any:
+ import base64
+
try:
value = self.construct_scalar(node).encode('ascii')
except UnicodeEncodeError as exc:
@@ -662,6 +666,8 @@ def construct_python_unicode(self, node: Any) -> Any:
return self.construct_scalar(node)
def construct_python_bytes(self, node: Any) -> Any:
+ import base64
+
try:
value = self.construct_scalar(node).encode('ascii')
except UnicodeEncodeError as exc:
@@ -1487,14 +1493,17 @@ def construct_yaml_object(self, node: Any, cls: Any) -> Any:
state = SafeConstructor.construct_mapping(self, node, deep=True)
data.__setstate__(state)
elif is_dataclass(data):
- mapping = SafeConstructor.construct_mapping(self, node)
+ mapping = SafeConstructor.construct_mapping(self, node, deep=True)
init_var_defaults = {}
for field in data.__dataclass_fields__.values():
# nprintf('field', field, field.default is MISSING,
# isinstance(field.type, InitVar))
# in 3.7, InitVar is a singleton
if (
- isinstance(field.type, InitVar) or field.type is InitVar
+ isinstance(field.type, InitVar)
+ or field.type is InitVar
+ # this following is for handling from __future__ import allocations
+ or (isinstance(field.type, str) and field.type.startswith('InitVar'))
) and field.default is not MISSING:
init_var_defaults[field.name] = field.default
for attr, value in mapping.items():
@@ -1676,20 +1685,21 @@ def construct_yaml_timestamp(
else:
return create_timestamp(**values)
# return SafeConstructor.construct_yaml_timestamp(self, node, values)
- dd = create_timestamp(**values) # this has delta applied
+ # print('>>>>>>>> here', values)
+ dd = create_timestamp(**values) # this has tzinfo
delta = None
if values['tz_sign']:
- tz_hour = int(values['tz_hour'])
+ hours = values['tz_hour']
+ tz_hour = int(hours)
minutes = values['tz_minute']
tz_minute = int(minutes) if minutes else 0
- delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute)
+ # ToDo: double work, replace with extraction from dd.tzinfo
+ delta = TimeDelta(hours=tz_hour, minutes=tz_minute)
if values['tz_sign'] == '-':
delta = -delta
- # should check for None and solve issue 366 should be tzinfo=delta)
- # isinstance(datetime.datetime.now, datetime.date) is true)
if isinstance(dd, datetime.datetime):
data = TimeStamp(
- dd.year, dd.month, dd.day, dd.hour, dd.minute, dd.second, dd.microsecond,
+ dd.year, dd.month, dd.day, dd.hour, dd.minute, dd.second, dd.microsecond, dd.tzinfo, # NOQA
)
else:
# ToDo: make this into a DateStamp?
diff --git a/cyaml.py b/cyaml.py
index 3f15ffc..54bd71e 100644
--- a/cyaml.py
+++ b/cyaml.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
from _ruamel_yaml import CParser, CEmitter # type: ignore
@@ -7,8 +8,9 @@
from ruamel.yaml.resolver import Resolver, BaseResolver
-from typing import Any, Union, Optional # NOQA
-from ruamel.yaml.compat import StreamTextType, StreamType, VersionType # NOQA
+if False: # MYPY
+ from typing import Any, Union, Optional # NOQA
+ from ruamel.yaml.compat import StreamTextType, StreamType, VersionType # NOQA
__all__ = ['CBaseLoader', 'CSafeLoader', 'CLoader', 'CBaseDumper', 'CSafeDumper', 'CDumper']
diff --git a/docinfo.py b/docinfo.py
index aec6ea7..1c9254b 100644
--- a/docinfo.py
+++ b/docinfo.py
@@ -15,17 +15,60 @@
- if provided to the dumper?
"""
-from typing import Optional, Tuple
-from dataclasses import dataclass, field, MISSING # NOQA
+if False: # MYPY
+ from typing import Optional, Tuple, Any
+# from dataclasses import dataclass, field, MISSING # NOQA
-@dataclass(order=True, frozen=True)
+
+# @dataclass(order=True, frozen=True)
class Version:
- major: int
- minor: int
+ # major: int
+ # minor: int
+ def __init__(self, major: int, minor: int) -> None:
+ self._major = major
+ self._minor = minor
+
+ @property
+ def major(self) -> int:
+ return self._major
+
+ @property
+ def minor(self) -> int:
+ return self._minor
+
+ def __eq__(self, v: Any) -> bool:
+ if not isinstance(v, Version):
+ return False
+ return self._major == v._major and self._minor == v._minor
+
+ def __lt__(self, v: Version) -> bool:
+ if self._major < v._major:
+ return True
+ if self._major > v._major:
+ return False
+ return self._minor < v._minor
- # def __repr__(self):
- # return f'Version("{self.major}.{self.minor}")'
+ def __le__(self, v: Version) -> bool:
+ if self._major < v._major:
+ return True
+ if self._major > v._major:
+ return False
+ return self._minor <= v._minor
+
+ def __gt__(self, v: Version) -> bool:
+ if self._major > v._major:
+ return True
+ if self._major < v._major:
+ return False
+ return self._minor > v._minor
+
+ def __ge__(self, v: Version) -> bool:
+ if self._major > v._major:
+ return True
+ if self._major < v._major:
+ return False
+ return self._minor >= v._minor
def version(
@@ -48,13 +91,24 @@ def version(
return Version(major, minor)
-@dataclass(frozen=True)
+# @dataclass(frozen=True)
class Tag:
- handle: str
- prefix: str
+ # handle: str
+ # prefix: str
+ def __init__(self, handle: str, prefix: str) -> None:
+ self._handle = handle
+ self._prefix = prefix
+
+ @property
+ def handle(self) -> str:
+ return self._handle
+
+ @property
+ def prefix(self) -> str:
+ return self._prefix
-@dataclass
+# @dataclass
class DocInfo:
"""
Store document information, can be used for analysis of a loaded YAML document
@@ -62,6 +116,15 @@ class DocInfo:
doc_version: from %YAML directive
tags: from %TAG directives in scanned order
"""
- requested_version: Optional[Version] = None
- doc_version: Optional[Version] = None
- tags: list[Tag] = field(default_factory=list)
+ # requested_version: Optional[Version] = None
+ # doc_version: Optional[Version] = None
+ # tags: list[Tag] = field(default_factory=list)
+ def __init__(
+ self,
+ requested_version: Optional[Version] = None,
+ doc_version: Optional[Version] = None,
+ tags: Optional[list[Tag]] = None,
+ ):
+ self.requested_version = requested_version
+ self.doc_version = doc_version
+ self.tags = [] if tags is None else tags
diff --git a/dumper.py b/dumper.py
index e6457a6..ece9429 100644
--- a/dumper.py
+++ b/dumper.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
from ruamel.yaml.emitter import Emitter
from ruamel.yaml.serializer import Serializer
@@ -10,8 +11,9 @@
)
from ruamel.yaml.resolver import Resolver, BaseResolver, VersionedResolver
-from typing import Any, Dict, List, Union, Optional # NOQA
-from ruamel.yaml.compat import StreamType, VersionType # NOQA
+if False: # MYPY
+ from typing import Any, Dict, List, Union, Optional # NOQA
+ from ruamel.yaml.compat import StreamType, VersionType # NOQA
__all__ = ['BaseDumper', 'SafeDumper', 'Dumper', 'RoundTripDumper']
diff --git a/emitter.py b/emitter.py
index 1d58be2..9352a16 100644
--- a/emitter.py
+++ b/emitter.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
# Emitter expects events obeying the following grammar:
# stream ::= STREAM-START document* STREAM-END
@@ -17,8 +18,9 @@
# fmt: on
-from typing import Any, Dict, List, Union, Text, Tuple, Optional # NOQA
-from ruamel.yaml.compat import StreamType # NOQA
+if False: # MYPY
+ from typing import Any, Dict, List, Union, Text, Tuple, Optional # NOQA
+ from ruamel.yaml.compat import StreamType # NOQA
__all__ = ['Emitter', 'EmitterError']
diff --git a/error.py b/error.py
index 4843fdb..30116d1 100644
--- a/error.py
+++ b/error.py
@@ -1,9 +1,11 @@
-# coding: utf-8
+
+from __future__ import annotations
import warnings
-import textwrap
+# import textwrap
-from typing import Any, Dict, Optional, List, Text # NOQA
+if False: # MYPY
+ from typing import Any, Dict, Optional, List, Text # NOQA
__all__ = [
@@ -133,7 +135,7 @@ def __init__(
# warn is ignored
def __str__(self) -> Any:
- lines: List[str] = []
+ lines: list[str] = []
if self.context is not None:
lines.append(self.context)
if self.context_mark is not None and (
@@ -148,11 +150,20 @@ def __str__(self) -> Any:
lines.append(self.problem)
if self.problem_mark is not None:
lines.append(str(self.problem_mark))
- if self.note is not None and self.note:
- note = textwrap.dedent(self.note)
- lines.append(note)
+ # if self.note is not None and self.note:
+ # note = textwrap.dedent(self.note)
+ # lines.append(note)
+ self.check_append(lines, self.note)
return '\n'.join(lines)
+ def check_append(self, lines: list[str], val: Optional[str]) -> None:
+ if val is None or not val:
+ return
+ import textwrap
+
+ note = textwrap.dedent(val)
+ lines.append(note)
+
class YAMLStreamError(Exception):
pass
@@ -195,14 +206,24 @@ def __str__(self) -> Any:
lines.append(self.problem)
if self.problem_mark is not None:
lines.append(str(self.problem_mark))
- if self.note is not None and self.note:
- note = textwrap.dedent(self.note)
- lines.append(note)
- if self.warn is not None and self.warn:
- warn = textwrap.dedent(self.warn)
- lines.append(warn)
+ # if self.note is not None and self.note:
+ # note = textwrap.dedent(self.note)
+ # lines.append(note)
+ self.check_append(lines, self.note)
+ # if self.warn is not None and self.warn:
+ # warn = textwrap.dedent(self.warn)
+ # lines.append(warn)
+ self.check_append(lines, self.warn)
return '\n'.join(lines)
+ def check_append(self, lines: list[str], val: Optional[str]) -> None:
+ if val is None or not val:
+ return
+ import textwrap
+
+ note = textwrap.dedent(val)
+ lines.append(note)
+
class ReusedAnchorWarning(YAMLWarning):
pass
@@ -288,10 +309,20 @@ def __str__(self) -> Any:
lines.append(self.problem)
if self.problem_mark is not None:
lines.append(str(self.problem_mark))
- if self.note is not None and self.note:
- note = textwrap.dedent(self.note)
- lines.append(note)
- if self.warn is not None and self.warn:
- warn = textwrap.dedent(self.warn)
- lines.append(warn)
+ # if self.note is not None and self.note:
+ # note = textwrap.dedent(self.note)
+ # lines.append(note)
+ self.check_append(lines, self.note)
+ # if self.warn is not None and self.warn:
+ # warn = textwrap.dedent(self.warn)
+ # lines.append(warn)
+ self.check_append(lines, self.warn)
return '\n'.join(lines)
+
+ def check_append(self, lines: list[str], val: Optional[str]) -> None:
+ if val is None or not val:
+ return
+ import textwrap
+
+ note = textwrap.dedent(val)
+ lines.append(note)
diff --git a/events.py b/events.py
index a570a0d..42af96c 100644
--- a/events.py
+++ b/events.py
@@ -1,8 +1,10 @@
-# coding: utf-8
+
+from __future__ import annotations
# Abstract classes.
-from typing import Any, Dict, Optional, List # NOQA
+if False: # MYPY
+ from typing import Any, Dict, Optional, List # NOQA
from ruamel.yaml.tag import Tag
SHOW_LINES = False
diff --git a/loader.py b/loader.py
index d6c708b..8406856 100644
--- a/loader.py
+++ b/loader.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
from ruamel.yaml.reader import Reader
from ruamel.yaml.scanner import Scanner, RoundTripScanner
@@ -12,8 +13,9 @@
)
from ruamel.yaml.resolver import VersionedResolver
-from typing import Any, Dict, List, Union, Optional # NOQA
-from ruamel.yaml.compat import StreamTextType, VersionType # NOQA
+if False: # MYPY
+ from typing import Any, Dict, List, Union, Optional # NOQA
+ from ruamel.yaml.compat import StreamTextType, VersionType # NOQA
__all__ = ['BaseLoader', 'SafeLoader', 'Loader', 'RoundTripLoader']
diff --git a/main.py b/main.py
index b80c1f5..acef4bd 100644
--- a/main.py
+++ b/main.py
@@ -1,4 +1,3 @@
-# coding: utf-8
from __future__ import annotations
@@ -36,10 +35,11 @@
from ruamel.yaml.comments import CommentedMap, CommentedSeq, C_PRE
from ruamel.yaml.docinfo import DocInfo, version, Version
-from typing import List, Set, Dict, Tuple, Union, Any, Callable, Optional, Text, Type # NOQA
-from types import TracebackType
-from ruamel.yaml.compat import StreamType, StreamTextType, VersionType # NOQA
-from pathlib import Path # NOQA
+if False: # MYPY
+ from typing import List, Set, Dict, Tuple, Union, Any, Callable, Optional, Text, Type # NOQA
+ from ruamel.yaml.compat import StreamType, StreamTextType, VersionType # NOQA
+ from types import TracebackType
+ from pathlib import Path
try:
from _ruamel_yaml import CParser, CEmitter # type: ignore
diff --git a/nodes.py b/nodes.py
index 1721049..f870167 100644
--- a/nodes.py
+++ b/nodes.py
@@ -1,8 +1,10 @@
-# coding: utf-8
+
+from __future__ import annotations
import sys
-from typing import Dict, Any, Text, Optional # NOQA
+if False: # MYPY
+ from typing import Dict, Any, Text, Optional # NOQA
from ruamel.yaml.tag import Tag
diff --git a/parser.py b/parser.py
index b031aa6..8f56195 100644
--- a/parser.py
+++ b/parser.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
# The following YAML grammar is LL(1) and is parsed by a recursive descent
# parser.
@@ -83,7 +84,8 @@
from ruamel.yaml.compat import nprint, nprintf # NOQA
from ruamel.yaml.tag import Tag
-from typing import Any, Dict, Optional, List, Optional # NOQA
+if False: # MYPY
+ from typing import Any, Dict, Optional, List, Optional # NOQA
__all__ = ['Parser', 'RoundTripParser', 'ParserError']
diff --git a/reader.py b/reader.py
index 3780a2c..ef0f777 100644
--- a/reader.py
+++ b/reader.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
# This module contains abstractions for the input stream. You don't have to
# looks further, there are no pretty code.
@@ -24,7 +25,8 @@
from ruamel.yaml.error import YAMLError, FileMark, StringMark, YAMLStreamError
from ruamel.yaml.util import RegExp
-from typing import Any, Dict, Optional, List, Union, Text, Tuple, Optional # NOQA
+if False: # MYPY
+ from typing import Any, Dict, Optional, List, Union, Text, Tuple, Optional # NOQA
# from ruamel.yaml.compat import StreamTextType # NOQA
__all__ = ['Reader', 'ReaderError']
diff --git a/representer.py b/representer.py
index 0d1ca12..e3f492e 100644
--- a/representer.py
+++ b/representer.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
from ruamel.yaml.error import * # NOQA
from ruamel.yaml.nodes import * # NOQA
@@ -35,7 +36,8 @@
import copyreg
import base64
-from typing import Dict, List, Any, Union, Text, Optional # NOQA
+if False: # MYPY
+ from typing import Dict, List, Any, Union, Text, Optional # NOQA
# fmt: off
__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer',
@@ -1024,13 +1026,16 @@ def represent_list(self, data: Any) -> SequenceNode:
def represent_datetime(self, data: Any) -> ScalarNode:
inter = 'T' if data._yaml['t'] else ' '
_yaml = data._yaml
- if _yaml['delta']:
+ if False and _yaml['delta']:
data += _yaml['delta']
value = data.isoformat(inter)
else:
- value = data.isoformat(inter)
- if _yaml['tz']:
+ value = data.isoformat(inter).strip()
+ if False and _yaml['tz']:
value += _yaml['tz']
+ if data.tzinfo and str(data.tzinfo):
+ if value[-6] in '+-':
+ value = value[:-6] + str(data.tzinfo)
return self.represent_scalar('tag:yaml.org,2002:timestamp', value)
def represent_tagged_scalar(self, data: Any) -> ScalarNode:
diff --git a/resolver.py b/resolver.py
index aa3ca11..d693b8e 100644
--- a/resolver.py
+++ b/resolver.py
@@ -1,9 +1,11 @@
-# coding: utf-8
+
+from __future__ import annotations
import re
-from typing import Any, Dict, List, Union, Text, Optional # NOQA
-from ruamel.yaml.compat import VersionType # NOQA
+if False: # MYPY
+ from typing import Any, Dict, List, Union, Text, Optional # NOQA
+ from ruamel.yaml.compat import VersionType # NOQA
from ruamel.yaml.tag import Tag
from ruamel.yaml.compat import _DEFAULT_YAML_VERSION # NOQA
diff --git a/scalarbool.py b/scalarbool.py
index 083d3cb..c38dd6b 100644
--- a/scalarbool.py
+++ b/scalarbool.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
"""
You cannot subclass bool, and this is necessary for round-tripping anchored
@@ -11,7 +12,8 @@
from ruamel.yaml.anchor import Anchor
-from typing import Text, Any, Dict, List # NOQA
+if False: # MYPY
+ from typing import Text, Any, Dict, List # NOQA
__all__ = ['ScalarBoolean']
diff --git a/scalarfloat.py b/scalarfloat.py
index 10b4c29..997cabc 100644
--- a/scalarfloat.py
+++ b/scalarfloat.py
@@ -1,9 +1,11 @@
-# coding: utf-8
+
+from __future__ import annotations
import sys
from ruamel.yaml.anchor import Anchor
-from typing import Text, Any, Dict, List # NOQA
+if False: # MYPY
+ from typing import Text, Any, Dict, List # NOQA
__all__ = ['ScalarFloat', 'ExponentialFloat', 'ExponentialCapsFloat']
diff --git a/scalarint.py b/scalarint.py
index af798b7..c9343d7 100644
--- a/scalarint.py
+++ b/scalarint.py
@@ -1,8 +1,10 @@
-# coding: utf-8
+
+from __future__ import annotations
from ruamel.yaml.anchor import Anchor
-from typing import Text, Any, Dict, List # NOQA
+if False: # MYPY
+ from typing import Text, Any, Dict, List # NOQA
__all__ = ['ScalarInt', 'BinaryInt', 'OctalInt', 'HexInt', 'HexCapsInt', 'DecimalInt']
diff --git a/scalarstring.py b/scalarstring.py
index 30f4fde..32d9f1c 100644
--- a/scalarstring.py
+++ b/scalarstring.py
@@ -1,9 +1,11 @@
-# coding: utf-8
+
+from __future__ import annotations
from ruamel.yaml.anchor import Anchor
-from typing import Text, Any, Dict, List # NOQA
-from ruamel.yaml.compat import SupportsIndex
+if False: # MYPY
+ from typing import Text, Any, Dict, List # NOQA
+ from ruamel.yaml.compat import SupportsIndex
__all__ = [
'ScalarString',
diff --git a/scanner.py b/scanner.py
index 65d9a77..650d544 100644
--- a/scanner.py
+++ b/scanner.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
# Scanner produces tokens of the following types:
# STREAM-START
@@ -28,13 +29,13 @@
# Read comments in the Scanner code for more details.
#
-import inspect
from ruamel.yaml.error import MarkedYAMLError, CommentMark # NOQA
from ruamel.yaml.tokens import * # NOQA
from ruamel.yaml.docinfo import Version, Tag # NOQA
-from ruamel.yaml.compat import check_anchorname_char, nprint, nprintf # NOQA
+from ruamel.yaml.compat import check_anchorname_char, _debug, nprint, nprintf # NOQA
-from typing import Any, Dict, Optional, List, Union, Text, Tuple # NOQA
+if False: # MYPY
+ from typing import Any, Dict, Optional, List, Union, Text, Tuple # NOQA
__all__ = ['Scanner', 'RoundTripScanner', 'ScannerError']
@@ -44,9 +45,9 @@
_SPACE_TAB = ' \t'
-def xprintf(*args: Any, **kw: Any) -> Any:
- return nprintf(*args, **kw)
- pass
+if _debug != 0:
+ def xprintf(*args: Any, **kw: Any) -> Any:
+ return nprintf(*args, **kw)
class ScannerError(MarkedYAMLError):
@@ -1983,17 +1984,23 @@ def __init__(self, value: Any, line: Any, column: Any) -> None:
self.line = line
self.column = column
self.used = ' '
- info = inspect.getframeinfo(inspect.stack()[3][0])
- self.function = info.function
- self.fline = info.lineno
- self.ufun = None
- self.uline = None
+ if _debug != 0:
+ import inspect
+
+ info = inspect.getframeinfo(inspect.stack()[3][0])
+ self.function = info.function
+ self.fline = info.lineno
+ self.ufun = None
+ self.uline = None
def set_used(self, v: Any = '+') -> None:
self.used = v
- info = inspect.getframeinfo(inspect.stack()[1][0])
- self.ufun = info.function # type: ignore
- self.uline = info.lineno # type: ignore
+ if _debug != 0:
+ import inspect
+
+ info = inspect.getframeinfo(inspect.stack()[1][0])
+ self.ufun = info.function # type: ignore
+ self.uline = info.lineno # type: ignore
def set_assigned(self) -> None:
self.used = '|'
@@ -2091,22 +2098,29 @@ def any_unprocessed(self) -> bool:
def unprocessed(self, use: Any = False) -> Any:
while len(self.unused) > 0:
- first = self.unused.pop(0) if use else self.unused[0]
- info = inspect.getframeinfo(inspect.stack()[1][0])
- xprintf('using', first, self.comments[first].value, info.function, info.lineno)
+ if _debug != 0:
+ import inspect
+
+ first = self.unused.pop(0) if use else self.unused[0]
+ info = inspect.getframeinfo(inspect.stack()[1][0])
+ xprintf('using', first, self.comments[first].value, info.function, info.lineno)
yield first, self.comments[first]
if use:
self.comments[first].set_used()
def assign_pre(self, token: Any) -> Any:
token_line = token.start_mark.line
- info = inspect.getframeinfo(inspect.stack()[1][0])
- xprintf('assign_pre', token_line, self.unused, info.function, info.lineno)
+ if _debug != 0:
+ import inspect
+
+ info = inspect.getframeinfo(inspect.stack()[1][0])
+ xprintf('assign_pre', token_line, self.unused, info.function, info.lineno)
gobbled = False
while self.unused and self.unused[0] < token_line:
gobbled = True
first = self.unused.pop(0)
- xprintf('assign_pre < ', first)
+ if _debug != 0:
+ xprintf('assign_pre < ', first)
self.comments[first].set_used()
token.add_comment_pre(first)
return gobbled
@@ -2123,7 +2137,8 @@ def assign_eol(self, tokens: Any) -> Any:
tokens[-idx], ValueToken,
):
idx += 1
- xprintf('idx1', idx)
+ if _debug != 0:
+ xprintf('idx1', idx)
if (
len(tokens) > idx
and isinstance(tokens[-idx], ScalarToken)
@@ -2137,13 +2152,15 @@ def assign_eol(self, tokens: Any) -> Any:
try:
eol_idx = self.unused.pop(0)
self.comments[eol_idx].set_used()
- xprintf('>>>>>a', idx, eol_idx, KEYCMNT)
+ if _debug != 0:
+ xprintf('>>>>>a', idx, eol_idx, KEYCMNT)
tokens[-idx].add_comment_eol(eol_idx, KEYCMNT)
except IndexError:
raise NotImplementedError
return
except IndexError:
- xprintf('IndexError1')
+ if _debug != 0:
+ xprintf('IndexError1')
pass
try:
if isinstance(tokens[-idx], ScalarToken) and isinstance(
@@ -2157,24 +2174,30 @@ def assign_eol(self, tokens: Any) -> Any:
raise NotImplementedError
return
except IndexError:
- xprintf('IndexError2')
+ if _debug != 0:
+ xprintf('IndexError2')
pass
for t in tokens:
xprintf('tt-', t)
- xprintf('not implemented EOL', type(tokens[-idx]))
+ if _debug != 0:
+ xprintf('not implemented EOL', type(tokens[-idx]))
import sys
sys.exit(0)
def assign_post(self, token: Any) -> Any:
token_line = token.start_mark.line
- info = inspect.getframeinfo(inspect.stack()[1][0])
- xprintf('assign_post', token_line, self.unused, info.function, info.lineno)
+ if _debug != 0:
+ import inspect
+
+ info = inspect.getframeinfo(inspect.stack()[1][0])
+ xprintf('assign_post', token_line, self.unused, info.function, info.lineno)
gobbled = False
while self.unused and self.unused[0] < token_line:
gobbled = True
first = self.unused.pop(0)
- xprintf('assign_post < ', first)
+ if _debug != 0:
+ xprintf('assign_post < ', first)
self.comments[first].set_used()
token.add_comment_post(first)
return gobbled
@@ -2222,11 +2245,12 @@ def need_more_tokens(self) -> bool:
if self.tokens[0].start_mark.line == self.tokens[-1].start_mark.line:
return True
if True:
- xprintf('-x--', len(self.tokens))
- for t in self.tokens:
- xprintf(t)
- # xprintf(self.comments.last())
- xprintf(self.comments.str_unprocessed()) # type: ignore
+ if _debug != 0:
+ xprintf('-x--', len(self.tokens))
+ for t in self.tokens:
+ xprintf(t)
+ # xprintf(self.comments.last())
+ xprintf(self.comments.str_unprocessed()) # type: ignore
self.comments.assign_pre(self.tokens[0]) # type: ignore
self.comments.assign_eol(self.tokens) # type: ignore
return False
diff --git a/serializer.py b/serializer.py
index 1ac46d2..90f7c11 100644
--- a/serializer.py
+++ b/serializer.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
from ruamel.yaml.error import YAMLError
from ruamel.yaml.compat import nprint, DBG_NODE, dbg, nprintf # NOQA
@@ -18,8 +19,9 @@
)
from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode
-from typing import Any, Dict, Union, Text, Optional # NOQA
-from ruamel.yaml.compat import VersionType # NOQA
+if False: # MYPY
+ from typing import Any, Dict, Union, Text, Optional # NOQA
+ from ruamel.yaml.compat import VersionType # NOQA
__all__ = ['Serializer', 'SerializerError']
diff --git a/tag.py b/tag.py
index 7ad23fe..9a4cad9 100644
--- a/tag.py
+++ b/tag.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
"""
In round-trip mode the original tag needs to be preserved, but the tag
@@ -10,7 +11,8 @@
only.
"""
-from typing import Any, Dict, Optional, List, Union, Optional, Iterator # NOQA
+if False: # MYPY
+ from typing import Any, Dict, Optional, List, Union, Optional, Iterator # NOQA
tag_attrib = '_yaml_tag'
diff --git a/timestamp.py b/timestamp.py
index 753dfc1..a9aad05 100644
--- a/timestamp.py
+++ b/timestamp.py
@@ -1,19 +1,21 @@
-# coding: utf-8
-import datetime
+from __future__ import annotations
+
import copy
+import datetime
# ToDo: at least on PY3 you could probably attach the tzinfo correctly to the object
# a more complete datetime might be used by safe loading as well
#
# add type information (iso8601, spaced)
-from typing import Any, Dict, Optional, List # NOQA
+if False: # MYPY
+ from typing import Any, Dict, Optional, List # NOQA
class TimeStamp(datetime.datetime):
def __init__(self, *args: Any, **kw: Any) -> None:
- self._yaml: Dict[Any, Any] = dict(t=False, tz=None, delta=0)
+ self._yaml: Dict[str, Any] = dict(t=False, tz=None, delta=0)
def __new__(cls, *args: Any, **kw: Any) -> Any: # datetime is immutable
return datetime.datetime.__new__(cls, *args, **kw)
diff --git a/tokens.py b/tokens.py
index 0c73dcf..700bd03 100644
--- a/tokens.py
+++ b/tokens.py
@@ -1,8 +1,10 @@
-# coding: utf-8
+
+from __future__ import annotations
from ruamel.yaml.compat import nprintf # NOQA
-from typing import Text, Any, Dict, Optional, List # NOQA
+if False: # MYPY
+ from typing import Text, Any, Dict, Optional, List # NOQA
from .error import StreamMark # NOQA
SHOW_LINES = True
diff --git a/util.py b/util.py
index b621ce0..17cb2d6 100644
--- a/util.py
+++ b/util.py
@@ -1,4 +1,5 @@
-# coding: utf-8
+
+from __future__ import annotations
"""
some helper functions that might be generally useful
@@ -9,8 +10,9 @@
import re
-from typing import Any, Dict, Optional, List, Text, Callable, Union # NOQA
-from .compat import StreamTextType # NOQA
+if False: # MYPY
+ from typing import Any, Dict, Optional, List, Text, Callable, Union # NOQA
+ from .compat import StreamTextType # NOQA
class LazyEval:
@@ -73,12 +75,12 @@ def create_timestamp(
tz_hour: Any,
tz_minute: Any,
) -> Union[datetime.datetime, datetime.date]:
- # create a timestamp from match against timestamp_regexp
+ # create a timestamp from matching against timestamp_regexp
MAX_FRAC = 999999
year = int(year)
month = int(month)
day = int(day)
- if not hour:
+ if hour is None:
return datetime.date(year, month, day)
hour = int(hour)
minute = int(minute)
@@ -97,16 +99,20 @@ def create_timestamp(
fraction = frac
else:
fraction = 0
+ tzinfo = None
delta = None
if tz_sign:
tz_hour = int(tz_hour)
tz_minute = int(tz_minute) if tz_minute else 0
- delta = datetime.timedelta(
- hours=tz_hour, minutes=tz_minute, seconds=1 if frac > MAX_FRAC else 0,
+ td = datetime.timedelta(
+ hours=tz_hour, minutes=tz_minute,
)
if tz_sign == '-':
- delta = -delta
- elif frac > MAX_FRAC:
+ td = -td
+ tzinfo = datetime.timezone(td, name=tz)
+ elif tz == 'Z':
+ tzinfo = datetime.timezone(datetime.timedelta(hours=0), name=tz)
+ if frac > MAX_FRAC:
delta = -datetime.timedelta(seconds=1)
# should do something else instead (or hook this up to the preceding if statement
# in reverse
@@ -116,7 +122,7 @@ def create_timestamp(
# datetime.timezone.utc)
# the above is not good enough though, should provide tzinfo. In Python3 that is easily
# doable drop that kind of support for Python2 as it has not native tzinfo
- data = datetime.datetime(year, month, day, hour, minute, second, fraction)
+ data = datetime.datetime(year, month, day, hour, minute, second, fraction, tzinfo)
if delta:
data -= delta
return data