pylocuszoom 0.5.0__py3-none-any.whl → 0.8.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
@@ -34,14 +34,31 @@ Species Support:
34
34
  - Custom: User provides all reference data
35
35
  """
36
36
 
37
- __version__ = "0.3.0"
37
+ __version__ = "0.6.0"
38
38
 
39
39
  # Main plotter class
40
40
  # Backend types
41
41
  from .backends import BackendType, get_backend
42
42
 
43
43
  # Colors and LD
44
- from .colors import LEAD_SNP_COLOR, get_ld_bin, get_ld_color, get_ld_color_palette
44
+ from .colors import (
45
+ LEAD_SNP_COLOR,
46
+ PHEWAS_CATEGORY_COLORS,
47
+ get_ld_bin,
48
+ get_ld_color,
49
+ get_ld_color_palette,
50
+ get_phewas_category_color,
51
+ get_phewas_category_palette,
52
+ )
53
+
54
+ # Ensembl integration
55
+ from .ensembl import (
56
+ clear_ensembl_cache,
57
+ fetch_exons_from_ensembl,
58
+ fetch_genes_from_ensembl,
59
+ get_ensembl_species_name,
60
+ get_genes_for_region,
61
+ )
45
62
 
46
63
  # eQTL support
47
64
  from .eqtl import (
@@ -65,6 +82,9 @@ from .finemapping import (
65
82
  validate_finemapping_df,
66
83
  )
67
84
 
85
+ # Forest plot support
86
+ from .forest import validate_forest_df
87
+
68
88
  # Gene track
69
89
  from .gene_track import get_nearest_gene, plot_gene_track
70
90
 
@@ -101,6 +121,9 @@ from .loaders import (
101
121
 
102
122
  # Logging configuration
103
123
  from .logging import disable_logging, enable_logging
124
+
125
+ # PheWAS support
126
+ from .phewas import validate_phewas_df
104
127
  from .plotter import LocusZoomPlotter
105
128
 
106
129
  # Reference data management
@@ -130,7 +153,10 @@ __all__ = [
130
153
  "get_ld_color",
131
154
  "get_ld_bin",
132
155
  "get_ld_color_palette",
156
+ "get_phewas_category_color",
157
+ "get_phewas_category_palette",
133
158
  "LEAD_SNP_COLOR",
159
+ "PHEWAS_CATEGORY_COLORS",
134
160
  # Gene track
135
161
  "get_nearest_gene",
136
162
  "plot_gene_track",
@@ -164,6 +190,10 @@ __all__ = [
164
190
  # Validation & Utils
165
191
  "ValidationError",
166
192
  "to_pandas",
193
+ # PheWAS
194
+ "validate_phewas_df",
195
+ # Forest plot
196
+ "validate_forest_df",
167
197
  # GWAS loaders
168
198
  "load_gwas",
169
199
  "load_plink_assoc",
@@ -187,4 +217,10 @@ __all__ = [
187
217
  "load_ensembl_genes",
188
218
  # Schema validation
189
219
  "LoaderValidationError",
220
+ # Ensembl integration
221
+ "get_genes_for_region",
222
+ "fetch_genes_from_ensembl",
223
+ "fetch_exons_from_ensembl",
224
+ "get_ensembl_species_name",
225
+ "clear_ensembl_cache",
190
226
  ]
@@ -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
+ ]