ialdev-core 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. iad/core/__init__.py +9 -0
  2. iad/core/array.py +1961 -0
  3. iad/core/binary.py +377 -0
  4. iad/core/cache.py +903 -0
  5. iad/core/codetools.py +203 -0
  6. iad/core/datatools.py +671 -0
  7. iad/core/docs/locators.ipynb +754 -0
  8. iad/core/dotstyle.py +99 -0
  9. iad/core/env.py +271 -0
  10. iad/core/events.py +650 -0
  11. iad/core/filesproc.py +1046 -0
  12. iad/core/fnctools.py +390 -0
  13. iad/core/label.py +240 -0
  14. iad/core/logs.py +182 -0
  15. iad/core/nptools.py +449 -0
  16. iad/core/one_dark.puml +881 -0
  17. iad/core/param/__init__.py +17 -0
  18. iad/core/param/confargparse.py +55 -0
  19. iad/core/param/paramaze.py +339 -0
  20. iad/core/param/tbox.py +277 -0
  21. iad/core/paths.py +563 -0
  22. iad/core/pdtools.py +2570 -0
  23. iad/core/pydantools/__init__.py +5 -0
  24. iad/core/pydantools/fixed_pydantic_yaml/__init__.py +32 -0
  25. iad/core/pydantools/fixed_pydantic_yaml/compat/__init__.py +0 -0
  26. iad/core/pydantools/fixed_pydantic_yaml/compat/hacks.py +76 -0
  27. iad/core/pydantools/fixed_pydantic_yaml/compat/old_enums.py +37 -0
  28. iad/core/pydantools/fixed_pydantic_yaml/compat/representers.py +92 -0
  29. iad/core/pydantools/fixed_pydantic_yaml/compat/types.py +122 -0
  30. iad/core/pydantools/fixed_pydantic_yaml/compat/yaml_lib.py +104 -0
  31. iad/core/pydantools/fixed_pydantic_yaml/ext/__init__.py +1 -0
  32. iad/core/pydantools/fixed_pydantic_yaml/ext/semver.py +152 -0
  33. iad/core/pydantools/fixed_pydantic_yaml/ext/versioned_model.py +113 -0
  34. iad/core/pydantools/fixed_pydantic_yaml/main.py +30 -0
  35. iad/core/pydantools/fixed_pydantic_yaml/mixin.py +281 -0
  36. iad/core/pydantools/fixed_pydantic_yaml/model.py +20 -0
  37. iad/core/pydantools/fixed_pydantic_yaml/py.typed +1 -0
  38. iad/core/pydantools/fixed_pydantic_yaml/version.py +1 -0
  39. iad/core/pydantools/models.py +560 -0
  40. iad/core/regexp.py +348 -0
  41. iad/core/short.py +308 -0
  42. iad/core/strings.py +635 -0
  43. iad/core/unc_panda.py +270 -0
  44. iad/core/units.py +58 -0
  45. iad/core/wrap.py +420 -0
  46. ialdev_core-0.1.0.dist-info/METADATA +73 -0
  47. ialdev_core-0.1.0.dist-info/RECORD +48 -0
  48. ialdev_core-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,5 @@
