Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2.2.0 #20

Merged
merged 5 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions docs/pages/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1013,3 +1013,30 @@ m = MyModel.model_validate(
) # does not raise an error
```


## `Version` Objects

```python
class Version(pydantic.RootModel[str])
```

A version string in the format of MAJOR.MINOR.PATCH[-(alpha|beta|rc).N]


##### `as_tag`

```python
def as_tag() -> str
```

Return the version string as a tag, i.e. vMAJOR.MINOR.PATCH...


##### `as_identifier`

```python
def as_identifier() -> str
```

Return the version string as a number, i.e. MAJOR.MINOR.PATCH...

933 changes: 493 additions & 440 deletions pdm.lock

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,20 @@ documentation = "https://tum-esm-utils.netlify.app"
[project.optional-dependencies]
dev = [
"pytest>=8.1.1",
"types-psutil>=5.9.5.20240316",
"types-requests>=2.31.0.20240406",
"pydocstyle>=6.3.0",
"yapf>=0.40.2",
"mypy>=1.9.0",
"pydoc-markdown>=4.8.2",
"matplotlib>=3.9.1.post1",
"numpy>=2.0.1",
"tailwind-colors>=1.2.1",
"polars>=1.4.1",
"types-psutil>=5.9.5.20240316",
"types-requests>=2.31.0.20240406",
"types-pytz>=2024.1.0.20240203",
]
plotting = ["matplotlib>=3.8.4", "numpy>=1.26.4", "tailwind-colors>=1.2.1"]
polars = ["polars>=0.20.20"]
plotting = ["matplotlib>=3.9.1.post1", "numpy>=2.0.1", "tailwind-colors>=1.2.1"]
polars = ["polars>=1.4.1"]

[build-system]
requires = ["pdm-backend"]
Expand Down
53 changes: 50 additions & 3 deletions tests/test_validators.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import pydantic
import tum_esm_utils
from tum_esm_utils.validators import StrictFilePath, StrictDirectoryPath, Version


def test_strict_path_validators() -> None:
class Config(pydantic.BaseModel):
f: tum_esm_utils.validators.StrictFilePath
d: tum_esm_utils.validators.StrictDirectoryPath
f: StrictFilePath
d: StrictDirectoryPath

test_file = tum_esm_utils.files.rel_to_abs_path("../pyproject.toml")
test_dir = tum_esm_utils.files.rel_to_abs_path("..")

c = Config(f=test_file, d=test_dir)
assert isinstance(c.f, tum_esm_utils.validators.StrictFilePath)
assert isinstance(c.f, StrictFilePath)
assert isinstance(c.f.root, str)
assert set(c.model_dump().keys()) == {"f", "d"}

Expand All @@ -34,3 +35,49 @@ class Config(pydantic.BaseModel):
{"f": "someinvalidfile", "d": "someinvaliddir"},
context={"ignore-path-existence": True},
)


def test_version_validator() -> None:
for valid_string in [
"0.0.0", "1.2.3", "1.2.3-alpha.1", "1.2.3-alpha.30", "1.2.3-beta.2",
"1.2.3-rc.70"
]:
v = Version(valid_string)
assert v.as_identifier() == valid_string
assert v.as_tag() == f"v{valid_string}"

for invalid_string in [
"0.0",
"1.2.3-alpha",
"1.2.3-alpha.1.2",
"1.2.3-beta.2.3",
"1.2.3-rc.70.1",
"1",
"-1.1.0",
"1.1.0-",
"1.1.0-alpha-",
"1.1.0-beta.",
"1.1.0-rc",
]:
try:
v = Version(invalid_string)
except pydantic.ValidationError:
pass
else:
raise AssertionError(
f"ValidationError not raised for invalid version string '{invalid_string}'"
)

assert Version("1.2.3-alpha.1") > Version("1.2.3-alpha.0")
assert Version("1.2.3-alpha.1") < Version("1.2.3-alpha.2")
assert Version("1.2.3-alpha.1") == Version("1.2.3-alpha.1")
assert Version("1.2.3-alpha.1") != Version("1.2.3-alpha.2")
assert Version("1.2.3") > Version("1.2.2")
assert Version("1.2.3") < Version("1.2.4")
assert Version("10.2.3") > Version("1.2.3")
assert Version("10.6.9") < Version("10.7.8")
assert Version("10.6.9-alpha.1") > Version("10.6.8-beta.3")
assert Version("10.6.9-alpha.4") < Version("10.6.9-beta.3")
assert Version("10.6.9-alpha.4") < Version("10.6.9-rc.3")
assert Version("10.6.9-rc.4") > Version("10.6.9-beta.3")
assert Version("10.6.9-rc.4") < Version("10.6.9-rc.5")
2 changes: 1 addition & 1 deletion tum_esm_utils/em27.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def load_proffast2_result(path: str) -> pl.DataFrame:
path,
has_header=True,
separator=",",
dtypes={
schema_overrides={
"UTC": pl.Datetime,
" LocalTime": pl.Utf8,
" spectrum": pl.Utf8,
Expand Down
77 changes: 77 additions & 0 deletions tum_esm_utils/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Implements: `StrictFilePath`, `StrictDirectoryPath`"""

from __future__ import annotations
from typing import Literal, Optional
import os
import pydantic

Expand Down Expand Up @@ -75,3 +76,79 @@ def path_should_exist(cls, v: str, info: pydantic.ValidationInfo) -> str:
if (not ignore_path_existence) and (not os.path.isdir(v)):
raise ValueError('Directory does not exist')
return v


class Version(pydantic.RootModel[str]):
"""A version string in the format of MAJOR.MINOR.PATCH[-(alpha|beta|rc).N]"""

root: str = pydantic.Field(
...,
pattern=r"^\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?$",
examples=["1.2.3", "4.5.6-alpha.78", "7.8.9-beta.10", "11.12.13-rc.14"],
)

def as_tag(self) -> str:
"""Return the version string as a tag, i.e. vMAJOR.MINOR.PATCH..."""
return "v" + self.root

def as_identifier(self) -> str:
"""Return the version string as a number, i.e. MAJOR.MINOR.PATCH..."""
return self.root

def _split(
self
) -> tuple[int, int, int, Optional[tuple[Literal["alpha", "beta", "rc"],
int]]]:
"""Split the version string into MAJOR, MINOR, PATCH, and TAG"""
version, tag = self.root.split("-") if "-" in self.root else (
self.root, ""
)
major, minor, patch = map(int, version.split("."))
if "-" in self.root:
tags = tag.split(".")
return major, minor, patch, (tags[0], int(tags[1])) # type: ignore
else:
return major, minor, patch, None

# add comparisons
def __lt__(self, other: Version) -> bool:
self_major, self_minor, self_patch, self_tag = self._split()
other_major, other_minor, other_patch, other_tag = other._split()

if self_major != other_major:
return self_major < other_major
if self_minor != other_minor:
return self_minor < other_minor
if self_patch != other_patch:
return self_patch < other_patch

if self_tag is None:
return False

if other_tag is None:
return True

assert (self_tag is not None) and (other_tag is not None)
self_tag_type, self_tag_number = self_tag
other_tag_type, other_tag_number = other_tag

if (self_tag_type == "alpha") and (other_tag_type in ["beta", "rc"]):
return True
if (self_tag_type == "beta") and (other_tag_type == "rc"):
return True
if (self_tag_type == "beta") and (other_tag_type == "alpha"):
return False
if (self_tag_type == "rc") and (other_tag_type in ["alpha", "beta"]):
return False

assert self_tag_type == other_tag_type
return self_tag_number < other_tag_number

def __le__(self, other: Version) -> bool:
return (self < other) or (self == other)

def __gt__(self, other: Version) -> bool:
return not (self <= other)

def __ge__(self, other: Version) -> bool:
return not (self < other)