-
Notifications
You must be signed in to change notification settings - Fork 63
Typed adapters #1047
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Typed adapters #1047
Conversation
|
Thanks for this nice write up. There are clear benefits to moving the common methods into a base class. What would the trade-offs be if we kept with Protocols to define the expected interfaces (as now) but then added an I see ABCs mixing the two---defining an interface while also smuggling in some implementation As a practical point, I can foresee defining Tiled-compatible Adapters in third-party libraries. For example, the library [Forgive the soap box here.] Duck typing (structural subtyping) enables the distributed coordination that has made Scientific Python work, and we've emulated that in Bluesky. Any Device-like object works with the RunEngine as long as it implements the protocols. Nominal subtyping would lock us into dependency and inheritance, and I'm reluctant to go that way unless there is strong benefit. |
I wanted to say that the horse is already bolted wrt. Spec and Storage, but what I found is even more concerning: from abc import abstractmethod
from dataclasses import dataclass
from typing import Protocol, Union, runtime_checkable
from tiled.storage import Storage
from tiled.structures.core import Spec
from tiled.type_aliases import JSON
@runtime_checkable
class BaseAdapter(Protocol):
supported_storage: set[type[Storage]]
@abstractmethod
def metadata(self) -> JSON:
pass
@abstractmethod
def specs(self) -> list[Spec]:
pass
FOREIGN_JSON = dict[str, Union[str, int, float, bool, dict[str, "FOREIGN_JSON"], list["FOREIGN_JSON"]]]
@dataclass(frozen=True)
class ForeignStorage:
unknown_field: float
@dataclass(frozen=True)
class ForeignSpec:
unknown_field: int
class ForeignAdapter:
supported_storage: set[type[ForeignStorage]] = {ForeignStorage}
def metadata(self) -> set[float]:
return {8, 9, 10}
def specs(self) -> list[ForeignSpec]:
return [ForeignSpec(1)]
foreign_adapt = ForeignAdapter()
foreign_store = ForeignStorage(7.3)
foreign_spec = ForeignSpec(2)
print(isinstance(foreign_adapt, BaseAdapter))
# True
print(isinstance(foreign_store, Storage))
# False
print(isinstance(foreign_spec, Spec))
# False
|
|
For If we're focused on |
|
@danielballan we've been discussing this more internally, I'm still not convinced by the argument of other libraries implementing adapters, vs. the existing standard of tiled having optional imports for which it defines adapters. A third party adapter would already need to be registered and associated with mimetypes to be useable, having a simple wrapping Adapter type defined in tiled [or in the script that initialises Tiled where presumably the adapter is being registered, at which point we're very happy to accept more optional contributions] would allow accomplishing that, even if the library implemented something Adapter-shaped. Where the SturctureFamily etc. are re-defined in bluesky there is already a dependency on |
|
That's all valid. I am still inclined toward Protocols but not finding the bandwidth make good arguments here, so I think we should go ahead with the approach you propose. We can conceivably refine later, and this would certainly be an improvement over the status quo. |
danielballan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @dan-fernandes and @DiamondJoseph! I appreciate you cleaning up so much cruft here. It's a big improvement.
|
I resolved merge conflicts. This is good to merge once CI passes. |
* fix(devcontainer): Add stage to Docker build for devcontainer * chore(types): Add type hints and hierarchy to Structure types * fix type checking issues on various versions * ClassVar * Numpy types * Add root Adapter type * chore: Type hint and rationalise adapters * Changes to fix pre-commit * Fixes for pre-commit including correct typing of MapAdapter * Fixes for pre-commit * Adopt Zarr expectations of JSON type * Extract Zarr Adapter from array assumptions * Fixes for pre-commit * Retype xarray adapters, MadAdapter * Add types-cachetools to dev requirements * Fixes to pass pre-commit * Remove type vars from non generic classes * Add SQLAdapter.structure_family * Fix treating .structure_family as a property * Make ParquetDatabaseAdapter inherit from Adapter[TableStructure] * Fixes for pre-commit * Make structure_family an attribute, make metadata a callable * Make CSVAdapter inherit directly from Adapter[TableStructure] * Fix structure family pickling issue * Fix hdf5 adapter self referential metadata * Change ZarrGroupAdapter conatiner structure to use list of keys * Change NPYAdapter to inherit directly from Adapter[ArrayStructure] * Remove unused print statement * Remove AnyStructure * Fix typing for backwards compatibility * Fix metadata argument * Add type to ExceAdapter(MadAdapter) generic * Add return type, remove unused type ignore * Fix JPEGAdapter.read_block signature, remove unused type ignore * Fix merge conflict resolution --------- Co-authored-by: Daniel Fernandes <[email protected]> Co-authored-by: Dan Allan <[email protected]>
Builds on the work of #1036 - committing to trade off to @dan-fernandes.
The general idea is that there is a root
Adaptertype:And then for each Structure type there is a class that defines the Structure-type behaviours, e.g.
Specific implementations may then modify slightly the behaviour or implementation (e.g. a generic adapter may not be able to be stored on disc/in a database, but an implementation with a filetype or a database form may). By typing the methods, especially the constructing class methods, it is clearer to see what's going on, the intent behind the design and follow that structure eventually up into nodes.
flowchart TD A["Adapter[S = TypeVar(bound=Structure)]"] --> B A["Adapter[S = TypeVar(bound=Structure)]"] --> C A["Adapter[S = TypeVar(bound=Structure)]"] --> F B["TableAdapter(Adapter[TableStructure])"] --> D C["ArrayAdapter(Adapter[ArrayStructure])"] --> E C --> G F["ContainerAdapter(Adapter[ContainerStructure], Mapping[str, A = TypeVar(bound=Adapter[S])]"] --> H F --> I D["ParquetAdapter(TableAdapter)"] E["CSVArrayAdapter(ArrayAdapter)"] G["JPEGAdapter(ArrayAdapter)"] H["MapAdaper(ContainerAdapter[A])"] I["ZarrGroupAdapter(ContainerAdapter[Union[ArrayAdapter, ZarrGroupAdapter]])"]Checklist