adiumentum 0.1.0__py3-none-any.whl → 0.3.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.
adiumentum/__init__.py CHANGED
@@ -3,23 +3,30 @@ from .comparison import equal_within, nearly_equal
3
3
  from .exceptions import CustomValidationError
4
4
  from .file_modification_time import (
5
5
  first_newer,
6
- get_time_created,
7
- get_time_modified,
6
+ time_created,
8
7
  time_created_readable,
8
+ time_modified,
9
9
  time_modified_readable,
10
10
  )
11
11
  from .frozendict import FrozenDefaultDict
12
12
  from .functional import (
13
13
  dmap,
14
+ endofilter,
15
+ endomap,
14
16
  fold_dictionaries,
15
17
  identity,
18
+ kfilter,
16
19
  kmap,
20
+ lfilter,
17
21
  lmap,
22
+ sfilter,
18
23
  smap,
24
+ tfilter,
19
25
  tmap,
26
+ vfilter,
20
27
  vmap,
21
28
  )
22
- from .io import (
29
+ from .io_utils import (
23
30
  list_full,
24
31
  read_json,
25
32
  read_raw,
@@ -28,20 +35,24 @@ from .io import (
28
35
  write_raw_bytes,
29
36
  )
30
37
  from .markers import (
31
- helper,
38
+ endo,
32
39
  impure,
33
40
  mutates,
34
41
  mutates_and_returns_instance,
35
42
  mutates_instance,
36
43
  pure,
37
44
  refactor,
38
- step_data,
39
- step_transition,
40
- validator,
45
+ )
46
+ from .merge import (
47
+ join_as_sequence,
48
+ make_hashable,
49
+ merge_dicts,
41
50
  )
42
51
  from .numerical import evenly_spaced, ihash, round5
43
- from .performance_logging import log_perf
44
- from .string import (
52
+ from .paths_manager import PathsManager
53
+ from .performance_logging import log_perf # type: ignore
54
+ from .pydantic_extensions import BaseDict, BaseList, BaseSet
55
+ from .string_utils import (
45
56
  MixedValidated,
46
57
  PromptTypeName,
47
58
  as_json,
@@ -49,8 +60,9 @@ from .string import (
49
60
  flexsplit,
50
61
  indent_lines,
51
62
  parse_sequence,
63
+ re_split,
52
64
  )
53
- from .timestamping import insert_timestamp
65
+ from .timestamping import insert_timestamp, make_timestamp
54
66
  from .typing_utils import (
55
67
  areinstances,
56
68
  call_fallback_if_none,
@@ -61,54 +73,64 @@ DELIMITER = "᜶"
61
73
 
62
74
  __all__ = [
63
75
  "DELIMITER",
76
+ "BaseDict",
77
+ "BaseList",
78
+ "BaseSet",
64
79
  "Colorizer",
65
80
  "CustomValidationError",
66
81
  "FrozenDefaultDict",
67
82
  "MixedValidated",
68
- "NoneDate",
69
- "NoneTime",
83
+ "PathsManager",
70
84
  "PromptTypeName",
71
85
  "areinstances",
72
- "args_to_dict",
73
86
  "as_json",
74
87
  "call_fallback_if_none",
75
88
  "cast_as",
76
89
  "dmap",
90
+ "endo",
91
+ "endofilter",
92
+ "endomap",
77
93
  "equal_within",
78
94
  "evenly_spaced",
79
95
  "fallback_if_none",
80
96
  "first_newer",
81
97
  "flexsplit",
82
98
  "fold_dictionaries",
83
- "get_time_created",
84
- "get_time_modified",
85
- "helper",
86
99
  "identity",
87
100
  "ihash",
88
101
  "impure",
89
102
  "indent_lines",
90
103
  "insert_timestamp",
104
+ "join_as_sequence",
105
+ "kfilter",
91
106
  "kmap",
107
+ "lfilter",
92
108
  "list_full",
93
109
  "lmap",
94
110
  "log_perf",
111
+ "make_hashable",
112
+ "make_timestamp",
113
+ "merge_dicts",
95
114
  "mutates",
96
115
  "mutates_and_returns_instance",
97
116
  "mutates_instance",
98
117
  "nearly_equal",
99
118
  "parse_sequence",
100
119
  "pure",
120
+ "re_split",
101
121
  "read_json",
102
122
  "read_raw",
103
123
  "refactor",
104
124
  "round5",
125
+ "sfilter",
105
126
  "smap",
106
- "step_data",
107
- "step_transition",
127
+ "tfilter",
128
+ "time_created",
108
129
  "time_created_readable",
130
+ "time_modified",
109
131
  "time_modified_readable",
110
132
  "tmap",
111
- "validator",
133
+ "vfilter",
112
134
  "vmap",
113
135
  "write_json",
114
136
  "write_raw",
@@ -0,0 +1,84 @@
1
+ # for tests
2
+ sample = [
3
+ {"id": "a", "priority": 0.4, "prerequisites": []},
4
+ {"id": "b", "priority": 0.7, "prerequisites": []},
5
+ {"id": "c", "priority": 0.4, "prerequisites": []},
6
+ {"id": "d", "priority": 0.3, "prerequisites": []},
7
+ {"id": "e", "priority": 0.8, "prerequisites": ["a", "b"]},
8
+ {"id": "f", "priority": 0.9, "prerequisites": []},
9
+ {"id": "g", "priority": 0.1, "prerequisites": ["i", "m"]},
10
+ {"id": "h", "priority": 0.5, "prerequisites": []},
11
+ {"id": "i", "priority": 0.45, "prerequisites": ["j"]},
12
+ {"id": "j", "priority": 0.3, "prerequisites": []},
13
+ {"id": "k", "priority": 0.8, "prerequisites": ["l", "m", "d"]},
14
+ {"id": "l", "priority": 0.6, "prerequisites": ["o"]},
15
+ {"id": "m", "priority": 0.9, "prerequisites": []},
16
+ {"id": "n", "priority": 0.4, "prerequisites": ["o"]},
17
+ {"id": "o", "priority": 0.2, "prerequisites": []},
18
+ {"id": "p", "priority": 0.5, "prerequisites": []},
19
+ ]
20
+
21
+
22
+ def dep_sort(task_list: list[dict]) -> list[dict]:
23
+ """
24
+ Sort list of dictionaries such that
25
+ 1) dependency constraints are satisfied and
26
+ 2) priority ordering is satisfied subject to (1)
27
+ """
28
+ # sort on priority to preserve priority order in the output
29
+ task_list.sort(key=lambda t: t["priority"], reverse=True)
30
+ task_ids = [t["id"] for t in task_list]
31
+
32
+ # important to make changes in reverse priority order so that insertion preserves priority order
33
+ dep_dict = {t["id"]: t["prerequisites"] for t in reversed(task_list) if t["prerequisites"]}
34
+ print(dep_dict)
35
+
36
+ def place_after(to_move: str, deps: list[str], id_list: list) -> bool:
37
+ if not deps:
38
+ return False
39
+ idx1 = id_list.index(to_move)
40
+ idx2 = max(map(id_list.index, deps))
41
+ if idx1 > idx2:
42
+ return False
43
+ id_list.pop(idx1)
44
+ id_list.insert(idx2, to_move)
45
+ return True
46
+
47
+ print(task_ids)
48
+ count = 0
49
+ maxcount = sum(range(len(task_ids) + 1))
50
+ while count < maxcount:
51
+ change_tracker = []
52
+ for task_id, task_deps in dep_dict.items():
53
+ changed = place_after(task_id, task_deps, task_ids)
54
+ change_tracker.append(changed)
55
+ unchanged = not any(change_tracker)
56
+ print(task_ids)
57
+ if unchanged:
58
+ return sorted(task_list, key=lambda t: task_ids.index(t["id"]))
59
+ count += 1
60
+
61
+ print("ERROR ---------------------------------------------------")
62
+ str(task_ids)
63
+ for task_id, task_deps in dep_dict.items():
64
+ changed = place_after(task_id, task_deps, task_ids)
65
+ if changed:
66
+ after = str(task_ids)
67
+ print(after)
68
+
69
+ raise ValueError("Graph contains a cycle.")
70
+
71
+ # levels = {t: 0 for t in task_ids}
72
+ # roots = list(filter(lambda t: t["depencies"] == [], task_list))
73
+ # depended_on = {t: set() for t in task_ids}
74
+ # for t in task_list:
75
+ # for d in t["prerequisites"]:
76
+ # depended_on[d].add(t)
77
+ # dict_rep = json.dumps(levels)
78
+ # new_rep = ""
79
+ # while new_rep != dict_rep:
80
+ # dict_rep = json.dumps(levels)
81
+ # for t in task_list:
82
+ # levels[t["id"]] = max([levels[d] for d in t["prerequisites"]]) + 1
83
+ # new_rep = json.dumps(levels)
84
+ # return sorted(task_list, key=lambda t: levels[t["id"]])
adiumentum/display.py ADDED
@@ -0,0 +1,49 @@
1
+ import re
2
+ from collections import defaultdict
3
+ from collections.abc import Callable
4
+
5
+
6
+ def display_counts(key: str, depth: int, dl: list[str]) -> None:
7
+ print(f"\n=== {key} ===")
8
+ for d in dl:
9
+ if key not in d:
10
+ print(d["name"])
11
+ cats = [d[key] for d in dl]
12
+ cats = [".".join(re.split(r"\.|, ", c)[:depth]) for c in cats]
13
+ counts = sorted([(cats.count(c), c) for c in sorted(set(cats))], reverse=True)
14
+ for count, item in counts:
15
+ print(f"{count:>4} {item}")
16
+
17
+
18
+ def print_tree(strings):
19
+ # Nested dictionary to hold tree structure
20
+ def tree() -> defaultdict:
21
+ return defaultdict(tree)
22
+
23
+ root = tree()
24
+
25
+ # Build the tree
26
+ for _string in strings:
27
+ parts = _string.split(".")
28
+ current_level = root
29
+ for part in parts:
30
+ current_level = current_level[part]
31
+
32
+ # Function to print the tree recursively
33
+ def print_subtree(node, prefix=""):
34
+ children = list(node.keys())
35
+ for i, child in enumerate(children):
36
+ is_last = i == len(children) - 1
37
+ if is_last:
38
+ print(prefix + "└─ " + child)
39
+ new_prefix = prefix + " "
40
+ else:
41
+ print(prefix + "├─ " + child)
42
+ new_prefix = prefix + "│ "
43
+ print_subtree(node[child], new_prefix)
44
+
45
+ # Print the root
46
+ print_subtree(root)
47
+
48
+
49
+ def wrap_line(line: str, length: int, formatter: Callable) -> str: ...
@@ -1,24 +1,32 @@
1
1
  import os
2
- import time
2
+ from datetime import datetime
3
3
  from pathlib import Path
4
4
 
5
5
 
6
- def get_time_created(path: Path | str) -> float:
7
- return os.path.getctime(path)
6
+ def time_created(path: Path | str) -> float:
7
+ return Path(path).stat().st_ctime
8
8
 
9
9
 
10
- def time_created_readable(path: Path | str) -> str:
11
- time_created: time.struct_time = time.strptime(time.ctime(get_time_created(path)))
12
- return time.strftime("%Y-%m-%d %H:%M:%S", time_created)
10
+ def format_time(raw_time: float, places: int = 3) -> str:
11
+ dt = datetime.fromtimestamp(raw_time)
12
+ if places > 0:
13
+ idx: int | None = min(0, int(places) - 6) or None
14
+ return dt.strftime("%Y-%m-%d_%H:%M:%S.%f")[:idx]
15
+ return dt.strftime("%Y-%m-%d_%H:%M:%S")
13
16
 
14
17
 
15
- def get_time_modified(path: Path | str) -> float:
16
- return os.path.getmtime(path)
18
+ def time_created_readable(path: Path | str, places: int = 3) -> str:
19
+ # time_created: time.struct_time = time.strptime(time.ctime(time_created(path)))
20
+ return format_time(time_created(path), places=places)
17
21
 
18
22
 
19
- def time_modified_readable(path: Path | str) -> str:
20
- time_modified: time.struct_time = time.strptime(time.ctime(get_time_modified(path)))
21
- return time.strftime("%Y-%m-%d %H:%M:%S", time_modified)
23
+ def time_modified(path: Path | str) -> float:
24
+ return Path(path).stat().st_mtime
25
+
26
+
27
+ def time_modified_readable(path: Path | str, places: int = 3) -> str:
28
+ # time_modified: time.struct_time = time.strptime(time.ctime(time_modified(path)))
29
+ return format_time(time_modified(path), places=places)
22
30
 
23
31
 
24
32
  def first_newer(file1: str | Path, file2: str | Path | tuple[Path, ...] | tuple[str, ...]) -> bool:
adiumentum/frozendict.py CHANGED
@@ -1,12 +1,12 @@
1
1
  from collections import defaultdict
2
2
  from collections.abc import Callable
3
- from typing import Generic, TypeVar, cast
3
+ from typing import TypeVar, cast
4
4
 
5
5
  T = TypeVar("T")
6
6
  K = TypeVar("K")
7
7
 
8
8
 
9
- class FrozenDefaultDict(defaultdict[K, T], Generic[K, T]):
9
+ class FrozenDefaultDict[K, T](defaultdict[K, T]):
10
10
  def __init__(self, default_factory: Callable[[], T], dictionary: dict[K, T]):
11
11
  super().__init__(default_factory, dictionary)
12
12
 
adiumentum/functional.py CHANGED
@@ -1,12 +1,39 @@
1
- from collections.abc import Callable, Iterable
1
+ from collections.abc import Callable, Hashable, Iterable
2
2
  from functools import reduce
3
- from typing import TypeVar
3
+ from typing import TypeVar, overload
4
4
 
5
5
  T = TypeVar("T")
6
6
  TPost = TypeVar("TPost")
7
7
  TPre = TypeVar("TPre")
8
- K = TypeVar("K")
8
+ K = TypeVar("K", bound=Hashable)
9
9
  V = TypeVar("V")
10
+ type Filterer[T] = Callable[[T], bool]
11
+
12
+
13
+ @overload
14
+ def endomap(callable_: Callable[[TPre], TPost], sequence: list[TPre]) -> list[TPost]: ...
15
+ @overload
16
+ def endomap(callable_: Callable[[TPre], TPost], sequence: set[TPre]) -> set[TPost]: ...
17
+ @overload
18
+ def endomap(
19
+ callable_: Callable[[TPre], TPost], sequence: tuple[TPre, ...]
20
+ ) -> tuple[TPost, ...]: ...
21
+
22
+
23
+ def endomap(callable_, sequence):
24
+ return type(sequence)(map(callable_, sequence))
25
+
26
+
27
+ @overload
28
+ def endofilter(callable_: Callable[[T], bool], sequence: list[T]) -> list[T]: ...
29
+ @overload
30
+ def endofilter(callable_: Callable[[T], bool], sequence: set[T]) -> set[T]: ...
31
+ @overload
32
+ def endofilter(callable_: Callable[[T], bool], sequence: tuple[T, ...]) -> tuple[T, ...]: ...
33
+
34
+
35
+ def endofilter(callable_, sequence):
36
+ return type(sequence)(filter(callable_, sequence))
10
37
 
11
38
 
12
39
  def lmap(callable_: Callable[[TPre], TPost], iterable: Iterable[TPre]) -> list[TPost]:
@@ -33,12 +60,36 @@ def dmap(callable_: Callable[[TPre], TPost], dictionary: dict[TPre, TPre]) -> di
33
60
  return {callable_(k): callable_(v) for k, v in dictionary.items()}
34
61
 
35
62
 
36
- def identity(x: T) -> T:
63
+ def lfilter(filterer: Filterer[T], iterable: Iterable[T]) -> list[T]:
64
+ return list(filter(filterer, iterable))
65
+
66
+
67
+ def sfilter(filterer: Filterer[T], iterable: Iterable[T]) -> set[T]:
68
+ return set(filter(filterer, iterable))
69
+
70
+
71
+ def tfilter(filterer: Filterer[T], iterable: Iterable[T]) -> tuple[T, ...]:
72
+ return tuple(filter(filterer, iterable))
73
+
74
+
75
+ def vfilter(filterer: Filterer[V], dictionary: dict[K, V]) -> dict[K, V]:
76
+ return {k: v for k, v in dictionary.items() if filterer(v)}
77
+
78
+
79
+ def kfilter(filterer: Filterer[K], dictionary: dict[K, V]) -> dict[K, V]:
80
+ return {k: v for k, v in dictionary.items() if filterer(k)}
81
+
82
+
83
+ def dfilter(filterer: Filterer[T], dictionary: dict[T, T]) -> dict[T, T]:
84
+ return {k: v for k, v in dictionary.items() if filterer(k) and filterer(v)}
85
+
86
+
87
+ def identity[T](x: T) -> T:
37
88
  return x
38
89
 
39
90
 
40
- def fold_dictionaries(dicts: Iterable[dict[K, T]]) -> dict[K, T]:
41
- def _or(dict1: dict[K, T], dict2: dict[K, T]) -> dict[K, T]:
91
+ def fold_dictionaries[K, V](dicts: Iterable[dict[K, V]]) -> dict[K, V]:
92
+ def _or(dict1: dict[K, V], dict2: dict[K, V]) -> dict[K, V]:
42
93
  return dict1 | dict2
43
94
 
44
95
  return reduce(_or, dicts)
adiumentum/io_utils.py ADDED
@@ -0,0 +1,63 @@
1
+ import json
2
+ import os
3
+ import shutil
4
+ from pathlib import Path
5
+ from typing import Literal
6
+
7
+ from .timestamping import make_timestamp
8
+ from .typing_utils import JSONDict, JSONList
9
+
10
+
11
+ def list_full(directory: str | Path, ending: str = "") -> list[Path]:
12
+ directory = Path(directory)
13
+ return sorted([directory / file for file in os.listdir(directory) if file.endswith(ending)])
14
+
15
+
16
+ def read_raw(json_path: Path) -> str:
17
+ with open(json_path, encoding="utf-8") as f:
18
+ return f.read()
19
+
20
+
21
+ def back_up_json(original_path: Path, backup_dir: Path | None) -> None:
22
+ if not backup_dir:
23
+ return
24
+ backup_path = backup_dir / original_path.name.replace(".json", f"__{make_timestamp()}.json")
25
+ if original_path.exists():
26
+ shutil.copy(original_path, backup_path)
27
+
28
+
29
+ def read_json(json_path: Path, backup_dir: Path | None = None) -> JSONDict | JSONList:
30
+ back_up_json(json_path, backup_dir)
31
+ with open(json_path, encoding="utf-8") as f:
32
+ return json.load(f)
33
+
34
+
35
+ def write_json(
36
+ python_obj: JSONDict | JSONList | bytes,
37
+ json_path: Path,
38
+ backup_dir: Path | None = None,
39
+ mode: Literal["w", "a"] = "w",
40
+ ) -> None:
41
+ if mode not in {"w", "a"}:
42
+ raise ValueError(f"Invalid mode: '{mode}'")
43
+ back_up_json(json_path, backup_dir)
44
+ if mode == "a":
45
+ existing = read_json(json_path)
46
+ if isinstance(existing, list) and isinstance(existing, list):
47
+ python_obj = existing + python_obj
48
+ elif isinstance(existing, list) and isinstance(existing, list):
49
+ python_obj = existing | python_obj
50
+ else:
51
+ raise ValueError("Data types do not match.")
52
+ with open(json_path, "w", encoding="utf-8") as f:
53
+ json.dump(python_obj, f, ensure_ascii=False, indent=4)
54
+
55
+
56
+ def write_raw(text: str, file_path: Path) -> None:
57
+ with open(file_path, "w", encoding="utf-8") as f:
58
+ f.write(text)
59
+
60
+
61
+ def write_raw_bytes(text: bytes, json_path: Path) -> None:
62
+ with open(json_path, "wb") as f:
63
+ f.write(text)
adiumentum/markers.py CHANGED
@@ -1,117 +1,100 @@
1
1
  from collections.abc import Callable
2
2
  from functools import wraps
3
+ from typing import TypeVar, cast
3
4
 
4
- from .functional import identity
5
+ # from .functional import identity
5
6
 
6
7
 
7
- def impure(callable_or_none: Callable | None = None, **kwargs) -> Callable:
8
+ T = TypeVar("T")
9
+ In = TypeVar("In")
10
+ Out = TypeVar("Out")
11
+
12
+
13
+ def trivial_decorator[In, Out](func: Callable[[In], Out]) -> Callable[[In], Out]:
14
+ return func
15
+
16
+
17
+ def impure[In, Out](
18
+ callable_or_none: Callable[[In], Out] | None = None,
19
+ message: str = "",
20
+ ) -> Callable[[In], Out] | Callable[[Callable[[In], Out]], Callable[[In], Out]]:
8
21
  if callable_or_none is None:
9
- return identity
22
+ return trivial_decorator
10
23
  else:
11
24
  return callable_or_none
12
25
 
13
26
 
14
- def pure(callable_or_none: Callable | None = None, **kwargs) -> Callable:
27
+ def pure[In, Out](
28
+ callable_or_none: Callable[[In], Out] | None = None,
29
+ message: str = "",
30
+ ) -> Callable[[In], Out] | Callable[[Callable[[In], Out]], Callable[[In], Out]]:
15
31
  if callable_or_none is None:
16
- return identity
32
+ return trivial_decorator
17
33
  else:
18
34
  return callable_or_none
19
35
 
20
36
 
21
- def helper(callable_or_none: Callable | None = None, **kwargs) -> Callable:
37
+ def endo(
38
+ callable_or_none: Callable[[T], T] | None = None,
39
+ message: str = "",
40
+ ) -> Callable[[T], T] | Callable[[Callable[[In], Out]], Callable[[In], Out]]:
22
41
  if callable_or_none is None:
23
- return identity
42
+ return trivial_decorator
24
43
  else:
25
44
  return callable_or_none
26
45
 
27
46
 
28
- def step_data(func: Callable) -> Callable:
47
+ def decorate_with_message[In, Out](func: Callable[[In], Out], message: str) -> Callable[[In], Out]:
29
48
  @wraps(func)
30
- def wrapper(*fargs, **fkwargs):
31
- return func(*fargs, **fkwargs)
49
+ def wrapper(*fargs, **fkwargs) -> Out: # type: ignore
50
+ print(message)
32
51
 
33
- return wrapper
52
+ return func(*fargs, **fkwargs) # type: ignore
34
53
 
54
+ return cast(Callable[[In], Out], wrapper)
35
55
 
36
- def step_transition(func: Callable) -> Callable:
37
- @wraps(func)
38
- def wrapper(*fargs, **fkwargs):
39
- return func(*fargs, **fkwargs)
40
56
 
41
- return wrapper
57
+ def mutates_instance[In, Out](func: Callable[[In], Out], *mutated: str) -> Callable[[In], Out]:
58
+ message = (
59
+ f"\u001b[31mMutating in place\u001b[0m: "
60
+ f" via \u001b[33m{func.__name__:<25}\u001b[0m"
61
+ f" in \u001b[34m{func.__module__}\u001b[0m"
62
+ )
42
63
 
64
+ return decorate_with_message(func, message)
43
65
 
44
- def validator(func: Callable) -> Callable:
45
- @wraps(func)
46
- def wrapper(*fargs, **fkwargs):
47
- return func(*fargs, **fkwargs)
48
66
 
49
- return wrapper
67
+ def mutates_and_returns_instance[In, Out](
68
+ func: Callable[[In], Out],
69
+ *mutated: str,
70
+ ) -> Callable[[In], Out]:
71
+ message = (
72
+ f"\u001b[31mMutating\u001b[0m"
73
+ f"{(' ' * bool(mutated)) + ', '.join(mutated)}"
74
+ f" \u001b[31mand returning instance of\u001b[0m: {func.__class__.__name__}"
75
+ f" via \u001b[33m{func.__name__:<25}\u001b[0m"
76
+ f" in \u001b[34m{func.__module__}\u001b[0m"
77
+ )
50
78
 
79
+ return decorate_with_message(func, message)
51
80
 
52
- def mutates_instance(func: Callable) -> Callable:
53
- @wraps(func)
54
- def wrapper(*fargs, **fkwargs):
55
- # print(
56
- # (
57
- # f"\u001b[31mMutating in place\u001b[0m: "
58
- # f"\u001b[32m{type(fargs[0]).__name__:<25}\u001b[0m"
59
- # f" via \u001b[33m{func.__name__:<25}\u001b[0m"
60
- # f" in \u001b[34m{func.__module__}\u001b[0m"
61
- # )
62
- # )
63
- return func(*fargs, **fkwargs)
64
81
 
65
- return wrapper
82
+ def mutates[In, Out](func: Callable[[In], Out], *mutated: str) -> Callable[[In], Out]:
83
+ message = (
84
+ f"Mutating \u001b[36m{', '.join(mutated):<50}\u001b[0m"
85
+ f" via \u001b[33m{func.__name__:<25}\u001b[0m"
86
+ f" in \u001b[34m{func.__module__:<40}\u001b[0m"
87
+ )
66
88
 
89
+ return decorate_with_message(func, message)
67
90
 
68
- def mutates_and_returns_instance(func: Callable) -> Callable:
69
- @wraps(func)
70
- def wrapper(*fargs, **fkwargs):
71
- # print(
72
- # (
73
- # f"\u001b[31mMutating and returning\u001b[0m: "
74
- # f"\u001b[32m{type(fargs[0]).__name__:<25}\u001b[0m"
75
- # f" via \u001b[33m{func.__name__:<25}\u001b[0m"
76
- # f" in \u001b[34m{func.__module__}\u001b[0m"
77
- # )
78
- # )
79
- return func(*fargs, **fkwargs)
80
-
81
- return wrapper
82
-
83
-
84
- def mutates(*args, **kwargs) -> Callable:
85
- def decorator(func: Callable) -> Callable:
86
- @wraps(func)
87
- def wrapper(*fargs, **fkwargs):
88
- print(
89
- f"Mutating attributes \u001b[36m{', '.join(args):<50}\u001b[0m"
90
- f" of \u001b[32m{type(fargs[0]).__name__:<25}\u001b[0m"
91
- f" via \u001b[33m{func.__name__:<25}\u001b[0m"
92
- f" in \u001b[34m{func.__module__.replace('consilium.', '.'):<40}\u001b[0m"
93
- )
94
-
95
- return func(*fargs, **fkwargs)
96
-
97
- return wrapper
98
-
99
- return decorator
100
-
101
-
102
- def refactor(*args) -> Callable:
103
- def decorator(func: Callable) -> Callable:
104
- @wraps(func)
105
- def wrapper(*fargs, **fkwargs):
106
- print(
107
- f"\u001b[36mREFACTOR\u001b[0m"
108
- f" \u001b[33m{func.__name__}\u001b[0m"
109
- f" in \u001b[34m{func.__module__.replace('consilium.', '.')}\u001b[0m."
110
- f" Notes: \u001b[32m{', '.join(args)}\u001b[0m"
111
- )
112
-
113
- return func(*fargs, **fkwargs)
114
-
115
- return wrapper
116
-
117
- return decorator
91
+
92
+ def refactor[In, Out](func: Callable[[In], Out], message: str = "") -> Callable[[In], Out]:
93
+ message = (
94
+ f"\u001b[36mREFACTOR\u001b[0m"
95
+ f" \u001b[33m{func.__name__}\u001b[0m"
96
+ f" in \u001b[34m{func.__module__.replace('consilium.', '.')}\u001b[0m."
97
+ f"\n {message}"
98
+ )
99
+
100
+ return decorate_with_message(func, message)