pylocuszoom 0.6.0__py3-none-any.whl → 1.0.0__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.
pylocuszoom/__init__.py CHANGED
@@ -51,9 +51,19 @@ from .colors import (
51
51
  get_phewas_category_palette,
52
52
  )
53
53
 
54
+ # Configuration classes (internal - use kwargs directly on plot()/plot_stacked())
55
+ # from .config import PlotConfig, StackedPlotConfig # Internal use only
56
+ # Ensembl integration
57
+ from .ensembl import (
58
+ clear_ensembl_cache,
59
+ fetch_exons_from_ensembl,
60
+ fetch_genes_from_ensembl,
61
+ get_ensembl_species_name,
62
+ get_genes_for_region,
63
+ )
64
+
54
65
  # eQTL support
55
66
  from .eqtl import (
56
- EQTLValidationError,
57
67
  calculate_colocalization_overlap,
58
68
  filter_eqtl_by_gene,
59
69
  filter_eqtl_by_region,
@@ -62,9 +72,19 @@ from .eqtl import (
62
72
  validate_eqtl_df,
63
73
  )
64
74
 
75
+ # Exception hierarchy
76
+ from .exceptions import (
77
+ BackendError,
78
+ DataDownloadError,
79
+ EQTLValidationError,
80
+ FinemappingValidationError,
81
+ LoaderValidationError,
82
+ PyLocusZoomError,
83
+ ValidationError,
84
+ )
85
+
65
86
  # Fine-mapping/SuSiE support
66
87
  from .finemapping import (
67
- FinemappingValidationError,
68
88
  filter_by_credible_set,
69
89
  filter_finemapping_by_region,
70
90
  get_credible_sets,
@@ -125,11 +145,8 @@ from .recombination import (
125
145
  load_recombination_map,
126
146
  )
127
147
 
128
- # Schema validation
129
- from .schemas import LoaderValidationError
130
-
131
148
  # Validation utilities
132
- from .utils import ValidationError, to_pandas
149
+ from .utils import to_pandas
133
150
 
134
151
  __all__ = [
135
152
  # Core
@@ -178,8 +195,12 @@ __all__ = [
178
195
  # Logging
179
196
  "enable_logging",
180
197
  "disable_logging",
181
- # Validation & Utils
198
+ # Exceptions
199
+ "PyLocusZoomError",
182
200
  "ValidationError",
201
+ "BackendError",
202
+ "DataDownloadError",
203
+ # Utils
183
204
  "to_pandas",
184
205
  # PheWAS
185
206
  "validate_phewas_df",
@@ -208,4 +229,10 @@ __all__ = [
208
229
  "load_ensembl_genes",
209
230
  # Schema validation
210
231
  "LoaderValidationError",
232
+ # Ensembl integration
233
+ "get_genes_for_region",
234
+ "fetch_genes_from_ensembl",
235
+ "fetch_exons_from_ensembl",
236
+ "get_ensembl_species_name",
237
+ "clear_ensembl_cache",
211
238
  ]
@@ -1,18 +1,77 @@
1
1
  """Pluggable plotting backends for pyLocusZoom.
2
2
 
3
3
  Supports matplotlib (default), plotly, and bokeh backends.
4
+
5
+ Backend Registration:
6
+ Backends are registered using the @register_backend decorator.
7
+ This enables extensibility without modifying core code.
8
+
9
+ @register_backend("custom")
10
+ class CustomBackend:
11
+ ...
12
+
13
+ Fallback Behavior:
14
+ When an optional backend (plotly, bokeh) is requested but not installed,
15
+ get_backend() falls back to matplotlib with a warning. This ensures
16
+ code works even without optional dependencies.
4
17
  """
5
18
 
19
+ import warnings
6
20
  from typing import Literal
7
21
 
8
22
  from .base import PlotBackend
9
- from .matplotlib_backend import MatplotlibBackend
10
23
 
11
24
  BackendType = Literal["matplotlib", "plotly", "bokeh"]
12
25
 
13
- _BACKENDS: dict[str, type[PlotBackend]] = {
14
- "matplotlib": MatplotlibBackend,
15
- }
26
+ # LaTeX to Unicode conversion table for interactive backends
27
+ _LATEX_TO_UNICODE = [
28
+ (r"$-\log_{10}$ P", "-log10(P)"),
29
+ (r"$-\log_{10}$", "-log10"),
30
+ (r"\log_{10}", "log10"),
31
+ (r"$r^2$", "r"),
32
+ (r"$R^2$", "R"),
33
+ ]
34
+
35
+
36
+ def convert_latex_to_unicode(label: str) -> str:
37
+ """Convert LaTeX-style labels to Unicode for display in interactive backends.
38
+
39
+ Args:
40
+ label: Label text possibly containing LaTeX notation.
41
+
42
+ Returns:
43
+ Label with LaTeX converted to Unicode characters.
44
+ """
45
+ for latex, unicode_str in _LATEX_TO_UNICODE:
46
+ if latex in label:
47
+ label = label.replace(latex, unicode_str)
48
+ return label.replace("$", "")
49
+
50
+
51
+ # Backend registry - populated by @register_backend decorator
52
+ _BACKENDS: dict[str, type[PlotBackend]] = {}
53
+
54
+
55
+ def register_backend(name: str):
56
+ """Decorator to register a backend class.
57
+
58
+ Args:
59
+ name: Backend name (e.g., "matplotlib", "plotly", "bokeh").
60
+
61
+ Returns:
62
+ Decorator function that registers the class.
63
+
64
+ Example:
65
+ @register_backend("custom")
66
+ class CustomBackend:
67
+ ...
68
+ """
69
+
70
+ def decorator(cls: type[PlotBackend]) -> type[PlotBackend]:
71
+ _BACKENDS[name] = cls
72
+ return cls
73
+
74
+ return decorator
16
75
 
17
76
 
18
77
  def get_backend(name: BackendType) -> PlotBackend:
@@ -25,24 +84,64 @@ def get_backend(name: BackendType) -> PlotBackend:
25
84
  Instantiated backend.
26
85
 
27
86
  Raises:
28
- ValueError: If backend name is invalid.
29
- ImportError: If backend dependencies are not installed.
30
- """
31
- if name == "plotly":
32
- from .plotly_backend import PlotlyBackend
87
+ ValueError: If backend name is completely unknown.
33
88
 
34
- _BACKENDS["plotly"] = PlotlyBackend
35
- elif name == "bokeh":
36
- from .bokeh_backend import BokehBackend
89
+ Note:
90
+ When optional backends (plotly, bokeh) are unavailable,
91
+ falls back to matplotlib with a UserWarning.
92
+ """
93
+ # Ensure matplotlib is always registered (it's always available)
94
+ if "matplotlib" not in _BACKENDS:
95
+ from .matplotlib_backend import MatplotlibBackend # noqa: F401
37
96
 
38
- _BACKENDS["bokeh"] = BokehBackend
97
+ # Try lazy import for optional backends
98
+ if name not in _BACKENDS:
99
+ if name == "plotly":
100
+ try:
101
+ from .plotly_backend import PlotlyBackend # noqa: F401
102
+ except ImportError:
103
+ warnings.warn(
104
+ "Plotly not installed, falling back to matplotlib. "
105
+ "Install plotly with: pip install plotly",
106
+ UserWarning,
107
+ stacklevel=2,
108
+ )
109
+ name = "matplotlib"
110
+ elif name == "bokeh":
111
+ try:
112
+ from .bokeh_backend import BokehBackend # noqa: F401
113
+ except ImportError:
114
+ warnings.warn(
115
+ "Bokeh not installed, falling back to matplotlib. "
116
+ "Install bokeh with: pip install bokeh",
117
+ UserWarning,
118
+ stacklevel=2,
119
+ )
120
+ name = "matplotlib"
39
121
 
40
122
  if name not in _BACKENDS:
41
- raise ValueError(
42
- f"Unknown backend: {name}. Available: matplotlib, plotly, bokeh"
43
- )
123
+ available = list(_BACKENDS.keys())
124
+ raise ValueError(f"Unknown backend: {name}. Available: {available}")
44
125
 
45
126
  return _BACKENDS[name]()
46
127
 
47
128
 
48
- __all__ = ["PlotBackend", "BackendType", "get_backend", "MatplotlibBackend"]
129
+ # Lazy import for backward compatibility - MatplotlibBackend available at module level
130
+ # The actual registration happens when matplotlib_backend is imported
131
+ def __getattr__(name: str):
132
+ """Lazy attribute access for backward compatibility."""
133
+ if name == "MatplotlibBackend":
134
+ from .matplotlib_backend import MatplotlibBackend
135
+
136
+ return MatplotlibBackend
137
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
138
+
139
+
140
+ __all__ = [
141
+ "PlotBackend",
142
+ "BackendType",
143
+ "get_backend",
144
+ "register_backend",
145
+ "MatplotlibBackend",
146
+ "convert_latex_to_unicode",
147
+ ]