fred.mlambda 0.1.0__tar.gz → 0.2.0__tar.gz

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 (23) hide show
  1. {fred_mlambda-0.1.0/src/main/fred.mlambda.egg-info → fred_mlambda-0.2.0}/PKG-INFO +1 -1
  2. fred_mlambda-0.2.0/src/main/fred/mlambda/_count.py +19 -0
  3. fred_mlambda-0.2.0/src/main/fred/mlambda/_rand.py +17 -0
  4. fred_mlambda-0.2.0/src/main/fred/mlambda/_strops.py +30 -0
  5. fred_mlambda-0.2.0/src/main/fred/mlambda/catalog.py +62 -0
  6. fred_mlambda-0.2.0/src/main/fred/mlambda/interface.py +36 -0
  7. fred_mlambda-0.2.0/src/main/fred/mlambda/parser.py +137 -0
  8. fred_mlambda-0.2.0/src/main/fred/mlambda/settings.py +26 -0
  9. fred_mlambda-0.2.0/src/main/fred/mlambda/version +1 -0
  10. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0/src/main/fred.mlambda.egg-info}/PKG-INFO +1 -1
  11. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/src/main/fred.mlambda.egg-info/SOURCES.txt +7 -0
  12. fred_mlambda-0.1.0/src/main/fred/mlambda/version +0 -1
  13. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/MANIFEST.in +0 -0
  14. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/README.md +0 -0
  15. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/requirements.txt +0 -0
  16. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/setup.cfg +0 -0
  17. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/setup.py +0 -0
  18. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/src/main/fred/mlambda/__init__.py +0 -0
  19. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/src/main/fred/mlambda/version.py +0 -0
  20. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/src/main/fred.mlambda.egg-info/dependency_links.txt +0 -0
  21. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/src/main/fred.mlambda.egg-info/entry_points.txt +0 -0
  22. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/src/main/fred.mlambda.egg-info/requires.txt +0 -0
  23. {fred_mlambda-0.1.0 → fred_mlambda-0.2.0}/src/main/fred.mlambda.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fred.mlambda
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: FRED-MLAMBDA
5
5
  Home-page: https://fred.fhr.tools
6
6
  Author: Fahera Research, Education, and Development
