Skip to content

Commit

Permalink
fixed transition of the dispatch class
Browse files Browse the repository at this point in the history
Now the dispatch class is interactive in the sense
that it only adds the fields you are interested in.

class A(_Dispatchs, dispatchs=("hello", "new"))

will add the A.hello and A.new fields.
This makes it alot easier to manage.

The subclassing of a dispatcher class with fields
will now search through the bases and discover the dispatch
names assigned at prior bases to collect them.
Then the dispatch attributes will be handled through the
when_subclassing argument.
If it is copy, it will copy the dispatcher.
If it is new, a new one will be created.
If it is keep, nothing will be done.

The new dispatch methods has now been added to the Atom
class and the Lattice class.

Atom.to also works for changing to a Sphere.

Lattice now supports more transfers.

TODO find a way to document the `to|new` dispatchers.

Fixed imports.

Signed-off-by: Nick Papior <nickpapior@gmail.com>
  • Loading branch information
zerothi committed Oct 12, 2023
1 parent 0095e10 commit 7145e58
Show file tree
Hide file tree
Showing 26 changed files with 501 additions and 360 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ we hit release version 1.0.0.

### Added
- added `Lattice.to` and `Lattice.new` to function the same
as `Geometry`, added Lattice.to["cuboid"]
as `Geometry`, added Lattice.to["Cuboid"]
- added `Atom.to`, currently only `to.Sphere()`
- enabled `Geometry.to|new.Sile(...)`
- added logging in some modules, to be added in more stuff to allow easier
debugging.
- marked all `toSphere|toEllipsoid|...` as deprecated


## [0.14.2] - 2023-10-04
Expand Down
4 changes: 4 additions & 0 deletions src/sisl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,13 @@
# Since __getitem__ always instantiate the class, we have to use the
# contained lookup table.
Geometry.new.register(BaseSile, Geometry.new._dispatchs[str])
Geometry.new.register("Sile", Geometry.new._dispatchs[str])
Geometry.to.register(BaseSile, Geometry.to._dispatchs[str])
Geometry.to.register("Sile", Geometry.to._dispatchs[str])
Lattice.new.register(BaseSile, Lattice.new._dispatchs[str])
Lattice.new.register("Sile", Lattice.new._dispatchs[str])
Lattice.to.register(BaseSile, Lattice.to._dispatchs[str])
Lattice.to.register("Sile", Lattice.to._dispatchs[str])

# Import the default geom structure
# This enables:
Expand Down
2 changes: 1 addition & 1 deletion src/sisl/_category.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
import logging
from abc import ABCMeta, abstractmethod
from collections import ChainMap, defaultdict
from collections.abc import Iterable
from functools import lru_cache, singledispatchmethod, wraps
import logging

from ._internal import set_module

Expand Down
143 changes: 82 additions & 61 deletions src/sisl/_dispatch_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,90 +9,111 @@
The usage interface should look something like this:
class A(_ToNew,
to=object used | str | None,
new=object used | str | None,
class A(_Dispatchs,
dispatchs=[
"new", "hello"
]
)
A.to.register ..
A.new.register ..
A.hello.register ..
"""
from typing import Any, Optional
import logging
from collections import namedtuple
from typing import Any, Optional, Sequence, Union

from ._dispatcher import ClassDispatcher, TypeDispatcher

_log = logging.getLogger("sisl")
_log.info(f"adding logger: {__name__}")
_log = logging.getLogger(__name__)

class _ToNew:

class _Dispatchs:
"""Subclassable for creating the new/to arguments"""

def __init_subclass__(cls, /,
new: Optional[Any] = "default",
to: Optional[Any] = "default",
dispatchs: Optional[Union[str, Sequence[Any]]]=None,
when_subclassing: Optional[str] = None,
**kwargs):
# complete the init_subclass
super().__init_subclass__(**kwargs)

# Get the allowed actions for subclassing
prefix = "_tonew"
allowed_subclassing = ("keep", "new", "copy")

def_new = ClassDispatcher("new")
def_to = ClassDispatcher("to")

# check for the default handler of the parent class
# We find the first base class which has either of the
first_base = None
for b in cls.__bases__:
if hasattr(b, "to") or hasattr(b, "new"):
# any one fits
first_base = b
break

if when_subclassing is None and first_base is not None:
when_subclassing = getattr(first_base, f"{prefix}_when_subclassing")
else:
when_subclassing = "new"
def find_base(cls, attr):
for base in cls.__bases__:
if hasattr(base, attr):
return base
return None

if dispatchs is None:
dispatchs = []
for base in cls.__bases__:
if hasattr(base, f"{prefix}_dispatchs"):
dispatchs.extend(getattr(base, f"{prefix}_dispatchs"))

if isinstance(dispatchs, str):
dispatchs = [dispatchs]

loop = []
for attr in dispatchs:

# argument could be:
# dispatchs = [
# ("new", "keep"),
# "to"
# ]
if isinstance(attr, (list, tuple)):
attr, obj = attr
else:
obj = None

if attr in cls.__dict__:
raise ValueError(f"The attribute {attr} already exists on {cls!r}")

base = find_base(cls, attr)
if base is None:
# this is likely the first instance of the class
# So one cannot do anything but specifying stuff
when_subcls = None
if obj is None:
obj = ClassDispatcher(attr)
else:
when_subcls = getattr(base, f"{prefix}_when_subclassing")
if obj is None:
obj = when_subcls

if isinstance(obj, str):
if obj == "new":
obj = ClassDispatcher(attr)
elif obj == "keep":
obj = None
elif obj == "copy":
obj = getattr(base, attr).copy()
loop.append((attr, obj, when_subcls))


if when_subclassing is None:
# first non-None value
when_subclassing = "copy"
for _, _, when_subcls in loop:
if when_subcls is not None:
when_subclassing = when_subcls
_log.debug(f"{cls!r} when_subclassing = {when_subclassing}")

if when_subclassing not in allowed_subclassing:
raise ValueError(f"when_subclassing should be one of {allowed_subclassing}")
raise ValueError(f"when_subclassing should be one of {allowed_subclassing}, got {when_subclassing}")

for attr, arg, def_ in (("to", to, def_to),
("new", new, def_new)):

base = first_base
for b in cls.__bases__:
if hasattr(b, attr):
base = b
break

if base is None and isinstance(arg, str):
# when there is no base and the class is not
# specified. Then regardless a new object will be used.
arg = def_
for attr, obj, _ in loop:

if isinstance(arg, str):
if arg == "default":
arg = when_subclassing

# now we can parse the potential subclassing problem
if arg == "keep":
# signal we do nothing...
# this will tap into the higher class structures
# registration.
arg = None

elif arg == "new":
# always create a new one
arg = def_

elif arg == "copy":
# TODO, this might fail if base does not have it...
# But then again, it shouldn't be `copy`...
# copy it!
arg = getattr(base, attr).copy()

if arg is not None and not isinstance(arg, str):
setattr(cls, attr, arg)
if obj is None:
_log.debug(f"Doing nothing for {attr} on class {cls!r}")
else:
_log.debug(f"Inserting {attr}={obj!r} onto class {cls!r}")
setattr(cls, attr, obj)

setattr(cls, f"{prefix}_when_subclassing", when_subclassing)
setattr(cls, f"{prefix}_dispatchs", [attr for attr, _, _ in loop])
Loading

0 comments on commit 7145e58

Please sign in to comment.