Skip to content

Commit

Permalink
🎯 feat: remove engine and value from param model.
Browse files Browse the repository at this point in the history
  • Loading branch information
korawica committed Oct 7, 2024
1 parent d54803e commit 53723af
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 88 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ import os
from ddeutil.io import YamlEnvFl

os.environ["HELLO"] = "WORLD"

content = YamlEnvFl('./.pre-commit-config.yaml').read(safe=True)
assert content['data']['get'] == "HELLO WORLD"
```
Expand Down
2 changes: 0 additions & 2 deletions src/ddeutil/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,10 @@
search_env_replace,
)
from .param import (
Engine,
Params,
PathData,
Rule,
Stage,
Value,
)
from .register import Register
from .utils import (
Expand Down
2 changes: 2 additions & 0 deletions src/ddeutil/io/conf.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
UPDATE_KEY: str = "__updt"
VERSION_KEY: str = "__version"
DATE_FMT: str = "%Y-%m-%d %H:%M:%S"
DATE_LOG_FMT: str = "%Y%m%d%H%M%S"
20 changes: 13 additions & 7 deletions src/ddeutil/io/files/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
try:
from yaml import CSafeLoader as SafeLoader
from yaml import CUnsafeLoader as UnsafeLoader
except ImportError: # no cove
except ImportError: # pragma: no cover
from yaml import SafeLoader, UnsafeLoader

from .utils import search_env, search_env_replace
Expand Down Expand Up @@ -68,9 +68,12 @@

def compress_lib(compress: str) -> CompressProtocol:
"""Return Compress module that use to unpack data from the compressed file.
Now, it support for "gzip", "gz", "xz", "bz2"]
Note:
Now, it support for "gzip", "gz", "xz", "bz2"]
:param compress: A compress string type value that want to get compress
package.
:type compress: str
:rtype: CompressProtocol
"""
if not compress:
return io
Expand All @@ -89,13 +92,13 @@ def compress_lib(compress: str) -> CompressProtocol:
raise NotImplementedError(f"Compress {compress} does not implement yet")


class CompressProtocol(Protocol): # no cove
class CompressProtocol(Protocol): # pragma: no cover
def decompress(self, *args, **kwargs) -> AnyStr: ...

def open(self, *args, **kwargs) -> IO: ...


class FlABC(abc.ABC): # no cove
class FlABC(abc.ABC): # pragma: no cover
"""Open File abstraction object for marking abstract method that need to
implement on any subclass.
"""
Expand All @@ -120,7 +123,10 @@ class Fl(FlABC):
:param compress: A compress type for this file.
Examples:
>>> with Fl('./<path>/<filename>.gz.txt', compress='gzip').open() as f:
>>> with Fl(
... './<path>/<filename>.gz.txt',
... compress='gzip',
... ).open() as f:
... data = f.readline()
"""

Expand All @@ -138,7 +144,7 @@ def __init__(
# NOTE: Action anything after set up attributes.
self.after_set_attrs()

def after_set_attrs(self) -> None: ...
def after_set_attrs(self) -> None: ... # pragma: no cover

def __call__(self, *args, **kwargs) -> IO:
"""Return IO of this object."""
Expand Down
22 changes: 11 additions & 11 deletions src/ddeutil/io/param.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from pydantic.functional_validators import field_validator
from typing_extensions import Self

from .conf import UPDATE_KEY, VERSION_KEY
from .exceptions import ConfigArgumentError
from .files import YamlEnvFl

Expand Down Expand Up @@ -146,19 +145,20 @@ def prepare_path_from_path_str(cls, v, info: ValidationInfo) -> Path:
return info.data["root"] / info.field_name


class Value(BaseModel):
dt_fmt: str = Field(default="%Y-%m-%d %H:%M:%S")
excluded: TupleStr = Field(default=(VERSION_KEY, UPDATE_KEY))


class Engine(BaseModel):
paths: PathData = Field(default_factory=PathData)
values: Value = Field(default_factory=Value)
class Params(BaseModel, validate_assignment=True):
"""Params Pydantic model.
Examples:
>>> params = {
... "paths": {
... "root": ".",
... },
... "stage": {},
... }
"""

class Params(BaseModel, validate_assignment=True):
stages: dict[str, Stage] = Field(default_factory=dict)
engine: Engine = Field(default_factory=Engine)
paths: PathData = Field(default_factory=PathData)

@classmethod
def from_yaml(cls, path: str | Path) -> Self:
Expand Down
42 changes: 17 additions & 25 deletions src/ddeutil/io/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
)
from typing_extensions import Self

from .conf import UPDATE_KEY, VERSION_KEY
from .conf import DATE_FMT, UPDATE_KEY, VERSION_KEY
from .config import ConfFl
from .exceptions import ConfigArgumentError, ConfigNotFound
from .files import Fl, rm
Expand Down Expand Up @@ -230,9 +230,7 @@ def __init__(

def __hash__(self) -> int:
return hash.hash_all(
self.fullname
+ self.stage
+ f"{self.timestamp:{self.params.engine.values.dt_fmt}}"
self.fullname + self.stage + f"{self.timestamp:{DATE_FMT}}"
)

def __str__(self) -> str:
Expand Down Expand Up @@ -260,12 +258,10 @@ def data(self, hashing: bool = False) -> dict[str, Any]:
_data = {
k: v
for k, v in (self.meta.get(self.stage, {}).items())
if k in self.params.engine.values.excluded
if k in (UPDATE_KEY, VERSION_KEY)
} | self.__data
return (
hash.hash_all(
_data, exclude=set(self.params.engine.values.excluded)
)
hash.hash_all(_data, exclude={UPDATE_KEY, VERSION_KEY})
if hashing
else _data
)
Expand All @@ -280,10 +276,7 @@ def timestamp(self) -> datetime:
if self.changed > 0:
return self.updt
elif _dt := self.data().get(UPDATE_KEY):
return datetime.strptime(
_dt,
self.params.engine.values.dt_fmt,
)
return datetime.strptime(_dt, DATE_FMT)
return self.updt

def version(self, _next: bool = False) -> VerPackage:
Expand Down Expand Up @@ -331,7 +324,7 @@ def compare_data(
target,
ignore_order=True,
exclude_paths={
f"root[{key!r}]" for key in self.params.engine.values.excluded
f"root[{key!r}]" for key in (UPDATE_KEY, VERSION_KEY)
},
)
if any(
Expand Down Expand Up @@ -386,13 +379,13 @@ def pick(
) -> dict[str, Any]:
if (stage is None) or (stage == "base"):
return ConfFl(
path=(self.params.engine.paths.conf / self.domain),
path=(self.params.paths.conf / self.domain),
open_file=self.loader,
open_file_stg=self.loader_stg,
).load(name=self.name, order=order)

loading = ConfFl(
path=self.params.engine.paths.data / stage,
path=self.params.paths.data / stage,
compress=self.params.get_stage(stage).rules.compress,
open_file=self.loader,
open_file_stg=self.loader_stg,
Expand All @@ -418,7 +411,7 @@ def move(
) -> Register:
"""Move file to the target stage."""
loading: ConfFl = ConfFl(
path=self.params.engine.paths.data / stage,
path=self.params.paths.data / stage,
compress=self.params.get_stage(stage).rules.compress,
open_file=self.loader,
open_file_stg=self.loader_stg,
Expand All @@ -427,7 +420,7 @@ def move(
self.compare_data(
hash.hash_all(
self.pick(stage=stage),
exclude=set(self.params.engine.values.excluded),
exclude={UPDATE_KEY, VERSION_KEY},
)
)
> 0
Expand All @@ -441,13 +434,12 @@ def move(
logging.warning(
f"File {_filename!r} already exists in {stage!r} stage."
)
_dt_fmt: str = self.params.engine.values.dt_fmt
loading.save_stage(
path=(loading.path / _filename),
data=merge.merge_dict(
self.data(),
{
UPDATE_KEY: f"{self.timestamp:{_dt_fmt}}",
UPDATE_KEY: f"{self.timestamp:{DATE_FMT}}",
VERSION_KEY: f"v{str(self.version())}",
},
),
Expand Down Expand Up @@ -480,7 +472,7 @@ def purge(self, stage: Optional[str] = None) -> None:
if not (_rules := self.params.get_stage(_stage).rules):
return
loading: ConfFl = ConfFl(
path=self.params.engine.paths.data / stage,
path=self.params.paths.data / stage,
compress=_rules.compress,
open_file=self.loader,
open_file_stg=self.loader_stg,
Expand Down Expand Up @@ -534,7 +526,7 @@ def remove(self, stage: Optional[str] = None) -> None:
_stage != "base"
), "The remove method can not process on the 'base' stage."
loading: ConfFl = ConfFl(
path=self.params.engine.paths.data / _stage,
path=self.params.paths.data / _stage,
open_file=self.loader,
open_file_stg=self.loader_stg,
)
Expand All @@ -557,7 +549,7 @@ def purge(self, stage: Optional[str] = None) -> None:
if not (_rules := self.params.get_stage(_stage).rules):
return
loading: ConfFl = ConfFl(
path=self.params.engine.paths.data / stage,
path=self.params.paths.data / stage,
compress=_rules.compress,
open_file=self.loader,
open_file_stg=self.loader_stg,
Expand Down Expand Up @@ -586,7 +578,7 @@ def purge(self, stage: Optional[str] = None) -> None:
)
loading.move(
_file,
dest=self.params.engine.paths.data / ".archive" / _ac_path,
dest=self.params.paths.data / ".archive" / _ac_path,
)
rm(loading.path / _file)

Expand All @@ -601,7 +593,7 @@ def remove(self, stage: Optional[str] = None) -> None:
_stage != "base"
), "The remove method can not process on the 'base' stage."
loading: ConfFl = ConfFl(
path=self.params.engine.paths.data / _stage,
path=self.params.paths.data / _stage,
open_file=self.loader,
open_file_stg=self.loader_stg,
)
Expand All @@ -614,6 +606,6 @@ def remove(self, stage: Optional[str] = None) -> None:
_ac_path: str = f"{_stage.lower()}_{self.updt:%Y%m%d%H%M%S}_{_file}"
loading.move(
_file,
dest=self.params.engine.paths / ".archive" / _ac_path,
dest=self.params.paths.data / ".archive" / _ac_path,
)
rm(loading.path / _file)
33 changes: 13 additions & 20 deletions tests/test_param.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
from pathlib import Path

import ddeutil.io.param as md
import pytest
from ddeutil.io.conf import UPDATE_KEY, VERSION_KEY
from ddeutil.io.exceptions import ConfigArgumentError
from ddeutil.io.param import Params, PathData, Rule, Stage


def test_param_path_data_default(test_path):
param = md.PathData.model_validate({"root": test_path})
param = PathData.model_validate({"root": test_path})
assert param.root == test_path


def test_param_path_data():
p = md.PathData.model_validate({"data": Path("."), "conf": Path(".")})
p = PathData.model_validate({"data": Path("."), "conf": Path(".")})
assert {
"data": Path("."),
"conf": Path("."),
Expand All @@ -21,7 +20,7 @@ def test_param_path_data():


def test_model_path_data_with_root():
p = md.PathData.model_validate({"root": "./src/"})
p = PathData.model_validate({"root": "./src/"})
assert {
"data": Path("./src/data"),
"conf": Path("./src/conf"),
Expand All @@ -35,11 +34,11 @@ def test_model_rule_data():
"version": None,
"excluded": [],
"compress": None,
} == md.Rule.model_validate({}).model_dump()
} == Rule.model_validate({}).model_dump()


def test_model_stage_data():
stage = md.Stage.model_validate(
stage = Stage.model_validate(
{
"alias": "persisted",
"format": "{timestamp:%Y-%m-%d}{naming:%c}.json",
Expand All @@ -62,7 +61,7 @@ def test_model_stage_data():
} == stage.model_dump()

with pytest.raises(ConfigArgumentError):
md.Stage.model_validate(
Stage.model_validate(
{
"alias": "persisted",
"format": "timestamp.json",
Expand All @@ -73,7 +72,7 @@ def test_model_stage_data():
)

with pytest.raises(ConfigArgumentError):
md.Stage.model_validate(
Stage.model_validate(
{
"alias": "persisted",
"format": "{datetime:%Y%m%d}.json",
Expand All @@ -85,7 +84,7 @@ def test_model_stage_data():


def test_model_params():
params = md.Params.model_validate(
params: Params = Params.model_validate(
{
"stages": {
"raw": {"format": "{naming:%s}.{timestamp:%Y%m%d_%H%M%S}"},
Expand Down Expand Up @@ -130,15 +129,9 @@ def test_model_params():
"layer": 3,
},
},
"engine": {
"values": {
"dt_fmt": "%Y-%m-%d %H:%M:%S",
"excluded": (VERSION_KEY, UPDATE_KEY),
},
"paths": {
"conf": Path("conf"),
"data": Path("data"),
"root": Path("."),
},
"paths": {
"conf": Path("conf"),
"data": Path("data"),
"root": Path("."),
},
} == params.model_dump()
Loading

0 comments on commit 53723af

Please sign in to comment.