commit
006c0a494d
25 changed files with 196 additions and 319 deletions
|
|
@ -1,88 +0,0 @@
|
||||||
from typing import final
|
|
||||||
|
|
||||||
from pydantic import Field
|
|
||||||
|
|
||||||
from snakia.core.ecs import Component
|
|
||||||
from snakia.core.ecs.system import System
|
|
||||||
from snakia.core.engine import Engine
|
|
||||||
from snakia.core.es import Event
|
|
||||||
from snakia.core.loader import Meta, Plugin, PluginProcessor
|
|
||||||
from snakia.types import Version
|
|
||||||
|
|
||||||
|
|
||||||
class HealthComponent(Component):
|
|
||||||
max_value: int = Field(default=100, ge=0)
|
|
||||||
value: int = Field(default=100, ge=0)
|
|
||||||
|
|
||||||
|
|
||||||
class DamageComponent(Component):
|
|
||||||
damage: int = Field(ge=0)
|
|
||||||
ticks: int = Field(default=1, ge=0)
|
|
||||||
|
|
||||||
|
|
||||||
class HealComponent(Component):
|
|
||||||
heal: int = Field(ge=0)
|
|
||||||
ticks: int = Field(default=1, ge=0)
|
|
||||||
|
|
||||||
|
|
||||||
class DeathEvent(Event):
|
|
||||||
entity: int = Field()
|
|
||||||
|
|
||||||
|
|
||||||
class HealthProcessor(PluginProcessor):
|
|
||||||
def process(self, system: System) -> None:
|
|
||||||
for entity, (heal, health) in system.get_components(
|
|
||||||
HealComponent, HealthComponent
|
|
||||||
):
|
|
||||||
health.value += heal.heal
|
|
||||||
heal.ticks -= 1
|
|
||||||
if heal.ticks <= 0:
|
|
||||||
system.remove_component(entity, HealComponent)
|
|
||||||
for entity, (damage, health) in system.get_components(
|
|
||||||
DamageComponent, HealthComponent
|
|
||||||
):
|
|
||||||
health.value -= damage.damage
|
|
||||||
damage.ticks -= 1
|
|
||||||
if damage.ticks <= 0:
|
|
||||||
system.remove_component(entity, DamageComponent)
|
|
||||||
if health.value <= 0:
|
|
||||||
system.remove_component(entity, HealthComponent)
|
|
||||||
self.plugin.dispatcher.publish(DeathEvent(entity=entity))
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
class HealthPlugin(
|
|
||||||
Plugin,
|
|
||||||
meta=Meta(
|
|
||||||
name="health",
|
|
||||||
author="snakia",
|
|
||||||
version=Version.from_args(1, 0, 0),
|
|
||||||
subscribers=(),
|
|
||||||
processors=(HealthProcessor,),
|
|
||||||
),
|
|
||||||
):
|
|
||||||
def on_load(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_unload(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
engine = Engine()
|
|
||||||
engine.loader.register(HealthPlugin)
|
|
||||||
engine.loader.load_all()
|
|
||||||
|
|
||||||
@engine.dispatcher.on(DeathEvent)
|
|
||||||
def on_death(event: DeathEvent) -> None:
|
|
||||||
print(f"Entity: {event.entity} is death!")
|
|
||||||
|
|
||||||
player = engine.system.create_entity()
|
|
||||||
engine.system.add_component(player, HealthComponent())
|
|
||||||
engine.system.add_component(player, DamageComponent(damage=10, ticks=10))
|
|
||||||
|
|
||||||
engine.start()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
0
py.typed
Normal file
0
py.typed
Normal file
|
|
@ -26,7 +26,7 @@ dependencies = [
|
||||||
"types-networkx>=3.5.0.20251106",
|
"types-networkx>=3.5.0.20251106",
|
||||||
"typing-extensions>=4.15.0",
|
"typing-extensions>=4.15.0",
|
||||||
]
|
]
|
||||||
license = "CC0-1.0"
|
license = "Unlicense"
|
||||||
license-files = ["LICENSE"]
|
license-files = ["LICENSE"]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,12 @@ from typing import Final
|
||||||
|
|
||||||
from .ecs import System
|
from .ecs import System
|
||||||
from .es import Dispatcher
|
from .es import Dispatcher
|
||||||
from .loader.loader import Loader
|
|
||||||
|
|
||||||
|
|
||||||
class Engine:
|
class Engine:
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
"system",
|
"system",
|
||||||
"dispatcher",
|
"dispatcher",
|
||||||
"loader",
|
|
||||||
"__system_thread",
|
"__system_thread",
|
||||||
"__dispatcher_thread",
|
"__dispatcher_thread",
|
||||||
)
|
)
|
||||||
|
|
@ -18,12 +16,13 @@ class Engine:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.system: Final = System()
|
self.system: Final = System()
|
||||||
self.dispatcher: Final = Dispatcher()
|
self.dispatcher: Final = Dispatcher()
|
||||||
self.loader: Final = Loader(self)
|
|
||||||
self.__system_thread: threading.Thread | None = None
|
self.__system_thread: threading.Thread | None = None
|
||||||
self.__dispatcher_thread: threading.Thread | None = None
|
self.__dispatcher_thread: threading.Thread | None = None
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
self.__system_thread = threading.Thread(target=self.system.start, daemon=False)
|
self.__system_thread = threading.Thread(
|
||||||
|
target=self.system.start, daemon=False
|
||||||
|
)
|
||||||
self.__dispatcher_thread = threading.Thread(
|
self.__dispatcher_thread = threading.Thread(
|
||||||
target=self.dispatcher.start, daemon=False
|
target=self.dispatcher.start, daemon=False
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
from .loadable import Loadable
|
|
||||||
from .meta import Meta
|
|
||||||
from .plugin import Plugin
|
|
||||||
from .plugin_processor import PluginProcessor
|
|
||||||
|
|
||||||
__all__ = ["Loadable", "Meta", "Plugin", "PluginProcessor"]
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from snakia.core.engine import Engine
|
|
||||||
|
|
||||||
|
|
||||||
class Loadable(ABC):
|
|
||||||
@abstractmethod
|
|
||||||
def __init__(self, engine: Engine) -> None: ...
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def load(self) -> None: ...
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def unload(self) -> None: ...
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Callable, Final
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from snakia.core.engine import Engine
|
|
||||||
from snakia.core.loader import Loadable
|
|
||||||
|
|
||||||
|
|
||||||
class Loader:
|
|
||||||
__slots__ = ("__engine", "__loadables")
|
|
||||||
|
|
||||||
def __init__(self, engine: Engine) -> None:
|
|
||||||
self.__engine: Final = engine
|
|
||||||
self.__loadables: Final[list[Loadable]] = []
|
|
||||||
|
|
||||||
def register(self, loadable: Callable[[Engine], Loadable]) -> None:
|
|
||||||
self.__loadables.append(loadable(self.__engine))
|
|
||||||
|
|
||||||
def load_all(self) -> None:
|
|
||||||
for loadable in self.__loadables:
|
|
||||||
loadable.load()
|
|
||||||
|
|
||||||
def unload_all(self) -> None:
|
|
||||||
for loadable in reversed(self.__loadables):
|
|
||||||
loadable.unload()
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
|
||||||
|
|
||||||
from snakia.core.es import Event, Subscriber
|
|
||||||
from snakia.types import Version
|
|
||||||
|
|
||||||
from .plugin_processor import PluginProcessor
|
|
||||||
|
|
||||||
|
|
||||||
class Meta(BaseModel):
|
|
||||||
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
|
|
||||||
|
|
||||||
name: str = Field(
|
|
||||||
default="unknown",
|
|
||||||
min_length=4,
|
|
||||||
max_length=32,
|
|
||||||
pattern="^[a-z0-9_]{4,32}$",
|
|
||||||
)
|
|
||||||
author: str = Field(
|
|
||||||
default="unknown",
|
|
||||||
min_length=4,
|
|
||||||
max_length=32,
|
|
||||||
pattern="^[a-z0-9_]{4,32}$",
|
|
||||||
)
|
|
||||||
version: Version = Field(default_factory=lambda: Version(major=1, minor=0, patch=0))
|
|
||||||
|
|
||||||
subscribers: tuple[tuple[type[Event], Subscriber[Event]], ...] = Field(
|
|
||||||
default_factory=tuple
|
|
||||||
)
|
|
||||||
processors: tuple[type[PluginProcessor], ...] = Field(default_factory=tuple)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self) -> str:
|
|
||||||
return f"{self.author}.{self.name}"
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from abc import abstractmethod
|
|
||||||
from typing import TYPE_CHECKING, ClassVar, Final, final
|
|
||||||
|
|
||||||
from snakia.core.ecs import System
|
|
||||||
from snakia.core.es import Dispatcher
|
|
||||||
|
|
||||||
from .loadable import Loadable
|
|
||||||
from .meta import Meta
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from snakia.core.engine import Engine
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(Loadable):
|
|
||||||
__meta: ClassVar[Meta]
|
|
||||||
|
|
||||||
@final
|
|
||||||
def __init__(self, engine: Engine) -> None:
|
|
||||||
self.__engine: Final = engine
|
|
||||||
|
|
||||||
@final
|
|
||||||
@property
|
|
||||||
def meta(self) -> Meta:
|
|
||||||
"""The plugin's metadata."""
|
|
||||||
return self.__meta
|
|
||||||
|
|
||||||
@final
|
|
||||||
@property
|
|
||||||
def dispatcher(self) -> Dispatcher:
|
|
||||||
return self.__engine.dispatcher
|
|
||||||
|
|
||||||
@final
|
|
||||||
@property
|
|
||||||
def system(self) -> System:
|
|
||||||
return self.__engine.system
|
|
||||||
|
|
||||||
@final
|
|
||||||
def load(self) -> None:
|
|
||||||
for processor in self.meta.processors:
|
|
||||||
self.__engine.system.add_processor(processor(self))
|
|
||||||
for event_type, subscriber in self.meta.subscribers:
|
|
||||||
self.__engine.dispatcher.subscribe(event_type, subscriber)
|
|
||||||
self.on_load()
|
|
||||||
|
|
||||||
@final
|
|
||||||
def unload(self) -> None:
|
|
||||||
for processor in self.meta.processors:
|
|
||||||
self.__engine.system.remove_processor(processor)
|
|
||||||
for event_type, subscriber in self.meta.subscribers:
|
|
||||||
self.__engine.dispatcher.unsubscribe(event_type, subscriber)
|
|
||||||
self.on_unload()
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def on_load(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def on_unload(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
|
|
||||||
@final
|
|
||||||
def __init_subclass__(cls, meta: Meta) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
def __init_subclass__(cls, meta: Meta) -> None:
|
|
||||||
cls.meta = meta
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Final, final
|
|
||||||
|
|
||||||
from snakia.core.ecs import Processor
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .plugin import Plugin
|
|
||||||
|
|
||||||
|
|
||||||
class PluginProcessor(Processor):
|
|
||||||
@final
|
|
||||||
def __init__(self, plugin: Plugin) -> None:
|
|
||||||
self.plugin: Final = plugin
|
|
||||||
|
|
@ -29,7 +29,6 @@ class BaseBindable(Generic[T]):
|
||||||
def value(self) -> T:
|
def value(self) -> T:
|
||||||
if self.has_value:
|
if self.has_value:
|
||||||
return self.__value
|
return self.__value
|
||||||
else:
|
|
||||||
return self.default_value
|
return self.default_value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ def combine(
|
||||||
)
|
)
|
||||||
|
|
||||||
def subscriber(_: ValueChanged[Any]) -> None:
|
def subscriber(_: ValueChanged[Any]) -> None:
|
||||||
combined.set(combiner(*[*map(lambda s: s.value, sources)]))
|
combined.set(combiner(*map(lambda s: s.value, sources)))
|
||||||
|
|
||||||
for source in sources:
|
for source in sources:
|
||||||
if isinstance(source, Bindable):
|
if isinstance(source, Bindable):
|
||||||
|
|
@ -185,7 +185,7 @@ def async_combine(
|
||||||
)
|
)
|
||||||
|
|
||||||
async def subscriber(_: ValueChanged[Any]) -> None:
|
async def subscriber(_: ValueChanged[Any]) -> None:
|
||||||
result = await combiner(*[*map(lambda s: s.value, sources)])
|
result = await combiner(*map(lambda s: s.value, sources))
|
||||||
await combined.set(result)
|
await combined.set(result)
|
||||||
|
|
||||||
for source in sources:
|
for source in sources:
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ from .bool import BoolField
|
||||||
from .field import Field
|
from .field import Field
|
||||||
from .float import FloatField
|
from .float import FloatField
|
||||||
from .int import IntField
|
from .int import IntField
|
||||||
|
from .list import ListField
|
||||||
|
from .optional import OptionalField
|
||||||
from .str import StrField
|
from .str import StrField
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
@ -11,5 +13,7 @@ __all__ = [
|
||||||
"BoolField",
|
"BoolField",
|
||||||
"FloatField",
|
"FloatField",
|
||||||
"IntField",
|
"IntField",
|
||||||
|
"ListField",
|
||||||
|
"OptionalField",
|
||||||
"StrField",
|
"StrField",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
import pickle
|
import pickle
|
||||||
from typing import Final, Generic, TypeVar
|
from typing import Callable, Final, Generic, TypeVar, overload
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from snakia.types import Unset
|
||||||
|
|
||||||
from .field import Field
|
from .field import Field
|
||||||
|
|
||||||
|
|
@ -9,15 +13,40 @@ T = TypeVar("T")
|
||||||
class AutoField(Field[T], Generic[T]):
|
class AutoField(Field[T], Generic[T]):
|
||||||
__slots__ = ("__target_type",)
|
__slots__ = ("__target_type",)
|
||||||
|
|
||||||
def __init__(self, default_value: T, *, target_type: type[T] | None = None) -> None:
|
@overload
|
||||||
super().__init__(default_value)
|
def __init__(
|
||||||
self.__target_type: Final = target_type
|
self, default_value: T, *, target_type: type[T] | Unset = Unset()
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
default_factory: Callable[[Self], T],
|
||||||
|
target_type: type[T] | Unset = Unset(),
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
default_value: T | Unset = Unset(),
|
||||||
|
*,
|
||||||
|
default_factory: Callable[[Self], T] | Unset = Unset(),
|
||||||
|
target_type: type[T] | Unset = Unset(),
|
||||||
|
) -> None:
|
||||||
|
if not Unset.itis(default_factory):
|
||||||
|
super().__init__(default_factory=Unset.unwrap(default_factory))
|
||||||
|
elif not Unset.itis(default_value):
|
||||||
|
super().__init__(Unset.unwrap(default_value))
|
||||||
|
else:
|
||||||
|
super().__init__()
|
||||||
|
self.__target_type: Final[type] = Unset.unwrap_or(target_type, object)
|
||||||
|
|
||||||
def serialize(self, value: T, /) -> bytes:
|
def serialize(self, value: T, /) -> bytes:
|
||||||
return pickle.dumps(value)
|
return pickle.dumps(value)
|
||||||
|
|
||||||
def deserialize(self, serialized: bytes, /) -> T:
|
def deserialize(self, serialized: bytes, /) -> T:
|
||||||
value = pickle.loads(serialized)
|
value = pickle.loads(serialized)
|
||||||
if not isinstance(value, self.__target_type or object):
|
|
||||||
return self.default_value
|
if not isinstance(value, self.__target_type):
|
||||||
|
return self._get_default()
|
||||||
return value # type: ignore
|
return value # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import TYPE_CHECKING, Any, Callable, Final, Generic, TypeVar, final
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
Generic,
|
||||||
|
TypeVar,
|
||||||
|
final,
|
||||||
|
)
|
||||||
|
|
||||||
from snakia.property.priv_property import PrivProperty
|
from snakia.property.priv_property import PrivProperty
|
||||||
from snakia.utils import inherit
|
from snakia.utils import inherit
|
||||||
|
|
@ -11,10 +18,6 @@ R = TypeVar("R")
|
||||||
|
|
||||||
|
|
||||||
class Field(ABC, PrivProperty[T], Generic[T]):
|
class Field(ABC, PrivProperty[T], Generic[T]):
|
||||||
def __init__(self, default_value: T) -> None:
|
|
||||||
self.default_value: Final[T] = default_value
|
|
||||||
super().__init__(default_value)
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def serialize(self, value: T, /) -> bytes:
|
def serialize(self, value: T, /) -> bytes:
|
||||||
"""Serialize a value
|
"""Serialize a value
|
||||||
|
|
@ -42,14 +45,18 @@ class Field(ABC, PrivProperty[T], Generic[T]):
|
||||||
serialize: Callable[[Field[R], R], bytes],
|
serialize: Callable[[Field[R], R], bytes],
|
||||||
deserialize: Callable[[Field[R], bytes], R],
|
deserialize: Callable[[Field[R], bytes], R],
|
||||||
) -> type[Field[R]]:
|
) -> type[Field[R]]:
|
||||||
return inherit(cls, {"serialize": serialize, "deserialize": deserialize})
|
return inherit(
|
||||||
|
cls, {"serialize": serialize, "deserialize": deserialize}
|
||||||
|
)
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_fields(class_: type[Any] | Any, /) -> dict[str, Field[Any]]:
|
def get_fields(class_: type[Any] | Any, /) -> dict[str, Field[Any]]:
|
||||||
if not isinstance(class_, type):
|
if not isinstance(class_, type):
|
||||||
class_ = class_.__class__
|
class_ = class_.__class__
|
||||||
return {k: v for k, v in class_.__dict__.items() if isinstance(v, Field)}
|
return {
|
||||||
|
k: v for k, v in class_.__dict__.items() if isinstance(v, Field)
|
||||||
|
}
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
|
||||||
38
src/snakia/field/list.py
Normal file
38
src/snakia/field/list.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
from typing import Callable, Final, Iterable, TypeVar
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from .field import Field
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class ListField(Field[list[T]]):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
field: Field[T],
|
||||||
|
*,
|
||||||
|
length_size: int = 1,
|
||||||
|
default_factory: Callable[[Self], Iterable[T]] = lambda _: (),
|
||||||
|
) -> None:
|
||||||
|
self.length_size: Final[int] = length_size
|
||||||
|
self.field: Final = field
|
||||||
|
super().__init__(default_factory=lambda s: [*default_factory(s)])
|
||||||
|
|
||||||
|
def serialize(self, items: list[T], /) -> bytes:
|
||||||
|
result = b""
|
||||||
|
for item in items:
|
||||||
|
value = self.field.serialize(item)
|
||||||
|
length_prefix = len(value).to_bytes(self.length_size, "big")
|
||||||
|
result += length_prefix + value
|
||||||
|
return result
|
||||||
|
|
||||||
|
def deserialize(self, serialized: bytes, /) -> list[T]:
|
||||||
|
result = []
|
||||||
|
while serialized:
|
||||||
|
length = int.from_bytes(serialized[: self.length_size], "big")
|
||||||
|
serialized = serialized[self.length_size :]
|
||||||
|
item = self.field.deserialize(serialized[:length])
|
||||||
|
serialized = serialized[length:]
|
||||||
|
result.append(item)
|
||||||
|
return result
|
||||||
28
src/snakia/field/optional.py
Normal file
28
src/snakia/field/optional.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
from typing import Final, TypeVar
|
||||||
|
|
||||||
|
from .field import Field
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class OptionalField(Field[T | None]):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
field: Field[T],
|
||||||
|
*,
|
||||||
|
none_value: bytes = b"",
|
||||||
|
) -> None:
|
||||||
|
super().__init__(None)
|
||||||
|
self.none_value: Final = none_value
|
||||||
|
self.field: Final = field
|
||||||
|
|
||||||
|
def serialize(self, value: T | None, /) -> bytes:
|
||||||
|
if value is None:
|
||||||
|
return self.none_value
|
||||||
|
return self.field.serialize(value)
|
||||||
|
|
||||||
|
def deserialize(self, serialized: bytes, /) -> T | None:
|
||||||
|
if serialized == self.none_value:
|
||||||
|
return None
|
||||||
|
return self.field.deserialize(serialized)
|
||||||
|
|
@ -4,6 +4,8 @@ from .bool import BoolField as bool
|
||||||
from .field import Field as field
|
from .field import Field as field
|
||||||
from .float import FloatField as float
|
from .float import FloatField as float
|
||||||
from .int import IntField as int
|
from .int import IntField as int
|
||||||
|
from .list import ListField as list
|
||||||
|
from .optional import OptionalField as optional
|
||||||
from .str import StrField as str
|
from .str import StrField as str
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
@ -12,5 +14,7 @@ __all__ = [
|
||||||
"field",
|
"field",
|
||||||
"float",
|
"float",
|
||||||
"int",
|
"int",
|
||||||
|
"list",
|
||||||
|
"optional",
|
||||||
"str",
|
"str",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,44 @@
|
||||||
from typing import Any, Generic, TypeVar
|
from typing import Any, Callable, Final, Generic, TypeVar, overload
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from snakia.types import Unset
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class PrivProperty(Generic[T]):
|
class PrivProperty(Generic[T]):
|
||||||
__slots__ = "__name", "__default_value"
|
__slots__ = "__name", "__default_value", "__default_factory"
|
||||||
|
|
||||||
__name: str
|
__name: str
|
||||||
|
|
||||||
def __init__(self, default_value: T | None = None) -> None:
|
@overload
|
||||||
self.__default_value: T | None = default_value
|
def __init__(self) -> None: ...
|
||||||
|
@overload
|
||||||
|
def __init__(self, default_value: T) -> None: ...
|
||||||
|
@overload
|
||||||
|
def __init__(self, *, default_factory: Callable[[Self], T]) -> None: ...
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
default_value: T | Unset = Unset(),
|
||||||
|
default_factory: Callable[[Self], T] | Unset = Unset(),
|
||||||
|
) -> None:
|
||||||
|
self.__default_value: Final[T | Unset] = default_value
|
||||||
|
self.__default_factory: Final[Callable[[Self], T] | Unset] = default_factory
|
||||||
|
|
||||||
|
def _get_default(self: Self) -> T:
|
||||||
|
return Unset.map(
|
||||||
|
self.__default_factory,
|
||||||
|
lambda f: f(self),
|
||||||
|
lambda _: Unset.unwrap(self.__default_value),
|
||||||
|
)
|
||||||
|
|
||||||
def __set_name__(self, owner: type, name: str) -> None:
|
def __set_name__(self, owner: type, name: str) -> None:
|
||||||
self.__name = f"_{owner.__name__}__{name}"
|
self.__name = f"_{owner.__name__}__{name}"
|
||||||
|
|
||||||
def __get__(self, instance: Any, owner: type | None = None, /) -> T:
|
def __get__(self, instance: Any, owner: type | None = None, /) -> T:
|
||||||
if self.__default_value:
|
if not hasattr(instance, self.__name):
|
||||||
return getattr(instance, self.__name, self.__default_value)
|
setattr(instance, self.__name, self._get_default())
|
||||||
return getattr(instance, self.__name) # type: ignore
|
return getattr(instance, self.__name) # type: ignore
|
||||||
|
|
||||||
def __set__(self, instance: Any, value: T, /) -> None:
|
def __set__(self, instance: Any, value: T, /) -> None:
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ MARKERS_ATTR = "__snakia_markers__"
|
||||||
|
|
||||||
|
|
||||||
def _get_all_markers(obj: Any) -> dict[type["Marker"], "Marker"]:
|
def _get_all_markers(obj: Any) -> dict[type["Marker"], "Marker"]:
|
||||||
return get_or_set_attr(obj, MARKERS_ATTR, dict[type[Marker], Marker]())
|
return get_or_set_attr(obj, MARKERS_ATTR, dict())
|
||||||
|
|
||||||
|
|
||||||
class Marker:
|
class Marker:
|
||||||
|
|
|
||||||
|
|
@ -49,24 +49,29 @@ class UniqueType(type):
|
||||||
raise TypeError(f"{cls} not unwrapped")
|
raise TypeError(f"{cls} not unwrapped")
|
||||||
return value # type: ignore
|
return value # type: ignore
|
||||||
|
|
||||||
|
def unwrap_or(cls: type[T], value: V | type[T] | T, default: R, /) -> V | R:
|
||||||
|
if value is cls or isinstance(value, cls):
|
||||||
|
return default
|
||||||
|
return value # type: ignore
|
||||||
|
|
||||||
def map(
|
def map(
|
||||||
cls: type[T],
|
cls: type[T],
|
||||||
value: V | type[T] | T,
|
value: V | type[T] | T,
|
||||||
and_then: Callable[[V], R],
|
or_else: Callable[[V], R],
|
||||||
or_else: Callable[[type[T]], R],
|
and_then: Callable[[type[T]], R],
|
||||||
) -> R:
|
) -> R:
|
||||||
if value is cls or isinstance(value, cls):
|
if value is cls or isinstance(value, cls):
|
||||||
return or_else(cls)
|
return and_then(cls)
|
||||||
return and_then(value) # type: ignore
|
return or_else(value) # type: ignore
|
||||||
|
|
||||||
def and_then(
|
def or_else(
|
||||||
cls: type[T], value: V | type[T] | T, func: Callable[[V], R]
|
cls: type[T], value: V | type[T] | T, func: Callable[[V], R]
|
||||||
) -> type[T] | R:
|
) -> type[T] | R:
|
||||||
if value is cls or isinstance(value, cls):
|
if value is cls or isinstance(value, cls):
|
||||||
return cls
|
return cls
|
||||||
return func(value) # type: ignore
|
return func(value) # type: ignore
|
||||||
|
|
||||||
def or_else(
|
def and_then(
|
||||||
cls: type[T], value: V | type[T] | T, func: Callable[[type[T]], R]
|
cls: type[T], value: V | type[T] | T, func: Callable[[type[T]], R]
|
||||||
) -> R | V:
|
) -> R | V:
|
||||||
if value is cls or isinstance(value, cls):
|
if value is cls or isinstance(value, cls):
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
from .attrs import get_attrs, get_or_set_attr
|
from .attrs import get_attrs, get_or_set_attr
|
||||||
from .calls import call, caller
|
|
||||||
from .exceptions import catch, throw
|
from .exceptions import catch, throw
|
||||||
from .frames import frame
|
from .frames import frame
|
||||||
|
from .funcs import call, caller, ret, side, side_func
|
||||||
from .gil import GIL_ENABLED, nolock
|
from .gil import GIL_ENABLED, nolock
|
||||||
from .inherit import inherit
|
from .inherit import inherit
|
||||||
from .side import side, side_func
|
|
||||||
from .this import this
|
from .this import this
|
||||||
from .to_async import to_async
|
from .to_async import to_async
|
||||||
|
|
||||||
|
|
@ -17,6 +16,7 @@ __all__ = [
|
||||||
"frame",
|
"frame",
|
||||||
"inherit",
|
"inherit",
|
||||||
"nolock",
|
"nolock",
|
||||||
|
"ret",
|
||||||
"side",
|
"side",
|
||||||
"side_func",
|
"side_func",
|
||||||
"this",
|
"this",
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
from typing import Callable, ParamSpec, TypeVar
|
|
||||||
|
|
||||||
P = ParamSpec("P")
|
|
||||||
T = TypeVar("T")
|
|
||||||
|
|
||||||
|
|
||||||
def call(f: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def caller(f: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> Callable[..., T]:
|
|
||||||
return lambda *_, **__: f(*args, **kwargs)
|
|
||||||
24
src/snakia/utils/funcs.py
Normal file
24
src/snakia/utils/funcs.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
from typing import Any, Callable, ParamSpec, TypeVar
|
||||||
|
|
||||||
|
P = ParamSpec("P")
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
def call(f: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def caller(f: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> Callable[..., T]:
|
||||||
|
return lambda *_, **__: f(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def side(value: T, *_: Any, **__: Any) -> T:
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def side_func(value: T, *_: Any, **__: Any) -> Callable[..., T]:
|
||||||
|
return lambda *_, **__: value
|
||||||
|
|
||||||
|
|
||||||
|
def ret() -> Callable[[T], T]:
|
||||||
|
return lambda x: x
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
from typing import Any, Callable, TypeVar
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
|
|
||||||
|
|
||||||
def side(value: T, *_: Any, **__: Any) -> T:
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def side_func(value: T, *_: Any, **__: Any) -> Callable[..., T]:
|
|
||||||
return lambda *_, **__: value
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue