Skip to content

Commit 9c5a3e8

Browse files
Merge pull request #3 from DeflateAwning/test-sample-db
* test: add sample file to read, create basic tests * several CI fixes * fix: ensure `\r` not in table_names on Windows
2 parents 717b72a + 97e8d3a commit 9c5a3e8

9 files changed

Lines changed: 276 additions & 21 deletions

File tree

.github/workflows/ci.yml

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ permissions:
1212

1313
jobs:
1414
build:
15-
timeout-minutes: 10
15+
timeout-minutes: 5
1616
strategy:
1717
matrix:
1818
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
1919
python-version: ["3.11", "3.12", "3.13"]
20+
polars-version: ["1.20", "1.30", "1.31"]
2021
include:
2122
# Methodology: Use Ubuntu for basically everything, then target
2223
# specific cases for the OSes and Python versions.
@@ -26,8 +27,6 @@ jobs:
2627
python-version: "3.12"
2728

2829
# Polars version tests (Python 3.12 only).
29-
- python-version: "3.12"
30-
polars-version: "0.20"
3130
- python-version: "3.12"
3231
polars-version: "1.20"
3332
- python-version: "3.12"
@@ -50,7 +49,7 @@ jobs:
5049
- name: Set up Python
5150
run: uv python install
5251

53-
- name: Install uv dependencies
52+
- name: Install uv dependencies (Linux/macOS)
5453
if: runner.os == 'Linux' || runner.os == 'macOS'
5554
run: |
5655
if [ -n "${{ matrix.polars-version || '' }}" ]; then
@@ -62,6 +61,7 @@ jobs:
6261
fi
6362
- name: Install uv dependencies (Windows)
6463
if: runner.os == 'Windows'
64+
shell: pwsh
6565
run: |
6666
if ($env:MATRIX_POLARS_VERSION) {
6767
Write-Host "Overriding Polars version to $env:MATRIX_POLARS_VERSION"
@@ -70,14 +70,16 @@ jobs:
7070
} else {
7171
uv sync --all-extras
7272
}
73-
shell: pwsh
7473
7574
- name: Run linting
7675
run: |
76+
uv run python -c "import polars ; polars.show_versions()"
7777
uv run ruff format --check .
7878
uv run ruff check .
7979
8080
- name: Run type checking
81+
# Skip type checking on many old Polars versions.
82+
if: matrix.polars-version != '0.20'
8183
run: uv run pyright .
8284

8385
# Linux (Ubuntu)
@@ -102,20 +104,34 @@ jobs:
102104
if: runner.os == 'Windows'
103105
run: |
104106
$url = "https://github.com/lsgunth/mdbtools-win/archive/refs/tags/v1.0.0.zip"
105-
$zipPath = "$env:RUNNER_TEMP\mdbtools-win.zip"
106-
$extractPath = "$env:GITHUB_WORKSPACE\path"
107+
$zipPath = "$env:RUNNER_TEMP\mdbtools-win-1.0.0.zip"
108+
$extractRoot = "$env:RUNNER_TEMP\mdbtools"
109+
$installPath = "$env:GITHUB_WORKSPACE\mdbtools"
107110
108111
# Download the zip file
109112
Invoke-WebRequest -Uri $url -OutFile $zipPath
110113
111-
# Create the target directory if it doesn't exist
112-
New-Item -ItemType Directory -Force -Path $extractPath | Out-Null
114+
# Create the extraction root and install directories
115+
New-Item -ItemType Directory -Force -Path $extractRoot | Out-Null
116+
New-Item -ItemType Directory -Force -Path $installPath | Out-Null
113117
114118
# Extract the zip file
115-
Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force
119+
Expand-Archive -Path $zipPath -DestinationPath $extractRoot -Force
120+
121+
# Move contents from the subfolder (mdbtools-win-1.0.0) to install path
122+
$innerDir = Join-Path $extractRoot "mdbtools-win-1.0.0"
123+
Copy-Item -Path (Join-Path $innerDir "*") -Destination $installPath -Recurse -Force
116124
117-
Write-Host "Extracted to $extractPath"
125+
# Add install path to PATH
126+
Write-Host "Adding $installPath to PATH"
127+
echo "$installPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
128+
129+
Write-Host "MDBTools installed at $installPath"
118130
shell: pwsh
119131

132+
- name: Verify MDBTools installation
133+
run: |
134+
mdb-ver --version
135+
120136
- name: Run tests
121137
run: uv run pytest tests/

pyproject.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ classifiers = [
1010
"License :: OSI Approved :: MIT License",
1111
"Operating System :: OS Independent",
1212
]
13-
dependencies = ["polars>=0.20"]
13+
dependencies = ["polars>=1.20"]
1414
keywords = ["polars", "mdbtools", "access", "database", "dataframe"]
1515

1616
[project.urls]
@@ -23,7 +23,12 @@ requires = ["hatchling"]
2323
build-backend = "hatchling.build"
2424

2525
[dependency-groups]
26-
dev = ["pyright>=1.1.406", "pytest>=8.4.2", "ruff>=0.14.1"]
26+
dev = [
27+
"pyright>=1.1.406",
28+
"pytest>=8.4.2",
29+
"requests>=2.32.5",
30+
"ruff>=0.14.1",
31+
]
2732

2833
[tool.pyright]
2934
typeCheckingMode = "strict"

