Skip to content

Commit e0cf7a2

Browse files
authored
Turn into package (#6)
* Add github builds * Add more to .gitignore
1 parent 8c2e1c7 commit e0cf7a2

File tree

9 files changed

+420
-0
lines changed

9 files changed

+420
-0
lines changed
File renamed without changes.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: build
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
11+
solo:
12+
# Matrix builds without pydicom
13+
runs-on: ubuntu-latest
14+
timeout-minutes: 20
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
python-version: [3.6, 3.7, 3.8]
19+
20+
steps:
21+
- uses: actions/checkout@v2
22+
- name: Set up Python ${{ matrix.python-version }}
23+
uses: actions/setup-python@v2
24+
with:
25+
python-version: ${{ matrix.python-version }}
26+
- name: Install and run pytest
27+
run: |
28+
python -m pip install --upgrade pip
29+
pip install pytest
30+
pytest
31+
32+
pydicom-dev:
33+
# Matrix builds with development pydicom
34+
runs-on: ubuntu-latest
35+
timeout-minutes: 20
36+
strategy:
37+
fail-fast: false
38+
matrix:
39+
python-version: [3.6, 3.7, 3.8]
40+
41+
steps:
42+
- uses: actions/checkout@v2
43+
- name: Set up Python ${{ matrix.python-version }}
44+
uses: actions/setup-python@v2
45+
with:
46+
python-version: ${{ matrix.python-version }}
47+
- name: Install dependencies
48+
run: |
49+
python -m pip install --upgrade pip
50+
pip install pytest
51+
pip install git+https://github.com/pydicom/pydicom
52+
- name: Test with pytest
53+
run: |
54+
pytest
55+
56+
pydicom-release:
57+
# Matrix builds with released pydicom
58+
runs-on: ubuntu-latest
59+
timeout-minutes: 20
60+
strategy:
61+
fail-fast: false
62+
matrix:
63+
python-version: [3.6, 3.7, 3.8]
64+
65+
steps:
66+
- uses: actions/checkout@v2
67+
- name: Set up Python ${{ matrix.python-version }}
68+
uses: actions/setup-python@v2
69+
with:
70+
python-version: ${{ matrix.python-version }}
71+
- name: Install dependencies
72+
run: |
73+
python -m pip install --upgrade pip
74+
pip install pytest pydicom
75+
- name: Test with pytest
76+
run: |
77+
pytest

.gitignore

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# .gitignore file
2+
3+
# Backup files
4+
*~
5+
__pycache__/
6+
*.py[cod]
7+
*$py.class
8+
9+
# Lock files used by the Emacs editor.
10+
.\#*
11+
12+
# Temporary files used by editors.
13+
*.swp
14+
*.orig
15+
*.bak
16+
17+
# A hidden file created by the Mac OS X Finder.
18+
.DS_Store
19+
20+
# python byte-code files
21+
*.pyc
22+
*.pyo
23+
24+
# Build/dist files
25+
dist/*
26+
build/*
27+
distribute*.tar.gz
28+
py3source
29+
.tox/*
30+
31+
# Distribution / packaging
32+
.Python
33+
env/
34+
develop-eggs/
35+
downloads/
36+
eggs/
37+
.eggs/
38+
lib/
39+
lib64/
40+
parts/
41+
sdist/
42+
var/
43+
wheels/
44+
*.egg-info/
45+
.installed.cfg
46+
*.egg
47+
48+
# Docs build
49+
docs/_build/*
50+
doc/_build/*
51+
doc/auto_examples/*
52+
doc/generated/*
53+
doc/reference/generated/*
54+
55+
# coverage.py files
56+
.coverage
57+
58+
# PyCharm IDE files
59+
*.idea*
60+
61+
62+
# jupyter notebooks
63+
*.ipynb
64+
.ipynb_checkpoints/*
65+
tests/test_pixel.py
66+
67+
# mypy
68+
pydicom-data/.mypy_cache/*
69+
70+
# vscode
71+
.vscode/*
72+
73+
# Unit test / coverage reports
74+
htmlcov/
75+
.tox/
76+
.coverage
77+
.coverage.*
78+
.cache
79+
nosetests.xml
80+
coverage.xml
81+
*,cover
82+
.hypothesis/
83+
.pytest_cache

data_store/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
from data_store._version import __version__
3+
from data_store.utils import DataStore

data_store/_version.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""Version information for pydicom-data based on PEP396 and 440."""
2+
3+
4+
import re
5+
6+
7+
__version__ = '1.0.0.dev0'
8+
9+
10+
VERSION_PATTERN = r"""
11+
v?
12+
(?:
13+
(?:(?P<epoch>[0-9]+)!)? # epoch
14+
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
15+
(?P<pre> # pre-release
16+
[-_\.]?
17+
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
18+
[-_\.]?
19+
(?P<pre_n>[0-9]+)?
20+
)?
21+
(?P<post> # post release
22+
(?:-(?P<post_n1>[0-9]+))
23+
|
24+
(?:
25+
[-_\.]?
26+
(?P<post_l>post|rev|r)
27+
[-_\.]?
28+
(?P<post_n2>[0-9]+)?
29+
)
30+
)?
31+
(?P<dev> # dev release
32+
[-_\.]?
33+
(?P<dev_l>dev)
34+
[-_\.]?
35+
(?P<dev_n>[0-9]+)?
36+
)?
37+
)
38+
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
39+
"""
40+
41+
42+
def is_canonical(version: str) -> bool:
43+
"""Return True if `version` is a PEP440 conformant version."""
44+
match = re.match(
45+
r'^([1-9]\d*!)?(0|[1-9]\d*)'
46+
r'(\.(0|[1-9]\d*))'
47+
r'*((a|b|rc)(0|[1-9]\d*))'
48+
r'?(\.post(0|[1-9]\d*))'
49+
r'?(\.dev(0|[1-9]\d*))?$', version)
50+
51+
return match is not None
52+
53+
54+
assert is_canonical(__version__)

data_store/tests/test_pydicom.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Tests for the interface with pydicom."""
2+
3+
import os
4+
from pathlib import Path
5+
6+
import pytest
7+
8+
from pydicom.data import get_testdata_file
9+
10+
11+
class TestPydicom:
12+
"""Test the interface with pydicom works correctly."""
13+
def setup(self):
14+
self.data_path = Path(__file__).resolve().parent.parent.parent / "data"
15+
16+
def test_pydicom_local(self):
17+
"""Test that pydicom gets its own test data."""
18+
fname = "CT_small.dcm"
19+
assert "pydicom/data/test_files" in get_testdata_file(fname)
20+
21+
def test_pydicom_external(self):
22+
"""Test that pydicom uses external data sources first."""
23+
fname = "693_UNCI.dcm"
24+
assert os.fspath(self.data_path / fname) == get_testdata_file(fname)

data_store/tests/test_utils.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Tests for utils.py"""
2+
3+
import os
4+
from pathlib import Path
5+
6+
import pytest
7+
8+
from data_store import DataStore
9+
10+
11+
class TestDataStore:
12+
"""Test the interface for the data sources"""
13+
def setup(self):
14+
self.data_path = Path(__file__).resolve().parent.parent.parent / "data"
15+
self.fname = "693_UNCI.dcm"
16+
17+
def test_data_path(self):
18+
"""Test the path to the data is correct."""
19+
s = DataStore()
20+
assert self.data_path == s.data_path
21+
22+
def test_get_path(self):
23+
"""Test retrieving a file path."""
24+
s = DataStore()
25+
result = s.get_path(self.fname)
26+
assert isinstance(result, str)
27+
assert os.fspath(self.data_path / self.fname) == result
28+
29+
with pytest.raises(ValueError, match=r"No file found named"):
30+
s.get_path("693_UNCI")
31+
32+
def test_get_path_raises_bad_fname(self):
33+
"""Test get_path raises if no matching filename."""
34+
s = DataStore()
35+
msg = r"No file found named 'no matching name.txt'"
36+
with pytest.raises(ValueError, match=msg):
37+
s.get_path("no matching name.txt")
38+
39+
def test_get_path_raises_bad_dtype(self):
40+
"""Test get_path raises if unsupported dtype."""
41+
s = DataStore()
42+
msg = r"No files available for the data type"
43+
with pytest.raises(ValueError, match=msg):
44+
s.get_path(self.fname, dtype=1)
45+
46+
def test_get_paths(self):
47+
"""Test get_paths."""
48+
s = DataStore()
49+
files = s.get_paths('693*')
50+
assert len(files) == 3
51+
assert isinstance(files[0], str)
52+
53+
def test_get_paths_bad_dtype(self):
54+
"""Test get_paths returns empty list if unsupported dtype."""
55+
s = DataStore()
56+
assert [] == s.get_paths("*", dtype=1)

data_store/utils.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
2+
from enum import IntEnum
3+
import fnmatch
4+
import os
5+
from pathlib import Path
6+
from typing import List
7+
8+
9+
class DataTypes(IntEnum):
10+
"""Constants for data types."""
11+
DATASET = 0
12+
CHARSET = 1
13+
PALETTE = 2
14+
DICOMDIR = 3
15+
16+
17+
class DataStore:
18+
"""Interface to pydicom-data data storage.
19+
20+
Attributes
21+
----------
22+
data_path : pathlib.Path
23+
The absolute path to the data directory.
24+
"""
25+
def __init__(self) -> None:
26+
"""Initialise a new Interface."""
27+
self.data_path = Path(__file__).resolve().parent.parent / "data"
28+
29+
def get_path(self, name: str, dtype: int = DataTypes.DATASET) -> str:
30+
"""Return the absolute path to the first file with filename `name`
31+
32+
Parameters
33+
----------
34+
name : str
35+
The filename of the file
36+
dtype : int, optional
37+
The type of data to search for, default ``0`` (DICOM dataset).
38+
39+
Returns
40+
-------
41+
str
42+
The absolute path to the first file found with filename `name`.
43+
44+
Raises
45+
------
46+
ValueError
47+
If no file found with a matching filename.
48+
"""
49+
if dtype != DataTypes.DATASET:
50+
raise ValueError("No files available for the data type")
51+
52+
matches = [m for m in self.data_path.glob(name)]
53+
if matches:
54+
return os.fspath(matches[0])
55+
56+
raise ValueError(f"No file found named '{name}'")
57+
58+
def get_paths(self, pattern: str, dtype: int = DataTypes.DATASET) -> List[str]:
59+
"""Return a list of absolute paths for files matching `pattern`.
60+
61+
Parameters
62+
----------
63+
pattern : str
64+
A string pattern to filter the files.
65+
dtype : int, optional
66+
The type of data to search for, default ``0`` (DICOM dataset).
67+
68+
Returns
69+
-------
70+
list of str
71+
A list of absolute paths to files with filenames matching
72+
`pattern`.
73+
"""
74+
if dtype != DataTypes.DATASET:
75+
return []
76+
77+
return [os.fspath(p) for p in self.data_path.glob(pattern)]

0 commit comments

Comments
 (0)