Skip to content

Commit 0f3cd0d

Browse files
authored
Add initial datasource module (#368)
1 parent 7f6d760 commit 0f3cd0d

File tree

4 files changed

+220
-0
lines changed

4 files changed

+220
-0
lines changed

tests/test_datasource.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import platform
2+
import pytest
3+
4+
from wfdb.io import (
5+
DataSource,
6+
DataSourceType,
7+
add_data_source,
8+
remove_data_source,
9+
reset_data_sources,
10+
)
11+
12+
from wfdb.io.datasource import _data_sources
13+
14+
LOCAL_PATH = (
15+
"C:\\Users\\Public\\data"
16+
if platform.system() == "Windows"
17+
else "/bigdata/smalldata"
18+
)
19+
20+
21+
class TestDataSource:
22+
def test_create_valid_local_ds(self):
23+
ds = DataSource(
24+
name="localds",
25+
ds_type=DataSourceType.LOCAL,
26+
uri=LOCAL_PATH,
27+
)
28+
assert ds
29+
30+
def test_create_invalid_local_ds(self):
31+
with pytest.raises(ValueError):
32+
DataSource(
33+
name="localds",
34+
ds_type=DataSourceType.LOCAL,
35+
uri="notabsolute",
36+
)
37+
38+
def test_create_valid_http_ds(self):
39+
ds = DataSource(
40+
name="httpds",
41+
ds_type=DataSourceType.HTTP,
42+
uri="http://bigdata.com",
43+
)
44+
assert ds.uri == "http://bigdata.com"
45+
46+
def test_create_invalid_http_ds(self):
47+
with pytest.raises(ValueError):
48+
DataSource(
49+
name="httpds",
50+
ds_type=DataSourceType.HTTP,
51+
uri="www.bigdata.com",
52+
)
53+
54+
def test_add_reset_ds(self):
55+
ds = DataSource(
56+
name="localds",
57+
ds_type=DataSourceType.LOCAL,
58+
uri=LOCAL_PATH,
59+
)
60+
add_data_source(ds)
61+
assert len(_data_sources) == 2
62+
assert _data_sources[ds.name] == ds
63+
# We rely on reset_data_sources for test cleanup.
64+
reset_data_sources(keep_pn=True)
65+
assert len(_data_sources) == 1
66+
67+
def test_add_multiple_ds(self):
68+
ds1 = DataSource(
69+
name="localds",
70+
ds_type=DataSourceType.LOCAL,
71+
uri=LOCAL_PATH,
72+
)
73+
add_data_source(ds1)
74+
ds2 = DataSource(
75+
name="anotherlocalds",
76+
ds_type=DataSourceType.LOCAL,
77+
uri=LOCAL_PATH,
78+
)
79+
add_data_source(ds2)
80+
81+
assert len(_data_sources) == 3
82+
assert _data_sources[ds1.name] == ds1
83+
assert _data_sources[ds2.name] == ds2
84+
reset_data_sources(keep_pn=True)
85+
86+
def test_remove_ds(self):
87+
ds = DataSource(
88+
name="localds",
89+
ds_type=DataSourceType.LOCAL,
90+
uri=LOCAL_PATH,
91+
)
92+
add_data_source(ds)
93+
remove_data_source("localds")
94+
assert len(_data_sources) == 1
95+
96+
def test_unique_ds_names(self):
97+
ds = DataSource(
98+
name="localds",
99+
ds_type=DataSourceType.LOCAL,
100+
uri=LOCAL_PATH,
101+
)
102+
add_data_source(ds)
103+
# Cannot set multiple data sources with the same name
104+
with pytest.raises(ValueError):
105+
add_data_source(ds)
106+
reset_data_sources(keep_pn=True)

wfdb/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@
2525
get_record_list,
2626
set_db_index_url,
2727
)
28+
29+
30+
from wfdb.io.datasource import (
31+
DataSource,
32+
DataSourceType,
33+
show_data_sources,
34+
add_data_source,
35+
remove_data_source,
36+
reset_data_sources,
37+
)
38+
39+
2840
from wfdb.plot.plot import plot_items, plot_wfdb, plot_all_records
2941

3042
from wfdb.version import __version__

wfdb/io/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,12 @@
2727
get_record_list,
2828
set_db_index_url,
2929
)
30+
31+
from wfdb.io.datasource import (
32+
DataSource,
33+
DataSourceType,
34+
show_data_sources,
35+
add_data_source,
36+
remove_data_source,
37+
reset_data_sources,
38+
)

wfdb/io/datasource.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from enum import Enum, auto
2+
from pathlib import Path
3+
from typing import Dict
4+
from urllib.parse import urlparse
5+
6+
7+
class DataSourceType(Enum):
8+
LOCAL = auto()
9+
HTTP = auto()
10+
11+
12+
class DataSource:
13+
def __init__(self, name: str, ds_type: DataSourceType, uri: str):
14+
self.name = name
15+
self.ds_type = ds_type
16+
self.uri = uri
17+
18+
def __str__(self):
19+
return f"{self.name} : {self.ds_type} : {self.uri}"
20+
21+
@property
22+
def uri(self):
23+
return self._uri
24+
25+
@uri.setter
26+
def uri(self, value: str):
27+
if self.ds_type == DataSourceType.LOCAL:
28+
path = Path(value)
29+
if not path.is_absolute():
30+
raise ValueError(
31+
"uri field for a LOCAL DataSource must be a valid absolute path"
32+
)
33+
elif self.ds_type is DataSourceType.HTTP:
34+
url = urlparse(value)
35+
if not url.netloc:
36+
raise ValueError(
37+
"uri field for an HTTP DataSource must be a valid URL"
38+
)
39+
self._uri = value
40+
41+
42+
_PHYSIONET_DATA_SOURCE = DataSource(
43+
"physionet",
44+
DataSourceType.HTTP,
45+
"https://physionet.org/files/",
46+
)
47+
48+
# Dict of configured data sources
49+
_data_sources: Dict[str, DataSource] = {"physionet": _PHYSIONET_DATA_SOURCE}
50+
51+
52+
def show_data_sources():
53+
"""
54+
Displays all configured data sources
55+
"""
56+
print("Data sources:")
57+
for _, ds in _data_sources.items():
58+
print(ds)
59+
60+
61+
def add_data_source(ds: DataSource):
62+
"""
63+
Add a data source to the set of configured data sources
64+
"""
65+
if ds.name in _data_sources:
66+
raise ValueError(
67+
f"There is already a configured data source with name: {ds.name}"
68+
)
69+
70+
_data_sources[ds.name] = ds
71+
72+
73+
def remove_data_source(ds_name: str):
74+
"""
75+
Remove a data source from the set of configured data sources
76+
"""
77+
del _data_sources[ds_name]
78+
79+
80+
def reset_data_sources(keep_pn: bool = False):
81+
"""
82+
Reset all configured data sources
83+
84+
Parameters
85+
----------
86+
87+
keep_pn : bool
88+
If True, keep the default physionet data source.
89+
90+
"""
91+
_data_sources.clear()
92+
if keep_pn:
93+
_data_sources["physionet"] = _PHYSIONET_DATA_SOURCE

0 commit comments

Comments
 (0)