src/polars_access_mdbtools/__init__.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,15 @@ def list_table_names(db_path: str | Path) -> list[str]:
3636
:param db_path: The MS Access database file.
3737
:return: A list of the tables in a given database.
3838
"""
39-
tables = subprocess.check_output( # noqa: S603
40-
["mdb-tables", "--single-column", _path_to_cmd_str(db_path)], # noqa: S607
41-
).decode()
42-
return tables.strip().split("\n")
39+
tables = (
40+
subprocess.check_output( # noqa: S603
41+
["mdb-tables", "--single-column", _path_to_cmd_str(db_path)], # noqa: S607
42+
)
43+
.decode()
44+
.replace("\r\n", "\n")
45+
.strip()
46+
)
47+
return tables.split("\n")
4348

4449

4550
def _convert_data_type_from_access_to_polars( # noqa: C901, PLR0911, PLR0912

src/polars_access_mdbtools/py.typed

Whitespace-only changes.

tests/conftest.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Fixtures (sample files) for tests."""
2+
3+
import hashlib
4+
import tempfile
5+
from collections.abc import Iterator
6+
from pathlib import Path
7+
8+
import pytest
9+
import requests
10+
11+
12+
def _sha256_checksum(file_path: Path) -> str:
13+
"""Compute the SHA-256 checksum of a file.
14+
15+
:param file_path: The path to the file.
16+
:return: The SHA-256 checksum as a hexadecimal string.
17+
"""
18+
sha256 = hashlib.sha256()
19+
with file_path.open("rb") as f:
20+
for chunk in iter(lambda: f.read(8192), b""):
21+
sha256.update(chunk)
22+
return sha256.hexdigest()
23+
24+
25+
@pytest.fixture
26+
def sample_db_1() -> Iterator[Path]:
27+
"""Give a sample access database file for tests.
28+
29+
Download the Access Example .accdb file to a temporary directory,
30+
yield its path for tests, and delete it afterward.
31+
"""
32+
url = "https://github.com/Access-projects/Access-examples/raw/refs/heads/master/Access_Example_VBA.accdb"
33+
with tempfile.TemporaryDirectory() as temp_dir_str:
34+
db_path = Path(temp_dir_str) / "Access_Example_VBA.accdb"
35+
response = requests.get(url, stream=True, timeout=10)
36+
response.raise_for_status()
37+
38+
db_path.write_bytes(response.content)
39+
assert (
40+
_sha256_checksum(db_path)
41+
== "3cd1fc88d70bc93909ae6d190fa9692e709d5641b205fd2f66185c905e42cfbf"
42+
), "Downloaded file checksum does not match expected value."
43+
44+
yield db_path # Provide file path to test.
45+
# Tempdir automatically cleaned up on exit.

tests/test_list_table_names.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""Test the `list_table_names()` function."""
2+
3+
from pathlib import Path
4+
5+
from polars_access_mdbtools import list_table_names
6+
7+
8+
def test_list_table_names_db_1(sample_db_1: Path) -> None:
9+
table_names = list_table_names(sample_db_1)
10+
assert isinstance(table_names, list)
11+
assert table_names == [
12+
"CMD_LINE_TB",
13+
"CUM_VAL_TB",
14+
"DATABASE_STRUCTURE_TB",
15+
"DECUM_VAL_TB",
16+
"ROULETTE_TB",
17+
"TAG_GRP_TB",
18+
"TAG_NME_TB",
19+
"tblContacts",
20+
"tblDefaults",
21+
"tblFileList",
22+
"USysRibbons",
23+
]

tests/test_mdbtools_installed.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Test to ensure mdbtools is installed and accessible."""
2+
3+
import subprocess
4+
5+
import pytest
6+
7+
8+
def test_mdbtools_installed() -> None:
9+
"""Test that mdbtools commands are accessible."""
10+
try:
11+
result = subprocess.run(
12+
["mdb-ver", "--version"], # noqa: S607
13+
capture_output=True,
14+
text=True,
15+
check=True,
16+
)
17+
output = result.stdout.strip()
18+
assert "mdbtools" in output, (
19+
"mdb-ver output does not indicate MDB Tools is installed."
20+
)
21+
except FileNotFoundError:
22+
pytest.fail("mdb-ver command not found. MDB Tools may not be installed.")
23+
except subprocess.CalledProcessError as e:
24+
pytest.fail(f"mdb-ver command failed with error: {e.stderr.strip()}")

tests/test_read_table.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
"""Tests for reading tables from Access databases."""
22

3+
from pathlib import Path
34

4-
def test_placeholder() -> None:
5-
"""A placeholder test."""
6-
assert True
5+
from polars_access_mdbtools import read_table
6+
7+
8+
def test_can_read_all_tables_in_sample_db_1(sample_db_1: Path) -> None:
9+
"""Test reading all tables in sample_db_1. Check row counts."""
10+
for table_name, expected_rows in [
11+
("CMD_LINE_TB", 14),
12+
("CUM_VAL_TB", 59),
13+
("DATABASE_STRUCTURE_TB", 46),
14+
("DECUM_VAL_TB", 59),
15+
("ROULETTE_TB", 38),
16+
("TAG_GRP_TB", 19),
17+
("TAG_NME_TB", 933),
18+
("tblContacts", 0),
19+
("tblDefaults", 1),
20+
("tblFileList", 0),
21+
("USysRibbons", 2),
22+
]:
23+
df = read_table(sample_db_1, table_name)
24+
assert df.height == expected_rows, (
25+
f"Table {table_name} should have {expected_rows} rows, but had {df.height}."
26+
)

0 commit comments

Comments
 (0)