Skip to content

Commit

Permalink
docs|test: address doctest issues
Browse files Browse the repository at this point in the history
the `README`'s `doctest` now passes

Signed-off-by: Bryant Finney <[email protected]>
  • Loading branch information
bryant-finney committed Sep 2, 2024
1 parent 161e7d2 commit f08b9d8
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 55 deletions.
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ repos:
rev: 0.7.17
hooks:
- id: mdformat
additional_dependencies:
- mdformat-gfm
- mdformat-frontmatter
- mdformat-footnote

- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.41.0
Expand Down
33 changes: 19 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

A lightweight, cross-platform, blazingly fast implementation of the `C-ECHO`\[^1\] DICOM procedure. 🔥
A lightweight, cross-platform, blazingly fast implementation of the `C-ECHO`[^1] DICOM procedure. 🔥

This package implements a service class user (SCU)\[^2\] app which functions like a `ping`, testing that the peer service class provider (SCP)\[^2\] is accepting associations for the given AE titles\[^3\].
This package implements a service class user (SCU)[^2] app which functions like a `ping`, testing that the peer service class provider (SCP)[^2] is accepting associations for the given AE titles[^3].

Both a simple CLI and a Python API are provided for easy integration with your DICOM projects.

Expand Down Expand Up @@ -61,26 +61,31 @@ To send a `C-ECHO` request to `localhost:11111`:

The `dicom_echo` module provides a simple API for sending `C-ECHO` requests:

<!--
```python
>>> address = getfixture('scpserver')
```
-->

```python
>>> import dicom_echo as echo

>>> echo.send('localhost:11111')
>>> echo.send(address)
0

```

See the [API documentation](https://dicom-echo.readthedocs.io/en/latest/) for more details.

\[^1\]: for additional details, see [9.3.5 `C-ECHO` protocol | DICOM PS3.7 2024c - Message Exchange](https://dicom.nema.org/medical/dicom/current/output/chtml/part07/sect_9.3.5.html#sect_9.3.5.1)
\[^2\]:
[6.7 Service Class Specification | DICOM PS3.4 2024c - Service Class Specifications](https://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_6.7.html#:~:text=The%20SCU%20or%20user%20agent,are%20determined%20during%20Association%20establishment.) for the definitions of service class user (SCU) and service class provider (SCP):
[^1]: for additional details, see [9.3.5 `C-ECHO` protocol | DICOM PS3.7 2024c - Message Exchange](https://dicom.nema.org/medical/dicom/current/output/chtml/part07/sect_9.3.5.html#sect_9.3.5.1)

```
> The SCU or user agent acts as the 'client,' while the SCP or origin server acts as the 'server'. For DIMSE based services the SCU/SCP roles are determined during Association establishment
```
[^2]: [6.7 Service Class Specification | DICOM PS3.4 2024c - Service Class Specifications](https://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_6.7.html#:~:text=The%20SCU%20or%20user%20agent,are%20determined%20during%20Association%20establishment.) for the definitions of service class user (SCU) and service class provider (SCP):

\[^3\]:
[C.1: DICOM Application Entity Titles | DICOM PS3.8 2024c - Network Communication Support for Message Exchange](https://dicom.nema.org/medical/dicom/current/output/chtml/part08/chapter_C.html):
> The SCU or user agent acts as the 'client,' while the SCP or origin server acts as the 'server'. For DIMSE based services the SCU/SCP roles are determined during Association establishment
```
> A DICOM Application Entity Title uniquely identifies a service or application on a specific system in the network.
```
[^3]: [C.1: DICOM Application Entity Titles | DICOM PS3.8 2024c - Network Communication Support for Message Exchange](https://dicom.nema.org/medical/dicom/current/output/chtml/part08/chapter_C.html):

> A DICOM Application Entity Title uniquely identifies a service or application on a specific system in the network.
3 changes: 3 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Load fixtures for the test suite."""

pytest_plugins = 'tests.fixtures'
146 changes: 145 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ pyproject-fmt = "^2.2.1"
ruff = "^0.6.3"
safety = ">=2.3,<4.0"
shellcheck-py = { version = ">=0.8,<0.11", markers = "platform_machine != 'arm64'" }
mdformat-gfm = "^0.3.6"
mdformat-frontmatter = "^2.0.8"
mdformat-footnote = "^0.1.1"

[tool.poetry.group.test.dependencies]
coverage = { extras = [
Expand Down Expand Up @@ -690,6 +693,7 @@ help = "Launch a local server to view this package's documentation"
uses = { PACKAGE_VERSION = "_version" }
cmd = """
pdoc
doctest
dicom_echo
dicom_echo.backend
dicom_echo.cli
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""The test suite for the `dicom_echo` package."""
41 changes: 1 addition & 40 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

from __future__ import annotations

import socket
import subprocess
from typing import TYPE_CHECKING, Iterator
from typing import TYPE_CHECKING

import pytest

Expand All @@ -14,43 +12,6 @@
from pytest_mock import MockerFixture


@pytest.fixture(scope='session')
def dicom_storescp() -> str:
"""Return the path to the `dicom-storescp` executable."""
import shutil

if (dicom_storescp := shutil.which('dicom-storescp')) is None:
pytest.skip('`dicom-storescp` not found. To install it, run `cargo install dicom-storescp`')

return dicom_storescp


@pytest.fixture(scope='session')
def localhost() -> Iterator[tuple[str, int]]:
"""Query the OS for the first available port; return the hostname of the socket as well."""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(('', 0))
host, port = sock.getsockname()
sock.close()
yield (host, port)


@pytest.fixture(scope='session')
def scpserver(dicom_storescp: str, localhost: tuple[str, int]) -> Iterator[str]:
"""Start a DICOM SCP server for use with tests."""
host, port = localhost
with subprocess.Popen([dicom_storescp, '-p', str(port)], text=True) as sub_proc:
with pytest.raises(subprocess.TimeoutExpired):
sub_proc.wait(timeout=0.01)

assert None is sub_proc.returncode

try:
yield f'{host}:{port}'
finally:
sub_proc.terminate()


@pytest.fixture
def mock_send_rc0(mocker: MockerFixture) -> MagicMock:
"""Mock the `dicom_echo.__send` function with a return code of `0`."""
Expand Down
46 changes: 46 additions & 0 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Define `pytest` fixtures for use throughout the `tests` package and `doctest` tests."""

from __future__ import annotations

import socket
import subprocess
from typing import Iterator

import pytest


@pytest.fixture(scope='session')
def dicom_storescp() -> str:
"""Return the path to the `dicom-storescp` executable."""
import shutil

if (dicom_storescp := shutil.which('dicom-storescp')) is None:
pytest.skip('`dicom-storescp` not found. To install it, run `cargo install dicom-storescp`')

return dicom_storescp


@pytest.fixture(scope='session')
def localhost() -> Iterator[tuple[str, int]]:
"""Query the OS for the first available port; return the hostname of the socket as well."""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(('', 0))
host, port = sock.getsockname()
sock.close()
yield (host, port)


@pytest.fixture(scope='session')
def scpserver(dicom_storescp: str, localhost: tuple[str, int]) -> Iterator[str]:
"""Start a DICOM SCP server for use with tests."""
host, port = localhost
with subprocess.Popen([dicom_storescp, '-p', str(port)], text=True) as sub_proc:
with pytest.raises(subprocess.TimeoutExpired):
sub_proc.wait(timeout=0.01)

assert None is sub_proc.returncode

try:
yield f'{host}:{port}'
finally:
sub_proc.terminate()

0 comments on commit f08b9d8

Please sign in to comment.