fairfetched 0.0.1.dev202607271__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.
File without changes
@@ -0,0 +1,9 @@
1
+ from .dataset import Chembl, Papyrus
2
+
3
+ __all__ = ["Chembl", "Papyrus"]
4
+
5
+ if __name__ == "__main__":
6
+ from .papyrus import ensure_raw_files, latest
7
+
8
+ print(latest())
9
+ ensure_raw_files(latest())
@@ -0,0 +1,219 @@
1
+ from collections.abc import Sequence
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ import polars as pl
6
+
7
+ from fairfetched.utils import (
8
+ BASE_DIR,
9
+ ensure_sqlite_db_to_parquets,
10
+ ensure_untarred_sqlite,
11
+ ensure_url,
12
+ file_suffix_from_url,
13
+ lowercase_columns,
14
+ )
15
+ from fairfetched.utils.typing import BioactivityDBViews
16
+
17
+ CHEMBL_DIR = BASE_DIR / "chembl"
18
+
19
+
20
+ def _format_version(version: float | str) -> str:
21
+ if isinstance(version, int | float):
22
+ version = str(version)
23
+ if isinstance(version, Sequence) and not isinstance(version, str):
24
+ raise TypeError(f"invalid version type: {type(version)}")
25
+ if not isinstance(version, str):
26
+ try:
27
+ version = str(version)
28
+ except Exception:
29
+ raise TypeError(f"invalid version type: {type(version)}")
30
+
31
+ version = version.lstrip("0")
32
+ if "." in version:
33
+ version = version.split(".")[0].zfill(2) + "." + version.split(".")[1]
34
+ # for canonicalize the version number 22.1 and 24.1 and left pad with a zero if needed
35
+ return version.replace(".", "_").replace("_0", "").zfill(2)
36
+
37
+
38
+ def _version_to_url(version: str):
39
+ base = "https://ftp.ebi.ac.uk/pub/databases/chembl/ChEMBLdb/releases"
40
+ return f"{base}/chembl_{version}/chembl_{version}_sqlite.tar.gz"
41
+
42
+
43
+ CHEMBL_VERSIONS: dict[str, dict[str, str]] = {
44
+ version: {"sql_db": _version_to_url(version)}
45
+ for version in sorted(map(_format_version, list(range(1, 38)) + ["24_1", "22_1"]))
46
+ }
47
+
48
+
49
+ def available_versions() -> tuple[str, ...]:
50
+ return tuple(CHEMBL_VERSIONS.keys())
51
+
52
+
53
+ def latest() -> str:
54
+ return available_versions()[-1]
55
+
56
+
57
+ def source_urls(version: str) -> dict[str, str]:
58
+ return CHEMBL_VERSIONS[str(version)]
59
+
60
+
61
+ def ensure_raw_files(
62
+ version: str, raw_dir: Path | str | None = None, force=False
63
+ ) -> dict[str, Path]:
64
+ """Download the original SQL database with its original name and compression."""
65
+ if raw_dir is None:
66
+ raw_dir = CHEMBL_DIR / version
67
+ raw_dir = Path(raw_dir)
68
+ return {
69
+ name: ensure_url(
70
+ url=url, path=raw_dir / f"{name}{file_suffix_from_url(url)}", force=force
71
+ )
72
+ for name, url in source_urls(version).items()
73
+ }
74
+
75
+
76
+ def ensure_parquet_tables(
77
+ raw_paths: dict[str, Path], table_dir: Path | str | Any | None = None
78
+ ) -> dict[str, Path]:
79
+ sql_tar_gz_path = raw_paths["sql_db"]
80
+ if table_dir is None:
81
+ table_dir = Path(sql_tar_gz_path).parent / "extracted"
82
+ table_dir = Path(table_dir)
83
+ table_dir.mkdir(exist_ok=True, parents=True)
84
+
85
+ raw_sql = ensure_untarred_sqlite(sql_tar_gz_path)
86
+ # the untarred should also stay so that we have access.....
87
+ # #@TODO: perhaps make tables deterministic for chembl to circumvent
88
+ parquets = ensure_sqlite_db_to_parquets(raw_sql, cache_dir=table_dir, force=False)
89
+
90
+ return parquets
91
+
92
+
93
+ def cleanly_scan_parquet(path_: Path | str) -> pl.LazyFrame:
94
+ """scans parquet paths and lazily handles null value conversion to None"""
95
+ return (
96
+ pl.scan_parquet(path_)
97
+ .pipe(lowercase_columns)
98
+ .fill_nan(None)
99
+ .with_columns(
100
+ pl.col(pl.String).replace({"": None}),
101
+ )
102
+ )
103
+
104
+
105
+ def cleanly_scan_parquet_tables(
106
+ parquet_paths: dict[str, Path],
107
+ ) -> dict[str, pl.LazyFrame]:
108
+ """scans parquet paths and lazily handles null value conversion to None"""
109
+ return {name: cleanly_scan_parquet(path_) for name, path_ in parquet_paths.items()}
110
+
111
+
112
+ # def build_views(lfs: dict[str, pl.LazyFrame]) -> ComposedLFDict:
113
+ def build_views(parquet_paths: dict[str, Path]) -> BioactivityDBViews:
114
+ """Build joined domain views from the scanned source tables."""
115
+ return {
116
+ "bioactivity": _bioactivities(parquet_paths),
117
+ "compounds": _compounds(parquet_paths),
118
+ "proteins": cleanly_scan_parquet(parquet_paths["protein"]),
119
+ "components": _components(parquet_paths),
120
+ }
121
+
122
+
123
+ def _bioactivities(parquet_paths: dict[str, Path]) -> pl.LazyFrame:
124
+ lfs = cleanly_scan_parquet_tables(parquet_paths)
125
+ return (
126
+ # dfs["activity_properties"]
127
+ # .join(dfs["activities"], on="activity_id", how="left")
128
+ # .join(dfs["action_type"], on="action_type", how="left")
129
+ # .join
130
+ lfs["bioactivity"]
131
+ .join(
132
+ lfs["protein"],
133
+ on="target_id",
134
+ how="left",
135
+ maintain_order="left",
136
+ validate="m:1", # one unique protein only from right, can reoccur within compounds.
137
+ )
138
+ .join(lfs["action_type"], on="action_type", how="left", suffix="_action_type")
139
+ .join(
140
+ lfs["assays"], on="assay_id", how="left", suffix="_assay"
141
+ ) # doc_ids to this
142
+ .join(lfs["assay_type"], on="assay_type", how="left", suffix="_assay_type")
143
+ # .join(lfs, on="bao_format")
144
+ # .join(dfs[])
145
+ )
146
+
147
+
148
+ def _components(parquet_paths: dict[str, Path]) -> pl.LazyFrame:
149
+ lfs = cleanly_scan_parquet_tables(parquet_paths)
150
+ return (
151
+ lfs["component_sequences"]
152
+ .join(
153
+ lfs["component_class"],
154
+ on="component_id",
155
+ how="left",
156
+ suffix="_class",
157
+ validate="1:m",
158
+ )
159
+ .join(
160
+ lfs["component_domains"].join(
161
+ lfs["domains"],
162
+ on="domain_id",
163
+ how="left",
164
+ suffix="_domains",
165
+ validate="m:1",
166
+ ),
167
+ on="component_id",
168
+ how="left",
169
+ suffix="_domains",
170
+ validate="1:m",
171
+ )
172
+ # .join(
173
+ # dfs["component_synonyms"],
174
+ # on="component_id",
175
+ # how="left",
176
+ # suffix="_synonyms",
177
+ # validate="m:m",
178
+ # )
179
+ )
180
+
181
+
182
+ def _compounds(parquet_paths: dict[str, Path]) -> pl.LazyFrame:
183
+ lfs = cleanly_scan_parquet_tables(parquet_paths)
184
+ return (
185
+ lfs["molecule_dictionary"]
186
+ .join(
187
+ lfs["compound_properties"],
188
+ on="molregno",
189
+ how="left",
190
+ suffix="_compound_properties",
191
+ validate="1:1",
192
+ )
193
+ .join(
194
+ lfs["compound_structures"],
195
+ on="molregno",
196
+ how="left",
197
+ suffix="_compound_structures",
198
+ validate="1:1",
199
+ )
200
+ .join(
201
+ lfs["compound_records"].join(
202
+ lfs["docs"], on="doc_id", how="left", suffix="_doc", validate="m:1"
203
+ ),
204
+ on="molregno",
205
+ how="left",
206
+ suffix="_compound_records",
207
+ validate="1:m",
208
+ )
209
+ .join(
210
+ lfs["compound_structural_alerts"],
211
+ # .join(
212
+ # dfs["docs"], on="doc_id", how="left", suffix="_doc",validate="m:1"
213
+ # )
214
+ on="molregno",
215
+ how="left",
216
+ suffix="_compound_structural_alerts",
217
+ validate="1:m",
218
+ )
219
+ )
@@ -0,0 +1,167 @@
1
+ from dataclasses import dataclass
2
+ from functools import cached_property
3
+ from pathlib import Path
4
+
5
+ from polars import LazyFrame
6
+
7
+ from fairfetched.get import chembl, papyrus
8
+ from fairfetched.utils import BASE_DIR
9
+ from fairfetched.utils.typing import BioactivityDBViews, DatasetGetModule
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class _Base:
14
+ """lightweight wrapper serving as the main point for a united API per database"""
15
+
16
+ version: str
17
+ raw_paths: dict[str, Path]
18
+ parquet_paths: dict[str, Path]
19
+ dir: Path
20
+ module: DatasetGetModule
21
+
22
+ def __str__(self) -> str:
23
+ return f"{self.name}_{self.version}"
24
+
25
+ def __repr__(self) -> str:
26
+ return f"<{self.name.capitalize()}_{self.version} at {self.dir}"
27
+
28
+ def __hash__(self):
29
+ return hash(
30
+ (self.version, str(self.sources), str(self.raw_paths), self.module.__name__)
31
+ )
32
+
33
+ @cached_property
34
+ def name(self) -> str:
35
+ return self.module.__name__.split(".")[-1]
36
+
37
+ @cached_property
38
+ def sources(self) -> dict[str, str]:
39
+
40
+ sources = self.module.source_urls(self.version)
41
+ return sources
42
+
43
+ @property
44
+ def lfs(self) -> dict[str, LazyFrame]:
45
+ return self.module.cleanly_scan_parquet_tables(self.parquet_paths)
46
+
47
+ @property
48
+ def views(self) -> BioactivityDBViews:
49
+ return self.module.build_views(self.parquet_paths)
50
+
51
+ @property
52
+ def bioactivity(self) -> LazyFrame:
53
+ return self.views["bioactivity"]
54
+
55
+ @property
56
+ def compounds(self) -> LazyFrame:
57
+ return self.views["compounds"]
58
+
59
+ @classmethod
60
+ def available_versions(cls) -> tuple[str, ...]:
61
+ return cls.module.available_versions()
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class Chembl(_Base):
66
+ module: DatasetGetModule = chembl
67
+
68
+ @staticmethod
69
+ def get_available_versions():
70
+ return chembl.available_versions()
71
+
72
+ @cached_property
73
+ def activity(self) -> LazyFrame:
74
+ return self.views["bioactivity"]
75
+
76
+ @cached_property
77
+ def raw_sql_db_path(self) -> Path:
78
+ return self.raw_paths["sql_db"]
79
+
80
+ @classmethod
81
+ def from_version(
82
+ cls,
83
+ version: str | int | float, # ruff: ignore[PYI041]
84
+ root_dir: Path | str = f"{BASE_DIR}/chembl",
85
+ force: bool = False,
86
+ ) -> "Chembl":
87
+ """Downloads Chembl for version if not yet present in the given cache directory"""
88
+ version = chembl._format_version(version)
89
+ dir = Path(root_dir) / version
90
+
91
+ raw_paths: dict[str, Path] = chembl.ensure_raw_files(
92
+ version, raw_dir=dir / "raw", force=force
93
+ )
94
+
95
+ parquet_paths = chembl.ensure_parquet_tables(
96
+ raw_paths, table_dir=dir / "parquet"
97
+ )
98
+ return Chembl(
99
+ version=version,
100
+ raw_paths=raw_paths,
101
+ parquet_paths=parquet_paths,
102
+ dir=dir,
103
+ module=cls.module,
104
+ )
105
+
106
+ @classmethod
107
+ def from_latest(
108
+ cls,
109
+ root_dir: Path | str = f"{BASE_DIR}/chembl",
110
+ force: bool = False,
111
+ ) -> "Chembl":
112
+ return cls.from_version(version=chembl.latest(), root_dir=root_dir, force=force)
113
+
114
+
115
+ @dataclass(frozen=True)
116
+ class Papyrus(_Base):
117
+ module: DatasetGetModule = papyrus
118
+
119
+ @staticmethod
120
+ def get_available_versions():
121
+ return papyrus.available_versions()
122
+
123
+ @property
124
+ def proteins(self) -> LazyFrame:
125
+ return self.views["proteins"]
126
+
127
+ @property
128
+ def full_data(self) -> LazyFrame:
129
+ """all data (bioactivity + protein data),
130
+ composed into one flat tabular format LazyFrame"""
131
+ return self.views["full"]
132
+
133
+ @classmethod
134
+ def from_version(
135
+ cls,
136
+ version: str,
137
+ root_dir: Path | str = f"{BASE_DIR}/papyrus",
138
+ ) -> "Papyrus":
139
+ """Downloads Chembl for version if not yet present in the given cache directory"""
140
+ dir = Path(root_dir) / version
141
+ raw_paths: dict[str, Path] = papyrus.ensure_raw_files(
142
+ version, raw_dir=dir / "raw"
143
+ )
144
+ parquet_paths: dict[str, Path] = papyrus.ensure_parquet_tables(
145
+ raw_paths, table_dir=dir / "parquet"
146
+ )
147
+ return Papyrus(
148
+ version=version,
149
+ raw_paths=raw_paths,
150
+ parquet_paths=parquet_paths,
151
+ dir=dir,
152
+ module=cls.module,
153
+ )
154
+
155
+ @classmethod
156
+ def from_latest(
157
+ cls,
158
+ root_dir: Path | str = f"{BASE_DIR}/papyrus",
159
+ ) -> "Papyrus":
160
+ return cls.from_version(version=papyrus.latest(), root_dir=root_dir)
161
+
162
+
163
+ if __name__ == "__main__":
164
+ p = Papyrus.from_latest()
165
+ p.views["proteins"]
166
+
167
+ p.lfs["proteins"]
@@ -0,0 +1,156 @@
1
+ """Papyrus dataset utilities for downloading, cleaning, and joining bioactivity and protein data.
2
+
3
+ This module provides functions to ensure the presence of raw and cleaned Papyrus dataset files,
4
+ and defines the Papyrus_57 database configuration.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import polars as pl
11
+
12
+ from fairfetched.utils import (
13
+ BASE_DIR,
14
+ decompress_and_scan_tsvxz,
15
+ ensure_url,
16
+ file_suffix_from_url,
17
+ lowercase_columns,
18
+ )
19
+ from fairfetched.utils.typing import BioactivityDBViews
20
+
21
+ PAPYRUS_VERSIONS: dict[str, dict[str, str]] = {
22
+ "05.6": {
23
+ "bioactivity": "https://zenodo.org/records/7373214/files/05.6_combined_set_with_stereochemistry.tsv.xz",
24
+ # "bioactivity_nostereochemistry": "https://zenodo.org/records/7373214/files/05.6_combined_set_without_stereochemistry.tsv.xz",
25
+ "readme": "https://zenodo.org/records/7373214/files/README.txt",
26
+ "protein": "https://zenodo.org/records/7373214/files/05.6_combined_set_protein_targets.tsv.xz",
27
+ },
28
+ "05.7": {
29
+ "bioactivity": "https://zenodo.org/records/13987985/files/05.7_combined_set_with_stereochemistry.tsv.xz",
30
+ # "bioactivity_nostereochemistry": "https://zenodo.org/records/13987985/files/05.7_combined_set_without_stereochemistry.tsv.xz",
31
+ "readme": "https://zenodo.org/records/13987985/files/README.txt",
32
+ "protein": "https://zenodo.org/records/13987985/files/05.7_combined_set_protein_targets.tsv.xz",
33
+ },
34
+ }
35
+
36
+
37
+ def available_versions() -> tuple[str, ...]:
38
+ return tuple(PAPYRUS_VERSIONS.keys())
39
+
40
+
41
+ def latest() -> str:
42
+ return available_versions()[-1]
43
+
44
+
45
+ def source_urls(version: str) -> dict[str, str]:
46
+ return PAPYRUS_VERSIONS[str(version)]
47
+
48
+
49
+ def ensure_raw_files(
50
+ version: str, raw_dir: Path | str | Any | None = None
51
+ ) -> dict[str, Path]:
52
+ """Download if missing, return path to raw file."""
53
+ if raw_dir is None:
54
+ raw_dir = BASE_DIR / "papyrus" / version / "raw"
55
+ raw_dir = Path(raw_dir)
56
+
57
+ return {
58
+ name: ensure_url(url=url, path=raw_dir / f"{name}{file_suffix_from_url(url)}")
59
+ for name, url in source_urls(version).items()
60
+ }
61
+
62
+
63
+ def ensure_parquet_tables(
64
+ raw_paths: dict[str, Path],
65
+ table_dir: Path | str | None = None,
66
+ ) -> dict[str, Path]:
67
+ """Downloads if missing, extracts to streamable parquets for lazy loading,
68
+ returns paths to raw files and parquet files. Ignores README.txt file
69
+
70
+ parquet_dir default is raw_filepath_dir.parent / "parquet"
71
+
72
+ """
73
+ if table_dir is None:
74
+ table_dir = next(iter(raw_paths.values())).parent.parent / "parquet"
75
+ table_dir = Path(table_dir)
76
+ table_dir.mkdir(exist_ok=True, parents=True)
77
+ schema_overrides = {
78
+ "Year": pl.Int32,
79
+ "pchembl_value_Mean": pl.Float64,
80
+ "pchembl_value_StdDev": pl.Float64,
81
+ "pchembl_value_SEM": pl.Float64,
82
+ # because of stray floats in pchembl_value_N, we do float -> int
83
+ "pchembl_value_N": pl.Float64,
84
+ "pchembl_value_Median": pl.Float64,
85
+ "pchembl_value_MAD": pl.Float64,
86
+ }
87
+
88
+ filepath_dict = {}
89
+ for name, path_ in raw_paths.items():
90
+ if path_.name.lower() == "readme.txt":
91
+ continue
92
+ new_path = table_dir / f"{path_.stem.split('.')[0]}.parquet"
93
+ filepath_dict[name] = new_path
94
+ if new_path.exists():
95
+ continue
96
+ decompress_and_scan_tsvxz(
97
+ path_,
98
+ separator="\t",
99
+ infer_schema=False,
100
+ schema_overrides=schema_overrides,
101
+ null_values=["NA", ""], # which values to take as None
102
+ ).cast(
103
+ {"pchembl_value_N": pl.Int64} if name == "bioactivity" else {}
104
+ ).sink_parquet(new_path)
105
+ return filepath_dict
106
+
107
+
108
+ def cleanly_scan_parquet_tables(
109
+ parquet_paths: dict[str, Path],
110
+ ) -> dict[str, pl.LazyFrame]:
111
+ """Scan table Parquet files and apply dataset-specific column cleanup."""
112
+
113
+ return {
114
+ "protein": (
115
+ pl.scan_parquet(parquet_paths["protein"])
116
+ .pipe(lowercase_columns)
117
+ .rename({"uniprotid": "uniprot_id"})
118
+ ),
119
+ "bioactivity": (
120
+ pl.scan_parquet(parquet_paths["bioactivity"]).pipe(lowercase_columns)
121
+ ),
122
+ }
123
+
124
+
125
+ def build_views(parquet_paths: dict[str, Path]) -> BioactivityDBViews:
126
+ """Build joined domain views from the scanned source tables."""
127
+ lfs = cleanly_scan_parquet_tables(parquet_paths)
128
+ return {
129
+ "bioactivity": lfs["bioactivity"],
130
+ "compounds": lfs["bioactivity"]
131
+ .drop(
132
+ "activity_id",
133
+ )
134
+ .unique(("connectivity", "inchikey", "inchi")),
135
+ "full": lfs["bioactivity"].join(
136
+ lfs["protein"],
137
+ on="target_id",
138
+ how="left",
139
+ maintain_order="left",
140
+ validate="m:1", # one unique protein only from right, can reoccur within compounds.
141
+ ),
142
+ "proteins": lfs["protein"],
143
+ }
144
+
145
+
146
+ def help() -> None:
147
+ """prints out example usage"""
148
+ print("""
149
+ Example usage:
150
+ ```
151
+ raw = ensure_raw_files("05.7")
152
+ tables = cleanly_scan_parquet_tables(raw)
153
+ views = build_views(tables)
154
+ views["full"].sink_parquet("output.parquet")
155
+ ```
156
+ """)
@@ -0,0 +1,23 @@
1
+ # mol_expr.pyi
2
+ import polars as pl
3
+
4
+ from .mol_expr import MolExprs, MolFn
5
+
6
+ # class MolExpr:
7
+ # def __init__(self, expr: pl.Expr) -> None: ...
8
+ # def from_smiles(self) -> pl.Expr: ...
9
+ # def from_inchi(self) -> pl.Expr: ...
10
+ # def to_smiles(self) -> pl.Expr: ...
11
+ # def to_inchi(self) -> pl.Expr: ...
12
+ # def to_inchikey(self) -> pl.Expr: ...
13
+ # def to_kekulised_smiles(self) -> pl.Expr: ...
14
+ # def num_atoms(self) -> pl.Expr: ...
15
+ # def num_heavy_atoms(self) -> pl.Expr: ...
16
+ # def standardise(self, *steps: MolFn) -> pl.Expr: ...
17
+
18
+ # teach ty that pl.Expr has a .mol accessor
19
+ class _ExprMolAccessor:
20
+ mol: MolExprs
21
+
22
+ # ty picks this up via the __getattr__ extension pattern
23
+ def __getattr__(name: str) -> MolExprs: ...
File without changes
@@ -0,0 +1,56 @@
1
+ import logging as lg
2
+
3
+ try:
4
+ from chembl_structure_pipeline import ( # ty:ignore[unresolved-import] #ty:ignore[unused-ignore-comment] #ty:ignore[unused-ignore-comment]
5
+ get_parent_mol as chembl_get_parent_mol,
6
+ )
7
+ from chembl_structure_pipeline import ( # ty:ignore[unresolved-import] #ty:ignore[unused-ignore-comment] #ty:ignore[unused-ignore-comment]
8
+ standardize_mol as chembl_standardize,
9
+ )
10
+ except ImportError as e:
11
+ lg.warning("""
12
+ you should install chembl_structure_pipeline if you want
13
+ to use chembl standardisation
14
+ """)
15
+ CHEMBL_IMPORT_ERROR = e
16
+
17
+ def chembl_get_parent_mol(mol):
18
+ """placeholder for chembl_structure_pipeline.get_parent_mol in case
19
+ the import doesn't work.
20
+ raises respective import error when called"""
21
+ lg.warning("""
22
+ please install `chembl_structure_pipeline` if you want to use
23
+ ChEMBL standardisation
24
+ """)
25
+ raise CHEMBL_IMPORT_ERROR
26
+
27
+ def chembl_standardize(mol):
28
+ """placeholder for chembl_structure_pipeline.chembl_standardize in case
29
+ the import doesn't work.
30
+
31
+ raises respective import error when called.
32
+ """
33
+ lg.warning("""
34
+ please install `chembl_structure_pipeline` if you want to use
35
+ ChEMBL standardisation
36
+ """)
37
+ raise CHEMBL_IMPORT_ERROR
38
+
39
+
40
+ try:
41
+ from papyrus_structure_pipeline import ( # ty:ignore[unresolved-import] #ty:ignore[unused-ignore-comment] #ty:ignore[unused-ignore-comment]
42
+ standardize as _papyrus_standardize,
43
+ )
44
+
45
+
46
+ except ImportError as e:
47
+ PAPYRUS_IMPORT_ERROR = e
48
+
49
+ def _papyrus_standardize(mol, **kwargs):
50
+ """placeholder for papyrus_standardize in case imports dont work.
51
+ raises respective import error when called"""
52
+ lg.warning("""
53
+ please install `papyrus_structure_pipeline` if you want to use
54
+ Papyrus standardisation
55
+ """)
56
+ raise PAPYRUS_IMPORT_ERROR