1
+ """Pydantic + YAML model utilities (moved from ``algutils.param``)."""
2
+
3
+ from iad.core.pydantools.models import YamlModel, make_yml_model_format, model_arguments
4
+
5
+ __all__ = ["YamlModel", "make_yml_model_format", "model_arguments"]
@@ -0,0 +1,32 @@
1
+ """YAML-enabled Pydantic models."""
2
+
3
+ from .version import __version__
4
+
5
+
6
+ __all__ = [
7
+ "__version__",
8
+ "SemVer",
9
+ "yaml",
10
+ "VersionedYamlModel",
11
+ "YamlEnum", # deprecated class
12
+ "YamlInt",
13
+ "YamlIntEnum",
14
+ "YamlModel",
15
+ "YamlModelMixin",
16
+ "YamlModelMixinConfig",
17
+ "YamlStr",
18
+ "YamlStrEnum",
19
+ ]
20
+ from .main import (
21
+ SemVer,
22
+ VersionedYamlModel,
23
+ YamlEnum,
24
+ YamlInt,
25
+ YamlIntEnum,
26
+ YamlModel,
27
+ YamlModelMixin,
28
+ YamlModelMixinConfig,
29
+ YamlStr,
30
+ YamlStrEnum,
31
+ yaml,
32
+ )
@@ -0,0 +1,76 @@
1
+ """Module for some hacks that are needed to work around the YAML library interfaces.
2
+
3
+ Mainy, this defines "safe" representers for common types that Pydantic is able to parse.
4
+ """
5
+
6
+ __all__ = ["inject_all"]
7
+
8
+ from typing import List, Type
9
+
10
+ from .representers import register_str_like, register_int_like
11
+
12
+
13
+ def get_str_like_types() -> List[Type]:
14
+ """Returns many string-like types from stdlib and Pydantic."""
15
+
16
+ # flake8: noqa
17
+
18
+ from uuid import UUID
19
+ from pathlib import (
20
+ Path,
21
+ PosixPath,
22
+ PurePath,
23
+ PurePosixPath,
24
+ PureWindowsPath,
25
+ WindowsPath,
26
+ )
27
+
28
+ from pydantic.v1 import types, networks
29
+
30
+ def _chk(x) -> bool:
31
+ try:
32
+ return issubclass(x, (str, Path, PurePath, UUID))
33
+ except Exception:
34
+ return False
35
+
36
+ # Get most candidates
37
+ candidates = (
38
+ list(locals().values())
39
+ + [getattr(types, v) for v in types.__all__]
40
+ + [getattr(networks, v) for v in networks.__all__]
41
+ )
42
+ str_like = [v for v in candidates if _chk(v)]
43
+
44
+ # SecretStr, SecretBytes are stringified as "**********" by Pydantic
45
+ str_like += [types.SecretStr, types.SecretBytes]
46
+ return str_like
47
+
48
+
49
+ def get_int_like_types() -> List[Type]:
50
+ """Returns many int-like types from stdlib and Pydantic."""
51
+
52
+ from pydantic.v1 import types
53
+
54
+ def _chk(x) -> bool:
55
+ try:
56
+ return issubclass(x, (int))
57
+ except Exception:
58
+ return False
59
+
60
+ candidates = list(locals().values()) + [getattr(types, v) for v in types.__all__]
61
+ int_like = [v for v in candidates if _chk(v)]
62
+ return int_like
63
+
64
+
65
+ def inject_all():
66
+ """Injects all necessary "hacks" into the namespace.
67
+
68
+ What this currently does:
69
+ - Registers many str-like and int-like types (from Pydantic and the standard
70
+ library) to be representable in YAML.
71
+ """
72
+ # Add representers for string-like and int-like values.
73
+ for cls in get_str_like_types():
74
+ register_str_like(cls, method=str)
75
+ for cls in get_int_like_types():
76
+ register_int_like(cls, method=int)
@@ -0,0 +1,37 @@
1
+ """Old class for YamlEnum that is now deprecated (and marked as such)."""
2
+
3
+ from enum import Enum
4
+ from inspect import isabstract
5
+
6
+ from deprecated import deprecated
7
+
8
+ from .representers import register_str_like
9
+
10
+ __all__ = ["YamlEnum"]
11
+
12
+
13
+ @deprecated(version="0.5.0", reason="Use the `YamlStrEnum` class instead.")
14
+ class YamlEnum(Enum):
15
+ """This class is DEPRECATED, please use `pydantic_yaml.YamlStrEnum` instead.
16
+
17
+ Enumeration that serializes as the proper underlying object type.
18
+
19
+ You can use this instead of `enum.Enum`, for example:
20
+ class MyEnum(str, YamlEnum):
21
+ val1 = "Value 1"
22
+ val2 = "Value 2"
23
+
24
+ Note
25
+ ----
26
+ This is actually a lie, it only supports `str` enums for now. Oops.
27
+ """
28
+
29
+ def __init_subclass__(cls):
30
+ if not isabstract(cls):
31
+ register_str_like(cls)
32
+
33
+ def __repr__(self) -> str:
34
+ return repr(self.value)
35
+
36
+ def __str__(self) -> str:
37
+ return str(self.value)
@@ -0,0 +1,92 @@
1
+ """Representers for dumping common objects to YAML."""
2
+
3
+ from functools import partial
4
+ from typing import Any, Callable, TypeVar
5
+
6
+ from .yaml_lib import yaml, dumper_classes, representer_classes
7
+
8
+ CType = TypeVar("CType")
9
+
10
+ __all__ = ["register_str_like", "register_int_like"]
11
+
12
+
13
+ def dump_as_str(dumper: yaml.Dumper, data: Any, method: Callable[[Any], str] = str):
14
+ """Represents an object as a string in YAML.
15
+
16
+ Parameters
17
+ ----------
18
+ dumper : yaml.Dumper
19
+ The dumper instance, such as `yaml.SafeDumper()`.
20
+ data
21
+ The object to dump. Ideally this is string-like.
22
+ method : callable
23
+ A method that converts `data` to a string. Default is `str`.
24
+
25
+ Returns
26
+ -------
27
+ node
28
+ The YAML internal node representation.
29
+ """
30
+ return dumper.represent_str(method(data))
31
+
32
+
33
+ def dump_as_int(dumper: yaml.Dumper, data: Any, method: Callable[[Any], int] = int):
34
+ """Represents an object as an integer in YAML.
35
+
36
+ Parameters
37
+ ----------
38
+ dumper : yaml.Dumper
39
+ The dumper instance, such as `yaml.SafeDumper()`.
40
+ data
41
+ The object to dump. Ideally this is int-like.
42
+ method : callable
43
+ A method that converts `data` to an integer. Default is `int`.
44
+
45
+ Returns
46
+ -------
47
+ node
48
+ The YAML internal node representation.
49
+ """
50
+ return dumper.represent_int(method(data))
51
+
52
+
53
+ def register_str_like(cls: CType, method: Callable[[Any], str] = str) -> CType:
54
+ """Registers `cls` to be dumped to YAML as a string.
55
+
56
+ Parameters
57
+ ----------
58
+ cls : Type
59
+ The class to represent.
60
+ method : callable
61
+ A method that converts objects of type `cls` to a string.
62
+ Default is `str`.
63
+
64
+ Returns
65
+ -------
66
+ cls
67
+ This is the same as the input `cls`.
68
+ """
69
+ for x_cls in dumper_classes + representer_classes:
70
+ x_cls.add_representer(cls, partial(dump_as_str, method=method))
71
+ return cls
72
+
73
+
74
+ def register_int_like(cls: CType, method: Callable[[Any], int] = int) -> CType:
75
+ """Registers `cls` to be dumped to YAML as an integer.
76
+
77
+ Parameters
78
+ ----------
79
+ cls : Type
80
+ The class to represent.
81
+ method : callable
82
+ A method that converts objects of type `cls` to an integer.
83
+ Default is `int`.
84
+
85
+ Returns
86
+ -------
87
+ cls
88
+ This is the same as the input `cls`.
89
+ """
90
+ for x_cls in dumper_classes + representer_classes:
91
+ x_cls.add_representer(cls, partial(dump_as_int, method=method))
92
+ return cls
@@ -0,0 +1,122 @@
1
+ """Defines useful types for building your own YAML-compatible types."""
2
+
3
+ from enum import Enum
4
+ from inspect import isabstract
5
+ from typing import Dict, Tuple, Union
6
+
7
+ from .representers import register_int_like, register_str_like
8
+ from .yaml_lib import yaml_safe_dump
9
+
10
+ __all__ = ["YamlInt", "YamlIntEnum", "YamlStr", "YamlStrEnum"]
11
+
12
+
13
+ class YamlStr(str):
14
+ """A `str` subclass that serializes to YAML as an integer."""
15
+
16
+ def __init_subclass__(cls):
17
+ """Adds a representer when defining a subclass."""
18
+ res = super().__init_subclass__()
19
+ if not isabstract(cls):
20
+ register_str_like(cls, method=cls._to_yaml_str)
21
+ return res
22
+
23
+ @classmethod
24
+ def _to_yaml_str(cls, v) -> str:
25
+ """Represent a value of this type as a string."""
26
+ return str(v)
27
+
28
+ def __str__(self) -> str:
29
+ """This just returns the wrapped string."""
30
+ return super().__str__()
31
+
32
+ def __repr__(self) -> str:
33
+ """Repr string that keeps the class type. You may redefine this."""
34
+ return type(self).__qualname__ + "(" + super().__repr__() + ")"
35
+
36
+ def __getnewargs__(self) -> Tuple[str]:
37
+ """Implementation to prevent Enum from sabotaging us.
38
+
39
+ I'm not joking! See `_make_class_unpicklable()` from `enum.py`
40
+ """
41
+ return super().__getnewargs__()
42
+
43
+
44
+ class YamlInt(int):
45
+ """An `int` subclass that serializes to YAML as an integer."""
46
+
47
+ def __init_subclass__(cls):
48
+ """Adds a representer when defining a subclass."""
49
+ res = super().__init_subclass__()
50
+ if not isabstract(cls):
51
+ register_int_like(cls, method=cls._to_yaml_int)
52
+ return res
53
+
54
+ @classmethod
55
+ def _to_yaml_int(cls, v) -> int:
56
+ """Represent a value of this type as an integer."""
57
+ return int(v)
58
+
59
+ def __int__(self) -> int:
60
+ return super().__int__()
61
+
62
+ def __str__(self) -> str:
63
+ """This returns the wrapped int."""
64
+ return super().__str__()
65
+
66
+ def __repr__(self) -> str:
67
+ """Repr string that keeps the class type. You may redefine this."""
68
+ return type(self).__qualname__ + "(" + super().__repr__() + ")"
69
+
70
+ def __getnewargs__(self) -> Tuple[int]:
71
+ """Implementation to prevent Enum from sabotaging us.
72
+
73
+ I'm not joking! See `_make_class_unpicklable()` from `enum.py`
74
+ """
75
+ return super().__getnewargs__()
76
+
77
+
78
+ class YamlStrEnum(YamlStr, Enum):
79
+ """String-valued enum that serializes to YAML directly as a string.
80
+
81
+ This also checks that your enum can properly be serialized.
82
+ """
83
+
84
+ def __init_subclass__(cls):
85
+ res = super().__init_subclass__()
86
+ if not isabstract(cls):
87
+ vals: Dict[str, cls] = dict(cls.__members__)
88
+ yaml_safe_dump(vals)
89
+ return res
90
+
91
+ def __str__(self) -> str:
92
+ """This returns the wrapped string."""
93
+ return str.__str__(self)
94
+
95
+ def __repr__(self) -> str:
96
+ """Repr string that keeps the class type. You may redefine this."""
97
+ return type(self).__qualname__ + "(" + str.__repr__(self) + ")"
98
+
99
+
100
+ class YamlIntEnum(YamlInt, Enum):
101
+ """Integer-valued enum that serializes to YAML directly as an integer.
102
+
103
+ This also checks that your enum can properly be serialized.
104
+ """
105
+
106
+ def __init_subclass__(cls):
107
+ res = super().__init_subclass__()
108
+ if not isabstract(cls):
109
+ vals: Dict[str, cls] = dict(cls.__members__)
110
+ yaml_safe_dump(vals)
111
+ return res
112
+
113
+
114
+ if __name__ == "__main__":
115
+
116
+ class A(YamlStrEnum):
117
+ a = "a"
118
+ b = "b"
119
+
120
+ class X(YamlIntEnum):
121
+ x1 = 1
122
+ x2 = 2
@@ -0,0 +1,104 @@
1
+ """Imports an installed YAML library."""
2
+
3
+ # flake8: noqa
4
+
5
+ from typing import Any, Optional
6
+ from io import BytesIO, StringIO, IOBase
7
+
8
+
9
+ try:
10
+ import ruamel.yaml as yaml # type: ignore
11
+
12
+ # ruamel.yaml doesn't have type annotations
13
+
14
+ if yaml.__version__ < "0.15.0":
15
+ __yaml_lib__ = "ruamel-old"
16
+ else:
17
+ __yaml_lib__ = "ruamel-new"
18
+ except ImportError:
19
+ try:
20
+ import yaml # type: ignore
21
+
22
+ __yaml_lib__ = "pyyaml"
23
+ except ImportError:
24
+ raise ImportError(
25
+ "Could not import ruamel.yaml or pyyaml, "
26
+ "please install at least one of them."
27
+ )
28
+
29
+ dumper_classes = []
30
+ for _fld in dir(yaml):
31
+ try:
32
+ if "Dumper" in _fld:
33
+ _obj = getattr(yaml, _fld)
34
+ dumper_classes.append(_obj)
35
+ except Exception:
36
+ pass
37
+
38
+ representer_classes = []
39
+ for _fld in dir(yaml):
40
+ try:
41
+ if "Representer" in _fld:
42
+ _obj = getattr(yaml, _fld)
43
+ representer_classes.append(_obj)
44
+ except Exception:
45
+ pass
46
+
47
+
48
+ def yaml_safe_load(stream) -> Any:
49
+ """Wrapper around YAML library loader."""
50
+ if __yaml_lib__ in ["ruamel-old", "pyyaml"]:
51
+ return yaml.safe_load(stream)
52
+ # Fixing deprecation warning in new ruamel.yaml versions
53
+ assert __yaml_lib__ == "ruamel-new"
54
+ ruamel_obj = yaml.YAML(typ="safe", pure=True)
55
+ if isinstance(stream, str):
56
+ return ruamel_obj.load(StringIO(stream))
57
+ elif isinstance(stream, bytes):
58
+ return ruamel_obj.load(BytesIO(stream))
59
+ # we hope it's a stream, but don't enforce it
60
+ return ruamel_obj.load(stream)
61
+
62
+
63
+ def yaml_safe_dump(
64
+ data: Any, stream=None, *, sort_keys: bool = False, **kwds
65
+ ) -> Optional[Any]:
66
+ """Wrapper around YAML library dumper.
67
+
68
+ Parameters
69
+ ----------
70
+ data
71
+ The data you want to dump, typically a mapping (dict).
72
+ stream
73
+ The stream to dump to. By default (if no stream is given, i.e. None),
74
+ this will instead dump to a text stream.
75
+ sort_keys : bool
76
+ Whether to sort the keys of mappings before dumping.
77
+ Default value is False, rather than True, which is the YAML default.
78
+ This is because Pydantic configs are easier to read if dumped in the
79
+ same order as they are defined.
80
+ kwds
81
+ Other keyword arguments to set on the YAML dumper instance.
82
+ """
83
+ if __yaml_lib__ in ["ruamel-old", "pyyaml"]:
84
+ return yaml.safe_dump(data, stream=stream, sort_keys=sort_keys, **kwds)
85
+ # Fixing deprecation warning in new ruamel.yaml versions
86
+ assert __yaml_lib__ == "ruamel-new"
87
+ ruamel_obj = yaml.YAML(typ="safe", pure=True)
88
+ ruamel_obj.sort_base_mapping_type_on_output = sort_keys # type: ignore
89
+ # Hacking some options that aren't available
90
+ for kw in ["encoding", "default_flow_style", "default_style", "indent"]:
91
+ if kw in kwds:
92
+ setattr(ruamel_obj, kw, kwds[kw])
93
+
94
+ if stream is None:
95
+ text_stream = StringIO()
96
+ ruamel_obj.dump(data, stream=text_stream)
97
+ text_stream.seek(0) # otherwise we always get ''
98
+ return text_stream.read()
99
+ else:
100
+ ruamel_obj.dump(data, stream=stream)
101
+ return None
102
+
103
+
104
+ __all__ = ["yaml_safe_dump", "yaml_safe_load", "yaml", "__yaml_lib__"]
@@ -0,0 +1 @@
1
+ """Additional extension types that may be useful specifically for YAML files."""
@@ -0,0 +1,152 @@
1
+ from functools import wraps
2
+ from typing import Any, Callable, Optional, Union, no_type_check
3
+
4
+ from pydantic.v1 import errors
5
+ from semver import VersionInfo
6
+
7
+ from ..compat.types import YamlStr
8
+
9
+ __all__ = ["SemVer"]
10
+
11
+
12
+ Comparator = Callable[["SemVer", Any], bool]
13
+
14
+
15
+ def _comparator(operator: Comparator) -> Comparator:
16
+ """Wrap a Version binary op method in a type-check."""
17
+
18
+ @wraps(operator)
19
+ def wrapper(self: "SemVer", other: Any) -> bool:
20
+ if not isinstance(other, SemVer):
21
+ try:
22
+ other = SemVer(other)
23
+ except Exception:
24
+ return NotImplemented
25
+ return operator(self, other)
26
+
27
+ return wrapper
28
+
29
+
30
+ class SemVer(YamlStr): # want to inherit from VersionInfo, but metaclass conflict
31
+ """Semantic Version string for Pydantic.
32
+
33
+ Depends on `semver` versions 2 or 3. Look here for how to fix the version:
34
+ https://python-semver.readthedocs.io/en/3.0.0-dev.2/install.html#release-policy
35
+ """
36
+
37
+ allow_build: bool = True
38
+ allow_prerelease: bool = True
39
+
40
+ __slots__ = ["_info"]
41
+
42
+ @no_type_check
43
+ def __new__(cls, version: Optional[str], **kwargs) -> object:
44
+ return YamlStr.__new__(cls, cls.parse(**kwargs) if version is None else version)
45
+
46
+ def __init__(self, version: str):
47
+ str.__init__(version)
48
+ self._info = VersionInfo.parse(version)
49
+
50
+ @classmethod
51
+ def parse(
52
+ self,
53
+ major: int,
54
+ minor: int = 0,
55
+ patch: int = 0,
56
+ prerelease: Optional[str] = None,
57
+ build: Optional[str] = None,
58
+ ) -> str:
59
+ return str(VersionInfo(major, minor, patch, prerelease, build))
60
+
61
+ @classmethod
62
+ def __get_validators__(cls):
63
+ yield cls.validate
64
+
65
+ @classmethod
66
+ def validate(cls, value: Union[str, "SemVer"]) -> "SemVer":
67
+ vi = VersionInfo.parse(value)
68
+ if not cls.allow_build and (vi.build is None):
69
+ raise errors.NotNoneError()
70
+ if not cls.allow_prerelease and (vi.prerelease is None):
71
+ raise errors.NotNoneError()
72
+ return cls(value)
73
+
74
+ @property
75
+ def info(self) -> VersionInfo:
76
+ return self._info
77
+
78
+ @info.setter
79
+ def info(self, value):
80
+ raise AttributeError("attribute 'info' is readonly")
81
+
82
+ @property
83
+ def major(self) -> int:
84
+ """The major part of a version (read-only)."""
85
+ return self._info.major
86
+
87
+ @major.setter
88
+ def major(self, value):
89
+ raise AttributeError("attribute 'major' is readonly")
90
+
91
+ @property
92
+ def minor(self) -> int:
93
+ """The minor part of a version (read-only)."""
94
+ return self._info.minor
95
+
96
+ @minor.setter
97
+ def minor(self, value):
98
+ raise AttributeError("attribute 'minor' is readonly")
99
+
100
+ @property
101
+ def patch(self) -> int:
102
+ """The patch part of a version (read-only)."""
103
+ return self._info.patch
104
+
105
+ @patch.setter
106
+ def patch(self, value):
107
+ raise AttributeError("attribute 'patch' is readonly")
108
+
109
+ @property
110
+ def prerelease(self) -> Optional[str]:
111
+ """The prerelease part of a version (read-only)."""
112
+ return self._info.prerelease
113
+
114
+ @prerelease.setter
115
+ def prerelease(self, value):
116
+ raise AttributeError("attribute 'prerelease' is readonly")
117
+
118
+ @property
119
+ def build(self) -> Optional[str]:
120
+ """The build part of a version (read-only)."""
121
+ return self._info.build
122
+
123
+ @build.setter
124
+ def build(self, value):
125
+ raise AttributeError("attribute 'build' is readonly")
126
+
127
+ def __hash__(self) -> int:
128
+ return super.__hash__(self) # use string hashing
129
+
130
+ @_comparator
131
+ def __eq__(self, other: "SemVer"):
132
+ return self._info == other._info
133
+
134
+ @_comparator
135
+ def __ne__(self, other: "SemVer"):
136
+ return self._info != other._info
137
+
138
+ @_comparator
139
+ def __lt__(self, other: "SemVer"):
140
+ return self._info < other._info
141
+
142
+ @_comparator
143
+ def __le__(self, other: "SemVer"):
144
+ return self._info <= other._info
145
+
146
+ @_comparator
147
+ def __gt__(self, other: "SemVer"):
148
+ return self._info > other._info
149
+
150
+ @_comparator
151
+ def __ge__(self, other: "SemVer"):
152
+ return self._info >= other._info