Skip to content

Commit b4afd99

Browse files
authored
feat: Replace higlass.tilesets.register with higlass.Tileset (#177)
* Replace `higlass.tilesets.register` with `higlass.Tileset` The `register` naming is not very clear, and also the meta-programming in the previous approach erased type information. By having a shared based class, implementing a custom tileset is much nicer: ```py from dataclasses import dataclass import higlass as hg from clodius.tiles import cooler @DataClass class MyCustomCoolerTileset(hg.Tileset): path: str datatype = "matrix" def tiles(self, tile_ids): return cooler.tiles(self.path, tile_ids) def info(self): return cooler.tileset_info(self.path) ts = MyCustomCoolerTileset("test.mcool") hg.view(ts.track()) ``` * Remove _TilesetMixin * Add docstring
1 parent af0059a commit b4afd99

File tree

5 files changed

+118
-105
lines changed

5 files changed

+118
-105
lines changed

examples/JupyterServer.ipynb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,27 @@
2929
"id": "2e2c753f-fb13-4d54-b031-10cf5865e138",
3030
"metadata": {},
3131
"outputs": [],
32-
"source": []
32+
"source": [
33+
"from dataclasses import dataclass\n",
34+
"\n",
35+
"from clodius.tiles.cooler import tiles, tileset_info\n",
36+
"\n",
37+
"\n",
38+
"@dataclass\n",
39+
"class MyCustomCoolerTileset(hg.Tileset):\n",
40+
" path: str\n",
41+
" datatype = \"matrix\"\n",
42+
"\n",
43+
" def tiles(self, tile_ids):\n",
44+
" return tiles(self.path, tile_ids)\n",
45+
"\n",
46+
" def info(self):\n",
47+
" return tileset_info(self.path)\n",
48+
"\n",
49+
"\n",
50+
"ts = MyCustomCoolerTileset(\"test.mcool\")\n",
51+
"hg.view(ts.track())"
52+
]
3353
},
3454
{
3555
"cell_type": "code",

src/higlass/__init__.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,32 @@
1010

1111
from higlass_schema import *
1212

13-
import higlass.tilesets as tilesets
14-
from higlass.api import *
15-
from higlass.tilesets import bed2ddb, beddb, bigwig, cooler, hitile, multivec, remote
13+
from higlass.api import (
14+
CombinedTrack,
15+
EnumTrack,
16+
HeatmapTrack,
17+
IndependentViewportProjectionTrack,
18+
PluginTrack,
19+
TrackT,
20+
View,
21+
Viewconf,
22+
ViewT,
23+
combine,
24+
concat,
25+
divide,
26+
hconcat,
27+
lock,
28+
track,
29+
vconcat,
30+
view,
31+
)
32+
from higlass.tilesets import (
33+
Tileset,
34+
bed2ddb,
35+
beddb,
36+
bigwig,
37+
cooler,
38+
hitile,
39+
multivec,
40+
remote,
41+
)

src/higlass/api.py

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import functools
44
from collections import defaultdict
55
from typing import (
6-
TYPE_CHECKING,
76
ClassVar,
87
Generic,
98
Literal,
@@ -38,8 +37,6 @@
3837
"view",
3938
]
4039

41-
if TYPE_CHECKING:
42-
from higlass._track_helper import TrackHelper
4340

4441
## Mixins
4542

@@ -124,46 +121,16 @@ def opts(
124121
return track
125122

126123

127-
class _TilesetMixin:
128-
def tileset(
129-
self: TrackT, # type: ignore
130-
tileset: TrackHelper,
131-
inplace: bool = False,
132-
) -> TrackT: # type: ignore
133-
"""Replace or add a tileset to a Track.
134-
135-
A convenience method to update a track with a tileset.
136-
137-
Parameters
138-
----------
139-
tileset : TilesetResource
140-
A tileset resource returned from `hg.server`.
141-
142-
inplace : bool, optional
143-
Whether to modify the existing track in place or return
144-
a new track (default: `False`)
145-
146-
Returns
147-
-------
148-
track : A track with the bound tileset.
149-
150-
"""
151-
track = self if inplace else utils.copy_unique(self)
152-
track.server = tileset.server
153-
track.tilesetUid = tileset.tileset.uid
154-
return track
155-
156-
157124
## Extend higlass-schema classes
158125

159126

160-
class EnumTrack(hgs.EnumTrack, _OptionsMixin, _PropertiesMixin, _TilesetMixin):
127+
class EnumTrack(hgs.EnumTrack, _OptionsMixin, _PropertiesMixin):
161128
"""Represents a generic track."""
162129

163130
...
164131

165132

166-
class HeatmapTrack(hgs.HeatmapTrack, _OptionsMixin, _PropertiesMixin, _TilesetMixin):
133+
class HeatmapTrack(hgs.HeatmapTrack, _OptionsMixin, _PropertiesMixin):
167134
"""Represets a specialized heatmap track."""
168135

169136
...
@@ -173,20 +140,19 @@ class IndependentViewportProjectionTrack(
173140
hgs.IndependentViewportProjectionTrack,
174141
_OptionsMixin,
175142
_PropertiesMixin,
176-
_TilesetMixin,
177143
):
178144
"""Represents a view-independent viewport projection track."""
179145

180146
...
181147

182148

183-
class CombinedTrack(hgs.CombinedTrack, _OptionsMixin, _PropertiesMixin, _TilesetMixin):
149+
class CombinedTrack(hgs.CombinedTrack, _OptionsMixin, _PropertiesMixin):
184150
"""Represents a track combining multiple tracks."""
185151

186152
...
187153

188154

189-
class PluginTrack(hgs.BaseTrack, _OptionsMixin, _PropertiesMixin, _TilesetMixin):
155+
class PluginTrack(hgs.BaseTrack, _OptionsMixin, _PropertiesMixin):
190156
"""Represents an unknown plugin track."""
191157

192158
plugin_url: ClassVar[str]

src/higlass/tilesets.py

Lines changed: 62 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
from __future__ import annotations
22

3+
import abc
34
import functools
45
import pathlib
56
import typing
67
from dataclasses import dataclass
78

89
import higlass.api
9-
from higlass._tileset_registry import TilesetInfo, TilesetProtocol, TilesetRegistry
10+
from higlass._tileset_registry import TilesetInfo, TilesetRegistry
1011
from higlass._utils import TrackType, datatype_default_track
1112

1213
__all__ = [
14+
"Tileset",
1315
"bed2ddb",
1416
"bigwig",
1517
"cooler",
1618
"hitile",
1719
"multivec",
18-
"register",
1920
"remote",
2021
]
2122

@@ -29,19 +30,60 @@
2930
"vector",
3031
]
3132

32-
T = typing.TypeVar("T", bound=TilesetProtocol)
3333

34+
@dataclass
35+
class RemoteTileset:
36+
uid: str
37+
server: str
38+
name: str | None = None
39+
40+
def track(self, type_: TrackType, **kwargs):
41+
track = higlass.api.track(
42+
type_=type_,
43+
server=self.server,
44+
tilesetUid=self.uid,
45+
**kwargs,
46+
)
47+
if self.name is not None:
48+
track.opts(name=self.name, inplace=True)
49+
return track
50+
51+
52+
def remote(
53+
uid: str, server: str = "https://higlass.io/api/v1", name: str | None = None
54+
):
55+
"""Create a remote tileset reference.
3456
35-
def register(klass: type[T]) -> type[T]:
36-
"""Decorator that adds a `track` method to a class implementing `TilesetProtocol`.
57+
Parameters
58+
----------
59+
uid : str
60+
The unique identifier for the remote tileset.
61+
server : str, optional
62+
The HiGlass server URL (default is "https://higlass.io/api/v1").
63+
name : str, optional
64+
An optional name for the tileset.
65+
66+
Returns
67+
-------
68+
RemoteTileset
69+
A RemoteTileset instance that can be used to create HiGlass tracks.
70+
"""
71+
return RemoteTileset(uid, server, name)
3772

38-
The `track` method automatically registers the tileset with the `TilesetRegistry`
39-
and returns a HiGlass track object, which can be used for visualization.
73+
74+
class Tileset(abc.ABC):
75+
"""Base class for defining custom tilesets in `higlass`.
76+
77+
Subclasses must implement the `tiles` and `info` methods.
78+
79+
The provided `track` method is a helper which automatically registers the
80+
tileset with the `TilesetRegistry` and returns a HiGlass track object
81+
ready for visualization.
4082
4183
Parameters
4284
----------
4385
klass : type[T]
44-
A class that implements `TilesetProtocol`.
86+
A class implementing `TilesetProtocol`.
4587
4688
Returns
4789
-------
@@ -50,12 +92,12 @@ def register(klass: type[T]) -> type[T]:
5092
5193
Examples
5294
--------
95+
>>> import higlass as hg
5396
>>> from dataclasses import dataclass
54-
>>> from clodius.tiles.cooler import tiles, tileset_info
97+
>>> from clodius.tiles import cooler
5598
>>>
56-
>>> @register
5799
>>> @dataclass
58-
>>> class MyCoolerTileset:
100+
>>> class MyCoolerTileset(hg.Tileset):
59101
>>> filepath: str
60102
>>> datatype = "matrix"
61103
>>>
@@ -66,12 +108,16 @@ def register(klass: type[T]) -> type[T]:
66108
>>> return tiles(self.filepath, tile_ids)
67109
>>>
68110
>>> tileset = MyCoolerTileset("test.mcool")
69-
>>> track = tileset.track("heatmap")
111+
>>> hg.view(tileset.track("heatmap"))
70112
"""
71113

72-
def track(
73-
self: TilesetProtocol, type_: TrackType | None = None, /, **kwargs
74-
) -> higlass.api.Track:
114+
@abc.abstractmethod
115+
def tiles(self, tile_ids: typing.Sequence[str], /) -> list[dict]: ...
116+
117+
@abc.abstractmethod
118+
def info(self) -> TilesetInfo: ...
119+
120+
def track(self, type_: TrackType | None = None, /, **kwargs) -> higlass.api.Track:
75121
"""
76122
Create a HiGlass track for the tileset.
77123
@@ -111,53 +157,9 @@ def track(
111157

112158
return track
113159

114-
setattr(klass, "track", track)
115-
return klass
116-
117-
118-
@dataclass
119-
class RemoteTileset:
120-
uid: str
121-
server: str
122-
name: str | None = None
123-
124-
def track(self, type_: TrackType, **kwargs):
125-
track = higlass.api.track(
126-
type_=type_,
127-
server=self.server,
128-
tilesetUid=self.uid,
129-
**kwargs,
130-
)
131-
if self.name is not None:
132-
track.opts(name=self.name, inplace=True)
133-
return track
134-
135-
136-
def remote(
137-
uid: str, server: str = "https://higlass.io/api/v1", name: str | None = None
138-
):
139-
"""Create a remote tileset reference.
140-
141-
Parameters
142-
----------
143-
uid : str
144-
The unique identifier for the remote tileset.
145-
server : str, optional
146-
The HiGlass server URL (default is "https://higlass.io/api/v1").
147-
name : str, optional
148-
An optional name for the tileset.
149-
150-
Returns
151-
-------
152-
RemoteTileset
153-
A RemoteTileset instance that can be used to create HiGlass tracks.
154-
"""
155-
return RemoteTileset(uid, server, name)
156-
157160

158-
@register
159161
@dataclass
160-
class ClodiusTileset(TilesetProtocol):
162+
class ClodiusTileset(Tileset):
161163
datatype: DataType
162164
tiles_impl: typing.Callable[[typing.Sequence[str]], list[typing.Any]]
163165
info_impl: typing.Callable[[], TilesetInfo]

test/test_tileset_registery.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66

77
from higlass._tileset_registry import TilesetRegistry
8-
from higlass.tilesets import ClodiusTileset, register
8+
from higlass.tilesets import ClodiusTileset, Tileset
99

1010

1111
def mock_tileset() -> ClodiusTileset:
@@ -42,8 +42,7 @@ def test_tilesets_are_weakly_referenced(Registry: type[TilesetRegistry]) -> None
4242

4343

4444
def test_custom_tileset_without_uid(Registry: type[TilesetRegistry]) -> None:
45-
@register
46-
class MyTileset:
45+
class MyTileset(Tileset):
4746
def tiles(self, tile_ids: typing.Sequence[str]) -> list[typing.Any]:
4847
return []
4948

0 commit comments

Comments
 (0)