From 5d9b2a8290900f2cc07329b39746fd23448de9ec Mon Sep 17 00:00:00 2001 From: Brad Cowie Date: Thu, 22 Feb 2024 15:40:54 +1300 Subject: [PATCH] Import ruamel.yaml 0.18.6 source. --- .hgtags | 1 + .readthedocs.yaml | 3 +- CHANGES | 7 ++++ LICENSE | 2 +- README.md | 13 +++++- __init__.py | 9 ++-- _doc/_static/pypi.svg | 2 +- _test/test_dataclass.py | 45 ++++++++++++++++++++ _test/test_datetime.py | 33 +++++++++++++++ _test/test_version.py | 14 +++++++ anchor.py | 6 ++- comments.py | 6 ++- compat.py | 27 ++++++------ composer.py | 6 ++- configobjwalker.py | 6 ++- constructor.py | 32 +++++++++----- cyaml.py | 8 ++-- docinfo.py | 91 +++++++++++++++++++++++++++++++++------- dumper.py | 8 ++-- emitter.py | 8 ++-- error.py | 69 ++++++++++++++++++++++--------- events.py | 6 ++- loader.py | 8 ++-- main.py | 10 ++--- nodes.py | 6 ++- parser.py | 6 ++- reader.py | 6 ++- representer.py | 15 ++++--- resolver.py | 8 ++-- scalarbool.py | 6 ++- scalarfloat.py | 6 ++- scalarint.py | 6 ++- scalarstring.py | 8 ++-- scanner.py | 92 ++++++++++++++++++++++++++--------------- serializer.py | 8 ++-- tag.py | 6 ++- timestamp.py | 10 +++-- tokens.py | 6 ++- util.py | 26 +++++++----- 39 files changed, 464 insertions(+), 171 deletions(-) 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. - + - + @@ -19,6 +20,7 @@
version0.18.50.18.6
updated2023-11-032024-02-07
documentation https://yaml.readthedocs.io
+ 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 @@ - pypipypi0.18.50.18.5 + pypipypi0.18.60.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