patos 0.0.1__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.
- patos/__init__.py +25 -0
- patos/_catalog.py +65 -0
- patos/dispatch.py +119 -0
- patos/flags.py +49 -0
- patos/flyweight.py +31 -0
- patos/py.typed +0 -0
- patos/registry.py +65 -0
- patos/singleton.py +25 -0
- patos/strategy.py +126 -0
- patos/strflag.py +28 -0
- patos-0.0.1.dist-info/METADATA +105 -0
- patos-0.0.1.dist-info/RECORD +14 -0
- patos-0.0.1.dist-info/WHEEL +4 -0
- patos-0.0.1.dist-info/licenses/LICENSE +202 -0
patos/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
|
+
|
|
3
|
+
from .dispatch import value_dispatch
|
|
4
|
+
from .flags import flags
|
|
5
|
+
from .flyweight import Flyweight, FlyweightMeta
|
|
6
|
+
from .registry import Registry
|
|
7
|
+
from .singleton import Singleton
|
|
8
|
+
from .strategy import Strategy
|
|
9
|
+
from .strflag import StrFlag
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
__version__ = version("patos")
|
|
13
|
+
except PackageNotFoundError:
|
|
14
|
+
__version__ = "0.0.0"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Flyweight",
|
|
18
|
+
"FlyweightMeta",
|
|
19
|
+
"Registry",
|
|
20
|
+
"Singleton",
|
|
21
|
+
"StrFlag",
|
|
22
|
+
"Strategy",
|
|
23
|
+
"flags",
|
|
24
|
+
"value_dispatch",
|
|
25
|
+
]
|
patos/_catalog.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass(frozen=True, slots=True)
|
|
5
|
+
class Pato:
|
|
6
|
+
"""A single pattern in the flock, copyable as one self-contained module.
|
|
7
|
+
|
|
8
|
+
module: file name inside `patos/` (without directory), e.g. `registry.py`.
|
|
9
|
+
summary: one-line description shown by `patos list`.
|
|
10
|
+
exports: public symbols the module defines and the package re-exports.
|
|
11
|
+
extras: optional dependency groups this pato needs (empty for zero-dep patos).
|
|
12
|
+
doc: documentation page path under `docs/`, e.g. `patos/registry.md`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
module: str
|
|
16
|
+
summary: str
|
|
17
|
+
exports: tuple[str, ...]
|
|
18
|
+
extras: tuple[str, ...] = ()
|
|
19
|
+
doc: str = ""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
PATOS: dict[str, Pato] = {
|
|
23
|
+
"registry": Pato(
|
|
24
|
+
module="registry.py",
|
|
25
|
+
summary="Self-registering class hierarchies: subclasses enroll via __init_subclass__.",
|
|
26
|
+
exports=("Registry",),
|
|
27
|
+
doc="patos/registry.md",
|
|
28
|
+
),
|
|
29
|
+
"singleton": Pato(
|
|
30
|
+
module="singleton.py",
|
|
31
|
+
summary="One instance per class, __init__ runs once.",
|
|
32
|
+
exports=("Singleton",),
|
|
33
|
+
doc="patos/singleton.md",
|
|
34
|
+
),
|
|
35
|
+
"flyweight": Pato(
|
|
36
|
+
module="flyweight.py",
|
|
37
|
+
summary="Intern instances by constructor args (build-once, immutable).",
|
|
38
|
+
exports=("Flyweight", "FlyweightMeta"),
|
|
39
|
+
doc="patos/flyweight.md",
|
|
40
|
+
),
|
|
41
|
+
"strategy": Pato(
|
|
42
|
+
module="strategy.py",
|
|
43
|
+
summary="A named family of interchangeable implementations, selected at runtime.",
|
|
44
|
+
exports=("Strategy",),
|
|
45
|
+
doc="patos/strategy.md",
|
|
46
|
+
),
|
|
47
|
+
"dispatch": Pato(
|
|
48
|
+
module="dispatch.py",
|
|
49
|
+
summary="Dispatch on a value the way singledispatch dispatches on a type.",
|
|
50
|
+
exports=("value_dispatch",),
|
|
51
|
+
doc="patos/dispatch.md",
|
|
52
|
+
),
|
|
53
|
+
"flags": Pato(
|
|
54
|
+
module="flags.py",
|
|
55
|
+
summary="Turn keyword options into a CLI argv tuple.",
|
|
56
|
+
exports=("flags",),
|
|
57
|
+
doc="patos/flags.md",
|
|
58
|
+
),
|
|
59
|
+
"strflag": Pato(
|
|
60
|
+
module="strflag.py",
|
|
61
|
+
summary="An enum Flag whose members carry a literal string, OR-combinable and iterable.",
|
|
62
|
+
exports=("StrFlag",),
|
|
63
|
+
doc="patos/strflag.md",
|
|
64
|
+
),
|
|
65
|
+
}
|
patos/dispatch.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
from collections.abc import Callable, Hashable
|
|
5
|
+
from types import MappingProxyType
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class value_dispatch:
|
|
10
|
+
"""Turn a function into a value-dispatched generic.
|
|
11
|
+
|
|
12
|
+
Like `functools.singledispatch`, but the dispatch key is the *value* of a
|
|
13
|
+
keyword argument (default `"kind"`) rather than the *type* of the first
|
|
14
|
+
positional argument. Both decorator forms work:
|
|
15
|
+
|
|
16
|
+
@value_dispatch
|
|
17
|
+
def render(node, **kw): ... # bare: kind is "kind"
|
|
18
|
+
|
|
19
|
+
@value_dispatch(kind="how")
|
|
20
|
+
def render(node, **kw): ... # kind is "how"
|
|
21
|
+
|
|
22
|
+
Implementations register under a key with `@render.register`. Each is also
|
|
23
|
+
exposed as an attribute (`render.html`) so callers that already know the
|
|
24
|
+
kind can skip the registry lookup. Calling with no kind (or `kind=None`)
|
|
25
|
+
falls back to the originally decorated function.
|
|
26
|
+
|
|
27
|
+
fallback: the wrapped function; supplied implicitly by the decorator.
|
|
28
|
+
kind: name of the keyword argument carrying the dispatch value.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
fallback: Callable[..., Any] | None = None,
|
|
34
|
+
*,
|
|
35
|
+
kind: str = "kind",
|
|
36
|
+
) -> None:
|
|
37
|
+
self.kind_arg = kind
|
|
38
|
+
self.registry_map: dict[Hashable, Callable[..., Any]] = {}
|
|
39
|
+
self.fallback: Callable[..., Any] | None = None
|
|
40
|
+
if fallback is not None:
|
|
41
|
+
self.bind_fallback(fallback)
|
|
42
|
+
|
|
43
|
+
def bind_fallback(self, fallback: Callable[..., Any]) -> None:
|
|
44
|
+
"""Adopt `fallback` as the default impl and copy its metadata onto self."""
|
|
45
|
+
self.fallback = fallback
|
|
46
|
+
functools.update_wrapper(self, fallback)
|
|
47
|
+
|
|
48
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
49
|
+
"""Bind the fallback on the parametrised first call, otherwise dispatch.
|
|
50
|
+
|
|
51
|
+
With no fallback yet, this is the parametrised-decorator stage: the sole
|
|
52
|
+
argument is the function to wrap. Once bound, pop the kind keyword and
|
|
53
|
+
route to its registered impl, falling back when the kind is absent.
|
|
54
|
+
"""
|
|
55
|
+
if self.fallback is None:
|
|
56
|
+
self.bind_fallback(args[0])
|
|
57
|
+
return self
|
|
58
|
+
value = kwargs.pop(self.kind_arg, None)
|
|
59
|
+
if value is None:
|
|
60
|
+
return self.fallback(*args, **kwargs)
|
|
61
|
+
try:
|
|
62
|
+
impl = self.registry_map[value]
|
|
63
|
+
except KeyError:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"{self.fallback.__name__}: unknown {self.kind_arg}={value!r}; "
|
|
66
|
+
f"choose from {sorted(self.registry_map, key=repr)}",
|
|
67
|
+
) from None
|
|
68
|
+
return impl(*args, **kwargs)
|
|
69
|
+
|
|
70
|
+
def register(
|
|
71
|
+
self,
|
|
72
|
+
arg: Hashable | Callable[..., Any] | None = None,
|
|
73
|
+
*,
|
|
74
|
+
name: Hashable | None = None,
|
|
75
|
+
) -> Callable[..., Any] | Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
76
|
+
"""Register an implementation under a kind value.
|
|
77
|
+
|
|
78
|
+
Three forms:
|
|
79
|
+
- `@register` bare: key is the function's `__name__`.
|
|
80
|
+
- `@register("foo")` or `@register(name="foo")`: explicit key.
|
|
81
|
+
- `register(existing, name="alias")`: register an existing function.
|
|
82
|
+
|
|
83
|
+
When the key is a valid identifier the impl is also set as a dispatcher
|
|
84
|
+
attribute, enabling direct `dispatcher.foo(...)` calls.
|
|
85
|
+
|
|
86
|
+
arg: explicit key, or the function itself in the bare/direct forms.
|
|
87
|
+
name: explicit key, taking precedence over `arg` and the `__name__`.
|
|
88
|
+
"""
|
|
89
|
+
if callable(arg):
|
|
90
|
+
return self.bind(arg, arg.__name__ if name is None else name)
|
|
91
|
+
key = arg if name is None else name
|
|
92
|
+
|
|
93
|
+
def decorate(impl: Callable[..., Any]) -> Callable[..., Any]:
|
|
94
|
+
return self.bind(impl, impl.__name__ if key is None else key)
|
|
95
|
+
|
|
96
|
+
return decorate
|
|
97
|
+
|
|
98
|
+
def bind(self, impl: Callable[..., Any], key: Hashable) -> Callable[..., Any]:
|
|
99
|
+
"""Store `impl` under `key`, also exposing it as an attribute when key is an identifier."""
|
|
100
|
+
self.registry_map[key] = impl
|
|
101
|
+
if isinstance(key, str) and key.isidentifier():
|
|
102
|
+
setattr(self, key, impl)
|
|
103
|
+
return impl
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def registry(self) -> MappingProxyType[Hashable, Callable[..., Any]]:
|
|
107
|
+
"""Read-only view of the kind to impl mapping."""
|
|
108
|
+
return MappingProxyType(self.registry_map)
|
|
109
|
+
|
|
110
|
+
def kinds(self) -> list[Hashable]:
|
|
111
|
+
"""All registered kinds, sorted by their `repr` so mixed-type keys stay orderable."""
|
|
112
|
+
return sorted(self.registry_map, key=repr)
|
|
113
|
+
|
|
114
|
+
def __contains__(self, key: Hashable) -> bool:
|
|
115
|
+
return key in self.registry_map
|
|
116
|
+
|
|
117
|
+
def __repr__(self) -> str:
|
|
118
|
+
name = self.fallback.__name__ if self.fallback else "(unbound)"
|
|
119
|
+
return f"<value_dispatch {name!r} kinds={self.kinds()}>"
|
patos/flags.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import TypeAlias
|
|
5
|
+
|
|
6
|
+
FlagValue: TypeAlias = bool | int | str | Sequence[str] | None
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def flags(
|
|
10
|
+
*,
|
|
11
|
+
joined: bool = False,
|
|
12
|
+
separator: str | None = None,
|
|
13
|
+
**options: FlagValue,
|
|
14
|
+
) -> tuple[str, ...]:
|
|
15
|
+
"""Turn keyword options into a CLI argv tuple, dropping the empty ones.
|
|
16
|
+
|
|
17
|
+
Keys become long flags with underscores hyphenated (`mem_gb` -> `--mem-gb`).
|
|
18
|
+
Rendering by value type:
|
|
19
|
+
|
|
20
|
+
- `None`, `False`, `""`: dropped entirely.
|
|
21
|
+
- `True`: a bare `--flag`.
|
|
22
|
+
- `int` / `str`: `--flag value` (or `--flag=value` when `joined`).
|
|
23
|
+
- sequence: by default one `--flag value` pair per item; with `separator`
|
|
24
|
+
a single `--flag a,b,c` joined on `separator`. Empty sequences drop.
|
|
25
|
+
|
|
26
|
+
joined: emit `--flag=value` single tokens instead of `--flag` then `value`.
|
|
27
|
+
separator: join sequence values into one flag instead of repeating it.
|
|
28
|
+
options: the flag name to value mapping.
|
|
29
|
+
"""
|
|
30
|
+
argv: list[str] = []
|
|
31
|
+
for key, value in options.items():
|
|
32
|
+
flag = f"--{key.replace('_', '-')}"
|
|
33
|
+
if value is None or value is False or value == "":
|
|
34
|
+
continue
|
|
35
|
+
if value is True:
|
|
36
|
+
argv.append(flag)
|
|
37
|
+
elif isinstance(value, str | int):
|
|
38
|
+
argv.extend(emit(flag, str(value), joined=joined))
|
|
39
|
+
elif separator is not None:
|
|
40
|
+
argv.extend(emit(flag, separator.join(value), joined=joined))
|
|
41
|
+
else:
|
|
42
|
+
for item in value:
|
|
43
|
+
argv.extend(emit(flag, item, joined=joined))
|
|
44
|
+
return tuple(argv)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def emit(flag: str, value: str, *, joined: bool) -> tuple[str, ...]:
|
|
48
|
+
"""Render one flag/value pair as `("--flag=value",)` or `("--flag", "value")`."""
|
|
49
|
+
return (f"{flag}={value}",) if joined else (flag, value)
|
patos/flyweight.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from abc import ABCMeta
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FlyweightMeta(ABCMeta):
|
|
6
|
+
"""Metaclass that interns instances by their construction arguments.
|
|
7
|
+
|
|
8
|
+
The first construction with a given `(args, kwargs)` builds and caches the instance; every
|
|
9
|
+
later construction with equal arguments returns that same object without re-running
|
|
10
|
+
`__init__` -- the metaclass owns `__call__`, so no re-entrancy guard is needed (unlike
|
|
11
|
+
caching `__new__`, which still re-runs `__init__`). Each class keeps its own cache and
|
|
12
|
+
arguments must be hashable. Subclasses `ABCMeta` so a flyweight can also be an abstract
|
|
13
|
+
base or carry abstract methods.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
|
|
17
|
+
cache: dict[tuple[Any, ...], Any] = cls.__dict__.get("flyweights", {})
|
|
18
|
+
if "flyweights" not in cls.__dict__:
|
|
19
|
+
cls.flyweights = cache
|
|
20
|
+
key = (args, frozenset(kwargs.items()))
|
|
21
|
+
if key not in cache:
|
|
22
|
+
cache[key] = super().__call__(*args, **kwargs)
|
|
23
|
+
return cache[key]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Flyweight(metaclass=FlyweightMeta):
|
|
27
|
+
"""Base for interned, build-once value objects: equal constructor arguments share one
|
|
28
|
+
instance (the flyweight / multiton pattern). Fits objects that are expensive to build and
|
|
29
|
+
immutable once built -- a lattice codebook, a trellis -- so a sweep or repeated
|
|
30
|
+
construction pays the build cost once. `Singleton` is the degenerate no-argument case.
|
|
31
|
+
"""
|
patos/py.typed
ADDED
|
File without changes
|
patos/registry.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import Any, ClassVar, Self, cast
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Registry:
|
|
5
|
+
"""Mixin for self-registering class hierarchies with try-each dispatch.
|
|
6
|
+
|
|
7
|
+
Each direct child of `Registry` becomes a registry root that owns the list of its
|
|
8
|
+
own concrete subclasses. `registry()` lists implementations; `dispatch` tries each
|
|
9
|
+
registered implementation's `from_dispatch` until one succeeds.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
registry_entries: ClassVar[list[type["Registry"]]]
|
|
13
|
+
|
|
14
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
15
|
+
"""Register `cls` as a root (if a direct child) and into its nearest root."""
|
|
16
|
+
super().__init_subclass__(**kwargs)
|
|
17
|
+
|
|
18
|
+
if Registry in cls.__bases__:
|
|
19
|
+
cls.registry_entries = []
|
|
20
|
+
|
|
21
|
+
for base in cls.__mro__:
|
|
22
|
+
if "registry_entries" in base.__dict__:
|
|
23
|
+
base.registry_entries.append(cls)
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def registry(cls) -> list[type["Registry"]]:
|
|
28
|
+
"""Return the implementation list owned by this class's nearest registry root."""
|
|
29
|
+
for base in cls.__mro__:
|
|
30
|
+
if "registry_entries" in base.__dict__:
|
|
31
|
+
return base.registry_entries
|
|
32
|
+
raise RuntimeError(f"No registry found for {cls.__name__}.")
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def is_registry_root(cls) -> bool:
|
|
36
|
+
"""Return whether this class owns a registry."""
|
|
37
|
+
return "registry_entries" in cls.__dict__
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def dispatch(cls, *args: Any, **kwargs: Any) -> Self:
|
|
41
|
+
"""Try each registered implementation's `from_dispatch`, returning the first success.
|
|
42
|
+
|
|
43
|
+
On a registry root, every other registered implementation is tried in order and the
|
|
44
|
+
last raised error is re-raised if all fail; off a root, `cls` dispatches directly.
|
|
45
|
+
"""
|
|
46
|
+
if not cls.is_registry_root():
|
|
47
|
+
return cls.from_dispatch(*args, **kwargs)
|
|
48
|
+
|
|
49
|
+
last_error: Exception | None = None
|
|
50
|
+
for subclass in cls.registry():
|
|
51
|
+
if subclass is cls:
|
|
52
|
+
continue
|
|
53
|
+
try:
|
|
54
|
+
return cast(Self, subclass.from_dispatch(*args, **kwargs))
|
|
55
|
+
except Exception as error:
|
|
56
|
+
last_error = error
|
|
57
|
+
|
|
58
|
+
if last_error is not None:
|
|
59
|
+
raise last_error
|
|
60
|
+
raise RuntimeError(f"No implementation found in {cls.__name__} registry.")
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def from_dispatch(cls, *args: Any, **kwargs: Any) -> Self:
|
|
64
|
+
"""Build an instance from dispatch arguments; subclasses override this."""
|
|
65
|
+
raise NotImplementedError(f"{cls.__name__} does not implement from_dispatch.")
|
patos/singleton.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SingletonMeta(type):
|
|
5
|
+
"""Metaclass that yields one instance per class, building it at most once.
|
|
6
|
+
|
|
7
|
+
Owning `__call__` means the cached instance is returned without re-running `__init__`,
|
|
8
|
+
avoiding the wart of caching `__new__` (which re-initialises on every call). Each class
|
|
9
|
+
keeps its own instance, so subclasses are independent singletons.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
instances: ClassVar[dict[type, Any]] = {}
|
|
13
|
+
|
|
14
|
+
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
|
|
15
|
+
if cls not in SingletonMeta.instances:
|
|
16
|
+
SingletonMeta.instances[cls] = super().__call__(*args, **kwargs)
|
|
17
|
+
return SingletonMeta.instances[cls]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Singleton(metaclass=SingletonMeta):
|
|
21
|
+
"""Base for single-instance classes: every construction returns the same object.
|
|
22
|
+
|
|
23
|
+
`__init__` runs only on the first construction, so later calls never re-initialise the
|
|
24
|
+
shared instance. The no-argument degenerate case of the flyweight pattern.
|
|
25
|
+
"""
|
patos/strategy.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Iterator
|
|
4
|
+
from typing import Generic, Protocol, TypeVar, runtime_checkable
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@runtime_checkable
|
|
10
|
+
class Available(Protocol):
|
|
11
|
+
"""An implementation that can report whether it applies on the current host.
|
|
12
|
+
|
|
13
|
+
Implementations may expose `available()` for first-available selection. Those
|
|
14
|
+
that do not are treated as always available, so plain value objects need no
|
|
15
|
+
boilerplate to participate in named selection.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def available(self) -> bool:
|
|
19
|
+
"""Whether this implementation should be chosen in `first_available` mode."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class StrategyError(KeyError):
|
|
23
|
+
"""Raised when a name is missing with no default, or no impl is available."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Strategy(Generic[T]):
|
|
27
|
+
"""A family of interchangeable named implementations with runtime selection.
|
|
28
|
+
|
|
29
|
+
`Strategy` replaces the bespoke `{"a": A(), "b": B()}.get(kind, default)` and
|
|
30
|
+
"pick the first thing that works" dispatch tables scattered across services.
|
|
31
|
+
Implementations register by name, eagerly (a value) or lazily (a zero-arg
|
|
32
|
+
factory called on first use and cached). Two selection modes:
|
|
33
|
+
|
|
34
|
+
- `select(name, default=...)`: keyed lookup, like `dict.get` but raising a
|
|
35
|
+
clear `StrategyError` instead of returning `None` when nothing matches.
|
|
36
|
+
- `first_available()`: walk registrations in insertion order and return the
|
|
37
|
+
first whose `available()` predicate is true (impls without one always are).
|
|
38
|
+
|
|
39
|
+
It is generic over the implementation or Protocol type, so `Strategy[Scheduler]`
|
|
40
|
+
keeps `select`/`first_available` returning `Scheduler`.
|
|
41
|
+
|
|
42
|
+
name: human-readable label used in error messages.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, name: str = "strategy") -> None:
|
|
46
|
+
self.name = name
|
|
47
|
+
self.factories: dict[str, Callable[[], T]] = {}
|
|
48
|
+
self.cache: dict[str, T] = {}
|
|
49
|
+
|
|
50
|
+
def register(self, name: str, impl: T) -> None:
|
|
51
|
+
"""Register an already-built implementation value under `name`.
|
|
52
|
+
|
|
53
|
+
impl: the implementation; stored as-is and returned by `select`/`first_available`.
|
|
54
|
+
"""
|
|
55
|
+
self.cache[name] = impl
|
|
56
|
+
self.factories[name] = lambda: impl
|
|
57
|
+
|
|
58
|
+
def factory(self, name: str, build: Callable[[], T]) -> None:
|
|
59
|
+
"""Register a zero-arg factory under `name`, built lazily on first resolution.
|
|
60
|
+
|
|
61
|
+
name: the implementation key.
|
|
62
|
+
build: produces the implementation the first time it is selected; cached after.
|
|
63
|
+
"""
|
|
64
|
+
self.factories[name] = build
|
|
65
|
+
|
|
66
|
+
def add(self, name: str) -> Callable[[Callable[[], T]], Callable[[], T]]:
|
|
67
|
+
"""Decorator form of `factory`, registering the decorated zero-arg builder under `name`."""
|
|
68
|
+
|
|
69
|
+
def decorate(build: Callable[[], T]) -> Callable[[], T]:
|
|
70
|
+
self.factory(name, build)
|
|
71
|
+
return build
|
|
72
|
+
|
|
73
|
+
return decorate
|
|
74
|
+
|
|
75
|
+
def resolve(self, name: str) -> T:
|
|
76
|
+
"""Build (once) and return the implementation registered under `name`."""
|
|
77
|
+
try:
|
|
78
|
+
return self.cache[name]
|
|
79
|
+
except KeyError:
|
|
80
|
+
self.cache[name] = self.factories[name]()
|
|
81
|
+
return self.cache[name]
|
|
82
|
+
|
|
83
|
+
def select(self, name: str, default: str | None = None) -> T:
|
|
84
|
+
"""Return the impl for `name`, or for `default`, raising if neither is registered.
|
|
85
|
+
|
|
86
|
+
name: the requested implementation key.
|
|
87
|
+
default: fallback key used when `name` is unknown; `None` means no fallback.
|
|
88
|
+
"""
|
|
89
|
+
if name in self.factories:
|
|
90
|
+
return self.resolve(name)
|
|
91
|
+
if default is not None and default in self.factories:
|
|
92
|
+
return self.resolve(default)
|
|
93
|
+
raise StrategyError(
|
|
94
|
+
f"{self.name}: no implementation for {name!r}; choose from {sorted(self.factories)}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def first_available(self) -> T:
|
|
98
|
+
"""Return the first registered impl whose `available()` is true, in insertion order.
|
|
99
|
+
|
|
100
|
+
Implementations that do not expose `available()` count as always available,
|
|
101
|
+
so a plain default placed last is the catch-all.
|
|
102
|
+
"""
|
|
103
|
+
for name in self.factories:
|
|
104
|
+
impl = self.resolve(name)
|
|
105
|
+
if not isinstance(impl, Available) or impl.available():
|
|
106
|
+
return impl
|
|
107
|
+
raise StrategyError(
|
|
108
|
+
f"{self.name}: no available implementation among {sorted(self.factories)}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def names(self) -> tuple[str, ...]:
|
|
113
|
+
"""Registered names in insertion order."""
|
|
114
|
+
return tuple(self.factories)
|
|
115
|
+
|
|
116
|
+
def __contains__(self, name: str) -> bool:
|
|
117
|
+
return name in self.factories
|
|
118
|
+
|
|
119
|
+
def __len__(self) -> int:
|
|
120
|
+
return len(self.factories)
|
|
121
|
+
|
|
122
|
+
def __iter__(self) -> Iterator[str]:
|
|
123
|
+
return iter(self.factories)
|
|
124
|
+
|
|
125
|
+
def __repr__(self) -> str:
|
|
126
|
+
return f"<Strategy {self.name!r} names={list(self.factories)}>"
|
patos/strflag.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Flag
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StrFlag(Flag):
|
|
7
|
+
"""A ``Flag`` whose members carry a literal string, kept OR-combinable and iterable.
|
|
8
|
+
|
|
9
|
+
Each member gets the next power-of-two value (so members compose with ``|`` and iterate)
|
|
10
|
+
while the declared string lives on ``.string``. Handy for building command-line option
|
|
11
|
+
sets where the enum is the vocabulary and ``.string`` is what you emit.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
class Opt(StrFlag):
|
|
15
|
+
ALL = "-a"
|
|
16
|
+
BIG = "--big"
|
|
17
|
+
|
|
18
|
+
[m.string for m in (Opt.ALL | Opt.BIG)] # ['-a', '--big']
|
|
19
|
+
```
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
string: str
|
|
23
|
+
|
|
24
|
+
def __new__(cls, string: str) -> StrFlag:
|
|
25
|
+
member = object.__new__(cls)
|
|
26
|
+
member._value_ = 1 << len(cls.__members__)
|
|
27
|
+
member.string = string
|
|
28
|
+
return member
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: patos
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Typed Python design and dispatch patterns, pip one in or copy one in and own the file
|
|
5
|
+
Project-URL: Homepage, https://phvv.me/patos
|
|
6
|
+
Project-URL: Repository, https://github.com/phvv-me/patos
|
|
7
|
+
Project-URL: Issues, https://github.com/phvv-me/patos/issues
|
|
8
|
+
Author-email: Pedro Valois <contact@phvv.me>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: hypothesis>=6; extra == 'dev'
|
|
23
|
+
Requires-Dist: pyrefly>=1; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest-randomly>=3; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=9; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.15; extra == 'dev'
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: mkdocs-llmstxt>=0.2; extra == 'docs'
|
|
30
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
31
|
+
Requires-Dist: mkdocs-static-i18n>=1.2; extra == 'docs'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# patos
|
|
35
|
+
|
|
36
|
+
[](https://github.com/phvv-me/patos/actions/workflows/ci.yml)
|
|
37
|
+
[](https://phvv.me/patos)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
[🇧🇷](https://phvv.me/patos/pt-BR/) [🇲🇽](https://phvv.me/patos/es/) [🇯🇵](https://phvv.me/patos/ja/) [🇨🇳](https://phvv.me/patos/zh/)
|
|
41
|
+
|
|
42
|
+
A flock of typed Python patterns. Pip one in, or copy one in and own the file.
|
|
43
|
+
|
|
44
|
+
Each pattern is a single self-contained `pato`, a duck you adopt. Every duck is fully typed,
|
|
45
|
+
standard library only, and small enough to read in one sitting.
|
|
46
|
+
|
|
47
|
+
## Two ways to use a pato
|
|
48
|
+
|
|
49
|
+
Install the whole flock and import the patterns you want.
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
pip install patos
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from patos import Registry
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or copy a single duck. Open its docs page, copy the source from the Source section, and paste it
|
|
60
|
+
into your own project. No dependency, no version pin, no tool, just one module you own.
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from patos import Singleton
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Settings(Singleton):
|
|
69
|
+
def __init__(self) -> None:
|
|
70
|
+
self.debug = False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
a = Settings()
|
|
74
|
+
a.debug = True
|
|
75
|
+
b = Settings() # same object, __init__ did not run again
|
|
76
|
+
|
|
77
|
+
assert a is b and b.debug
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## The flock
|
|
81
|
+
|
|
82
|
+
| pato | does |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `registry` | self-registering class hierarchies via `__init_subclass__` |
|
|
85
|
+
| `singleton` | one instance per class, `__init__` runs once |
|
|
86
|
+
| `flyweight` | intern instances by constructor args, build-once and immutable |
|
|
87
|
+
| `strategy` | a named family of interchangeable implementations |
|
|
88
|
+
| `dispatch` | dispatch on a value the way `singledispatch` dispatches on a type |
|
|
89
|
+
| `flags` | turn keyword options into a CLI argv tuple |
|
|
90
|
+
| `strflag` | an enum `Flag` whose members carry a literal string, OR-combinable and iterable |
|
|
91
|
+
|
|
92
|
+
## Documentation
|
|
93
|
+
|
|
94
|
+
Full documentation lives at [https://phvv.me/patos](https://phvv.me/patos).
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
This project's dev environment is managed by [chefe](https://phvv.me/chefe).
|
|
99
|
+
|
|
100
|
+
- Install: `chefe install`
|
|
101
|
+
- Lint: `chefe run lint`
|
|
102
|
+
- Typecheck: `chefe run typecheck`
|
|
103
|
+
- Test: `chefe run test`
|
|
104
|
+
- Docs: `chefe run docs`
|
|
105
|
+
- Build: `uv build`
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
patos/__init__.py,sha256=Vp2leXMBS-fF4LRPoBaRHXLmzEohkUvvXesF1eJcdtw,544
|
|
2
|
+
patos/_catalog.py,sha256=Xf5YmF_1iQ_0EEM2KRbK6j7_EOC86bK1BMmS7UkuE7o,2134
|
|
3
|
+
patos/dispatch.py,sha256=Q1pGAfKqqeXEYCTOePXGNkFf4FjwdWAjoenUg47VnfE,4720
|
|
4
|
+
patos/flags.py,sha256=tvdFwikkDYwK9bubTtGO6hw5GtR6n3BvQZfXs9TmRqE,1800
|
|
5
|
+
patos/flyweight.py,sha256=KNqFQ9CqHMKkvVnTm7PbMEmx0-QiQNqe1W4xRxhGcL0,1444
|
|
6
|
+
patos/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
patos/registry.py,sha256=93vZbn3drmSs_bo6yswUZZOJTwSnbd5q3sVXFWZ49Ps,2553
|
|
8
|
+
patos/singleton.py,sha256=wARFaicksCnRQRTeIAec4En2Dfnv4Q7pgy6M-2ey5sw,984
|
|
9
|
+
patos/strategy.py,sha256=1uL5Begz0T2exf-hFpxZHOraWy-A5JxM4UtWfpAs2Ik,4804
|
|
10
|
+
patos/strflag.py,sha256=xsQC4AxB4fRl5Ow15qC3XQvxjWhjC0tXwxq9ZcNcAm0,796
|
|
11
|
+
patos-0.0.1.dist-info/METADATA,sha256=TyL9-l-4cUtJ81XKtESKAS2B-YCx7TZ8ne3Vid32IKA,3605
|
|
12
|
+
patos-0.0.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
13
|
+
patos-0.0.1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
14
|
+
patos-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|