@@ -0,0 +1,19 @@
1
+ from typing import Any, Optional
2
+
3
+ from fred.settings import logger_manager
4
+
5
+
6
+ logger = logger_manager.get_logger(__name__)
7
+
8
+
9
+ def count(value: Any, fail: bool = False) -> int:
10
+ if hasattr(value, "__len__"):
11
+ return len(value)
12
+ from collections.abc import Sized
13
+ if isinstance(value, Sized):
14
+ return len(value)
15
+ error = f"Unknown type: {type(value)}"
16
+ logger.warning(error)
17
+ if fail:
18
+ raise ValueError(error)
19
+ return 0
@@ -0,0 +1,17 @@
1
+ import random
2
+ from typing import Any
3
+
4
+ from fred.settings import logger_manager
5
+
6
+
7
+ logger = logger_manager.get_logger(__name__)
8
+
9
+
10
+ def rand(*args, k=1, disable_autoflat: bool = False) -> list[Any]:
11
+ if not disable_autoflat and k == 1:
12
+ out, *_ = rand(*args, k=k, disable_autoflat=True)
13
+ return out
14
+ return random.choices(
15
+ population=args,
16
+ k=k,
17
+ )
@@ -0,0 +1,30 @@
1
+ from typing import Optional
2
+
3
+ from fred.settings import logger_manager
4
+
5
+
6
+ logger = logger_manager.get_logger(__name__)
7
+
8
+
9
+ def strops(string: str, ops: str, fail: bool = False) -> Optional[str]:
10
+ match ops:
11
+ case "lower":
12
+ return string.lower()
13
+ case "upper":
14
+ return string.upper()
15
+ case "title":
16
+ return string.title()
17
+ case "capitalize":
18
+ return string.capitalize()
19
+ case "strip":
20
+ return string.strip()
21
+ case "lstrip":
22
+ return string.lstrip()
23
+ case "rstrip":
24
+ return string.rstrip()
25
+ case _:
26
+ msg = f"Unknown operation: {ops}"
27
+ if fail:
28
+ raise ValueError(msg)
29
+ logger.warning(msg)
30
+ return None
@@ -0,0 +1,62 @@
1
+ from enum import Enum
2
+ from typing import Optional
3
+
4
+ from fred.settings import logger_manager
5
+ from fred.mlambda.settings import FRED_MLAMBDA_PARSED_ALIASES
6
+ from fred.mlambda.interface import MLambda
7
+
8
+ logger = logger_manager.get_logger(__name__)
9
+
10
+
11
+ class MLambdaCatalog(Enum):
12
+ STROPS = MLambda(
13
+ name="strops",
14
+ import_pattern="fred.mlambda._strops",
15
+ )
16
+ RAND = MLambda(
17
+ name="rand",
18
+ import_pattern="fred.mlambda._rand",
19
+ )
20
+
21
+ @classmethod
22
+ def keys(cls) -> list[str]:
23
+ return [
24
+ mem.name
25
+ for mem in cls
26
+ ]
27
+
28
+ @classmethod
29
+ def get_or_create(cls, target: str, fail: bool = False) -> Optional[MLambda]:
30
+ if "." in target:
31
+ *import_path, function_name = target.split(".")
32
+ return MLambda(
33
+ name=function_name,
34
+ import_pattern=".".join(import_path),
35
+ )
36
+ return cls.find(
37
+ alias=target,
38
+ fail=fail,
39
+ )
40
+
41
+ @classmethod
42
+ def find(cls, alias: str, fail: bool = False, disable_variants: bool = False) -> Optional[MLambda]:
43
+ if "." in alias:
44
+ logger.warning("The target is a dotpath, not an alias. Use MLambdaParser.from_string() instead.")
45
+ return None
46
+ variants: list[str] = [alias, alias.upper(), alias.lower()]
47
+ for variant in variants[:(1 if disable_variants else len(variants))]:
48
+ # Check if the target is an alias registered in the environment or defaults
49
+ if variant in FRED_MLAMBDA_PARSED_ALIASES:
50
+ *import_path, function_name = FRED_MLAMBDA_PARSED_ALIASES[variant].split(".")
51
+ return MLambda(
52
+ name=function_name,
53
+ import_pattern=".".join(import_path),
54
+ )
55
+ # Check if the alias is a registered MLambdaCatalog Enum
56
+ elif variant in cls.keys():
57
+ return cls[variant].value
58
+ error = f"Unknown MLambda: {alias}"
59
+ logger.warning(error)
60
+ if fail:
61
+ raise ValueError(error)
62
+ return None
@@ -0,0 +1,36 @@
1
+ from dataclasses import dataclass
2
+ from typing import Callable
3
+
4
+
5
+ @dataclass(frozen=True, slots=True)
6
+ class Arguments:
7
+ args: list
8
+ kwargs: dict
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class MLambda:
13
+ name: str
14
+ import_pattern: str
15
+
16
+ @property
17
+ def function(self) -> Callable:
18
+ import importlib
19
+ # Import the module
20
+ module = importlib.import_module(self.import_pattern)
21
+ # Get the function from the module
22
+ if not (fn := getattr(module, self.name, None)):
23
+ raise ValueError(f"Function {self.name} not found in module {self.import_pattern}")
24
+ return fn
25
+
26
+ def run(self, arguments: Arguments):
27
+ return self.function(*arguments.args, **arguments.kwargs)
28
+
29
+ def __call__(self, *args, **kwargs):
30
+ arguments = Arguments(
31
+ args=args,
32
+ kwargs=kwargs
33
+ )
34
+ return self.run(
35
+ arguments=arguments
36
+ )
@@ -0,0 +1,137 @@
1
+ import re
2
+ import io
3
+ import csv
4
+ from dataclasses import dataclass
5
+ from typing import Any, Callable, Union, Optional
6
+
7
+ from fred.mlambda.interface import Arguments, MLambda
8
+ from fred.mlambda.catalog import MLambdaCatalog
9
+
10
+ # Matches: ${path.to.function: param_line}
11
+ # Group 1 (dotpath): "path.to.function"
12
+ # Group 2 (param_line): "arg1,arg2,kwarg1=value1,..."
13
+ _MLAMBDA_PATTERN = re.compile(
14
+ r"^\$\{\s*(?P<funref>[A-Za-z_][A-Za-z0-9_.]*)\s*:\s*(?P<param_line>[^}]*)\}$"
15
+ )
16
+
17
+ # Supported type annotations via the "::" syntax, e.g. "42::int"
18
+ MLAMBDA_TYPES = Optional[Union[int, float, bool, str]]
19
+ _NULL_VALUES = ("null", "none", "")
20
+ _TYPE_CASTERS: dict[str, Callable] = {
21
+ "int": int,
22
+ "float": float,
23
+ "bool": lambda v: v.strip().lower() not in ("false", "0", "no", ""),
24
+ "str": str,
25
+ }
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class MLambdaParser:
30
+ mlambda: MLambda
31
+ arguments: Arguments
32
+
33
+ @staticmethod
34
+ def cast(raw: str, disable_autoinfer: bool = False) -> MLAMBDA_TYPES:
35
+ """
36
+ Parse a raw token string, applying an optional '::type' suffix.
37
+
38
+ Examples:
39
+ "hello" -> "hello" (str)
40
+ "42::int" -> 42 (int)
41
+ "3.14::float" -> 3.14 (float)
42
+ "true::bool" -> True (bool)
43
+ """
44
+ raw = raw.strip()
45
+ # Early exit for None values IF autoinfer is enabled
46
+ if not disable_autoinfer and raw.lower() in ("null", "none", ""):
47
+ return None
48
+ # Check for type annotation
49
+ if "::" in raw:
50
+ value_part, _, type_name = raw.rpartition("::")
51
+ caster = _TYPE_CASTERS.get(type_name.strip())
52
+ if caster is None:
53
+ raise ValueError(
54
+ f"Unknown type annotation '{type_name}'. "
55
+ f"Supported: {list(_TYPE_CASTERS)}"
56
+ )
57
+ return caster(value_part.strip())
58
+ if not disable_autoinfer and raw.isdigit():
59
+ return int(raw)
60
+ if not disable_autoinfer and raw.replace(".", "", 1).isdigit():
61
+ return float(raw)
62
+ if not disable_autoinfer and raw.lower() in ("true", "false"):
63
+ return raw.lower() == "true"
64
+ return raw
65
+
66
+ @classmethod
67
+ def parse_line(cls, param_line: str) -> tuple[list[MLAMBDA_TYPES], dict[str, MLAMBDA_TYPES]]:
68
+ """
69
+ Split a CSV-like parameter string into positional args and keyword args.
70
+
71
+ The CSV reader handles:
72
+ - Comma-separated tokens
73
+ - Quoted values (e.g. "hello, world" treated as a single token)
74
+
75
+ Each token is classified as:
76
+ - kwarg if it contains '=' (first '=' is the separator)
77
+ - positional arg otherwise
78
+
79
+ Type coercion via '::type' is applied to every value.
80
+ """
81
+ args: list[MLAMBDA_TYPES] = []
82
+ kwargs: dict[str, MLAMBDA_TYPES] = {}
83
+
84
+ if not param_line.strip():
85
+ return args, kwargs
86
+
87
+ reader = csv.reader(io.StringIO(param_line), skipinitialspace=True)
88
+ for row in reader:
89
+ for token in row:
90
+ token = token.strip()
91
+ if not token:
92
+ continue
93
+ if "=" in token:
94
+ key, _, raw_value = token.partition("=")
95
+ kwargs[key.strip()] = cls.cast(raw_value)
96
+ else:
97
+ args.append(cls.cast(token))
98
+
99
+ return args, kwargs
100
+
101
+ @classmethod
102
+ def from_string(cls, string: str) -> "MLambdaParser":
103
+ payload = string.strip()
104
+
105
+ match = _MLAMBDA_PATTERN.match(payload)
106
+ if not match:
107
+ raise ValueError(
108
+ f"Invalid MLambda expression: {payload!r}\n"
109
+ "Expected format: ${path.to.function: arg1,arg2,kwarg1=value1,...}"
110
+ )
111
+ # Get the function reference and param_line from the match
112
+ funref: str = match.group("funref")
113
+ param_line: str = match.group("param_line")
114
+ # Parse the CSV-like parameter line
115
+ args, kwargs = cls.parse_line(param_line)
116
+ arguments = Arguments(
117
+ args=args,
118
+ kwargs=kwargs,
119
+ )
120
+ # If the function reference is an alias, get the MLambda from the catalog
121
+ if "." not in funref:
122
+ return cls(
123
+ mlambda=MLambdaCatalog.get_or_create(funref, fail=True),
124
+ arguments=arguments,
125
+ )
126
+ # Split "path.to.function" -> import_pattern="path.to", fname="function"
127
+ import_pattern, fname = funref.rsplit(".", 1)
128
+ return cls(
129
+ mlambda=MLambda(
130
+ name=fname,
131
+ import_pattern=import_pattern
132
+ ),
133
+ arguments=arguments,
134
+ )
135
+
136
+ def execute(self) -> Any:
137
+ return self.mlambda.run(arguments=self.arguments)
@@ -0,0 +1,26 @@
1
+ import os
2
+
3
+ from fred.settings import get_environ_variable
4
+
5
+
6
+ FRED_MLAMBDA_ALIASES_SEP = get_environ_variable(
7
+ "FRED_MLAMBDA_ALIASES_SEP",
8
+ default=";"
9
+ )
10
+
11
+ FRED_MLAMBDA_ALIASES = [
12
+ alias
13
+ for line in get_environ_variable(
14
+ "FRED_MLAMBDA_ALIASES",
15
+ default="",
16
+ ).split(FRED_MLAMBDA_ALIASES_SEP)
17
+ if "=" in line and (alias := line.strip().split("="))
18
+ ]
19
+
20
+ FRED_MLAMBDA_PARSED_ALIASES = {
21
+ "count": "fred.mlambda._count.count",
22
+ **{
23
+ key: val
24
+ for key, val in FRED_MLAMBDA_ALIASES
25
+ }
26
+ }
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fred.mlambda
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: FRED-MLAMBDA
5
5
  Home-page: https://fred.fhr.tools
6
6
  Author: Fahera Research, Education, and Development
@@ -9,5 +9,12 @@ src/main/fred.mlambda.egg-info/entry_points.txt
9
9
  src/main/fred.mlambda.egg-info/requires.txt
10
10
  src/main/fred.mlambda.egg-info/top_level.txt
11
11
  src/main/fred/mlambda/__init__.py
12
+ src/main/fred/mlambda/_count.py
13
+ src/main/fred/mlambda/_rand.py
14
+ src/main/fred/mlambda/_strops.py
15
+ src/main/fred/mlambda/catalog.py
16
+ src/main/fred/mlambda/interface.py
17
+ src/main/fred/mlambda/parser.py
18
+ src/main/fred/mlambda/settings.py
12
19
  src/main/fred/mlambda/version
13
20
  src/main/fred/mlambda/version.py
@@ -1 +0,0 @@
1
- 0.1.0
File without changes
File without changes
File without changes
File without changes