Skip to content

Commit 60903e1

Browse files
Release v0.2 (#21)
* Created reading functions that are lax on corrupt records. They return `([recs], int|None)`, where the second element is the byte where the corrupted records start, or None if no records are corrupted. These functions have the same python signature, but are named `read_dmap_lax`, `read_iqdat_lax`, etc. * Used macro_rules to reduce similar code blocks. * Renamed `GenericRecord` to `DmapRecord`, and moved into dmap.rs file. * Moved dmap.rs file containing `Record` trait to src/record.rs * Added a few unit tests to types.rs * Added new functions to read from / write to bytes via Python API. * Fleshed out README.md with a developer guide to explain the crate structure and what the files roughly contain. * Version number bump * Run all OS builds in CI for PR * Updated CI.yml with new `maturin-ci generate` command.
1 parent 50e1041 commit 60903e1

File tree

18 files changed

+1370
-2197
lines changed

18 files changed

+1370
-2197
lines changed

.github/workflows/CI.yml

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# This file is autogenerated by maturin v1.7.0
1+
# This file is autogenerated by maturin v1.9.0
22
# To update, run
33
#
44
# maturin generate-ci github --zig
@@ -25,18 +25,18 @@ jobs:
2525
strategy:
2626
matrix:
2727
platform:
28-
- runner: ubuntu-latest
28+
- runner: ubuntu-22.04
2929
target: x86_64
30-
- runner: ubuntu-latest
30+
- runner: ubuntu-22.04
3131
target: x86
32-
- runner: ubuntu-latest
32+
- runner: ubuntu-22.04
3333
target: aarch64
34-
- runner: ubuntu-latest
34+
- runner: ubuntu-22.04
3535
target: armv7
36-
- runner: ubuntu-latest
36+
- runner: ubuntu-22.04
3737
target: s390x
38-
# - runner: ubuntu-latest
39-
# target: ppc64le
38+
# - runner: ubuntu-22.04
39+
# target: ppc64le
4040
steps:
4141
- uses: actions/checkout@v4
4242
- uses: actions/setup-python@v5
@@ -51,6 +51,15 @@ jobs:
5151
manylinux: auto
5252
before-script-linux: |
5353
sudo apt update -y && sudo apt-get install -y libssl-dev openssl pkg-config
54+
# - name: Build free-threaded wheels
55+
# uses: PyO3/maturin-action@v1
56+
# with:
57+
# target: ${{ matrix.platform.target }}
58+
# args: --release --out dist --zig -i python3.13t
59+
# sccache: 'false'
60+
# manylinux: auto
61+
# before-script-linux: |
62+
# sudo apt update -y && sudo apt-get install -y libssl-dev openssl pkg-config
5463
- name: Upload wheels
5564
uses: actions/upload-artifact@v4
5665
with:
@@ -59,17 +68,16 @@ jobs:
5968

6069
musllinux:
6170
runs-on: ${{ matrix.platform.runner }}
62-
if: "startsWith(github.ref, 'refs/tags/')"
6371
strategy:
6472
matrix:
6573
platform:
66-
- runner: ubuntu-latest
74+
- runner: ubuntu-22.04
6775
target: x86_64
68-
- runner: ubuntu-latest
76+
- runner: ubuntu-22.04
6977
target: x86
70-
- runner: ubuntu-latest
78+
- runner: ubuntu-22.04
7179
target: aarch64
72-
- runner: ubuntu-latest
80+
- runner: ubuntu-22.04
7381
target: armv7
7482
steps:
7583
- uses: actions/checkout@v4
@@ -83,6 +91,15 @@ jobs:
8391
args: --release --out dist
8492
sccache: 'false'
8593
manylinux: musllinux_1_2
94+
before-script-linux: |
95+
sudo apt update -y && sudo apt-get install -y libssl-dev openssl pkg-config
96+
# - name: Build free-threaded wheels
97+
# uses: PyO3/maturin-action@v1
98+
# with:
99+
# target: ${{ matrix.platform.target }}
100+
# args: --release --out dist -i python3.13t
101+
# sccache: 'false'
102+
# manylinux: musllinux_1_2
86103
- name: Upload wheels
87104
uses: actions/upload-artifact@v4
88105
with:
@@ -91,12 +108,13 @@ jobs:
91108

92109
windows:
93110
runs-on: ${{ matrix.platform.runner }}
94-
if: "startsWith(github.ref, 'refs/tags/')"
95111
strategy:
96112
matrix:
97113
platform:
98114
- runner: windows-latest
99115
target: x64
116+
- runner: windows-latest
117+
target: x86
100118
steps:
101119
- uses: actions/checkout@v4
102120
- uses: actions/setup-python@v5
@@ -109,6 +127,16 @@ jobs:
109127
target: ${{ matrix.platform.target }}
110128
args: --release --out dist
111129
sccache: 'false'
130+
- uses: actions/setup-python@v5
131+
with:
132+
python-version: 3.13t
133+
architecture: ${{ matrix.platform.target }}
134+
# - name: Build free-threaded wheels
135+
# uses: PyO3/maturin-action@v1
136+
# with:
137+
# target: ${{ matrix.platform.target }}
138+
# args: --release --out dist -i python3.13t
139+
# sccache: 'false'
112140
- name: Upload wheels
113141
uses: actions/upload-artifact@v4
114142
with:
@@ -117,11 +145,10 @@ jobs:
117145

118146
macos:
119147
runs-on: ${{ matrix.platform.runner }}
120-
if: "startsWith(github.ref, 'refs/tags/')"
121148
strategy:
122149
matrix:
123150
platform:
124-
- runner: macos-12
151+
- runner: macos-13
125152
target: x86_64
126153
- runner: macos-14
127154
target: aarch64
@@ -136,6 +163,12 @@ jobs:
136163
target: ${{ matrix.platform.target }}
137164
args: --release --out dist
138165
sccache: 'false'
166+
# - name: Build free-threaded wheels
167+
# uses: PyO3/maturin-action@v1
168+
# with:
169+
# target: ${{ matrix.platform.target }}
170+
# args: --release --out dist -i python3.13t
171+
# sccache: 'false'
139172
- name: Upload wheels
140173
uses: actions/upload-artifact@v4
141174
with:
@@ -146,9 +179,6 @@ jobs:
146179
runs-on: ubuntu-latest
147180
steps:
148181
- uses: actions/checkout@v4
149-
- uses: actions/setup-python@v5
150-
with:
151-
python-version: 3.x
152182
- name: Build sdist
153183
uses: PyO3/maturin-action@v1
154184
with:
@@ -163,18 +193,26 @@ jobs:
163193
release:
164194
name: Release
165195
runs-on: ubuntu-latest
166-
if: "startsWith(github.ref, 'refs/tags/')"
196+
if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
167197
needs: [linux, musllinux, windows, macos, sdist]
168198
permissions:
199+
# Use to sign the release artifacts
169200
id-token: write
170-
environment: release
201+
# Used to upload release artifacts
202+
contents: write
203+
# Used to generate artifact attestation
204+
attestations: write
171205
steps:
172206
- uses: actions/download-artifact@v4
173-
- uses: actions/setup-python@v5
207+
- name: Generate artifact attestation
208+
uses: actions/attest-build-provenance@v2
174209
with:
175-
python-version: 3.x
210+
subject-path: 'wheels-*/*'
176211
- name: Publish to PyPI
212+
if: ${{ startsWith(github.ref, 'refs/tags/') }}
177213
uses: PyO3/maturin-action@v1
214+
# env:
215+
# MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
178216
with:
179217
command: upload
180218
args: --non-interactive --skip-existing wheels-*/*

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dmap"
3-
version = "0.1.6"
3+
version = "0.2.0"
44
edition = "2021"
55
rust-version = "1.63.0"
66

@@ -22,6 +22,7 @@ thiserror = "1.0.63"
2222
zerocopy = { version = "0.7.35", features = ["byteorder"] }
2323
lazy_static = "1.5.0"
2424
bzip2 = "0.4.4"
25+
paste = "1.0.15"
2526

2627
[dev-dependencies]
2728
criterion = { version = "0.4", features = ["html_reports"] }

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,49 @@
11
# Dmap
22

33
Rust tools for SuperDARN DMAP file format operations.
4+
5+
This project exposes both Rust and Python APIs for handling DMAP I/O.
6+
I/O can be conducted either on byte buffers, or directly to/from files.
7+
8+
The SuperDARN DMAP file formats are all supported (IQDAT, RAWACF, FITACF, GRID, MAP, and SND)
9+
as well as a generic DMAP format that is unaware of any required fields or types
10+
(e.g. char, int32) for any fields.
11+
12+
## Developer Guidelines
13+
14+
### `src/record.rs`
15+
This file contains the `Record` trait, which defines a set of functions that specific DMAP formats must implement.
16+
For example, `read_file(infile: &PathBuf) -> Result<Vec<Self>, DmapError>` is defined in the `Record` trait, and handles
17+
reading in records from a file at the specified path. This function is generic, in that it doesn't know what type of records
18+
(RAWACF, FITACF, etc.) are expected. Also, since it is a trait function, you can only use it through a struct which implements
19+
the trait. For example, the `FitacfRecord` struct defined in `src/formats/fitacf.rs` implements the `Record` trait, and so
20+
you can call `FitacfRecord::read_file(...)` to read a FITACF file, but you couldn't invoke `Record::read_file(...)`.
21+
22+
### `src/types.rs`
23+
This file defines necessary structs and enums for encapsulating basic types (`i8`, `u32`, `String`, etc.) into
24+
objects like `DmapField`, `DmapScalar`, `DmapVec`, etc. that abstract over the supported underlying types.
25+
For instance, when reading scalar from a DMAP file, the underlying data type is inferred from the `type` field in the
26+
scalar's metadata, so it can't be known beforehand. This requires some encapsulating type, `DmapScalar` in this case,
27+
which contains the metadata of the field and has a known size for the stack memory.
28+
29+
This file defines the `Fields` struct, which is used to hold the names and types of the required and optional
30+
scalar and vector fields for a type of DMAP record (RAWACF, FITACF, etc.).
31+
32+
This file defines the `DmapType` trait and implements it for supported data types that can be in DMAP records, namely
33+
`u8`, `u16`, `u32`, `u64`, `i8`, `i16`, `i32`, `i64`, `f32`, `f64`, and `String`. The implementation of the trait for
34+
these types only means that other types, e.g. `i128`, cannot be stored in DMAP records.
35+
36+
Lastly, functions for parsing scalars and vectors from a byte buffer are defined in this file.
37+
38+
### `src/formats`
39+
This directory holds the files that define the DMAP record formats: IQDAT, RAWACF, FITACF, GRID, MAP, SND, and the generic DMAP.
40+
If you are defining a new DMAP format, you will need to make a new file in this directory following the structure of the
41+
existing files. Essentially, you define the scalar and vector fields, both required and optional, and the groups of vector
42+
fields which must have identical dimensions, then call a macro to autogenerate the struct code for you.
43+
44+
### `tests`
45+
In `tests.rs`, integration tests for reading and writing all file types are present. Small example files
46+
are contained in `tests/test_files`.
47+
48+
### `benches/io_benchmarking.rs`
49+
This file contains benchmarking functions for checking the performance of the basic read functions.

benches/io_benchmarking.rs

Lines changed: 22 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use criterion::{criterion_group, criterion_main, Criterion};
2-
use dmap::formats::dmap::Record;
32
use dmap::formats::fitacf::FitacfRecord;
43
use dmap::formats::grid::GridRecord;
54
use dmap::formats::iqdat::IqdatRecord;
65
use dmap::formats::map::MapRecord;
76
use dmap::formats::rawacf::RawacfRecord;
87
use dmap::formats::snd::SndRecord;
8+
use dmap::record::Record;
9+
use paste::paste;
910
use std::fs::File;
1011

1112
fn criterion_benchmark(c: &mut Criterion) {
@@ -15,12 +16,6 @@ fn criterion_benchmark(c: &mut Criterion) {
1516
c.bench_function("Read GRID", |b| b.iter(|| read_grid()));
1617
c.bench_function("Read SND", |b| b.iter(|| read_snd()));
1718
c.bench_function("Read MAP", |b| b.iter(|| read_map()));
18-
// c.bench_function("Read Full-size RAWACF", |b| {
19-
// b.iter(|| read_fullsize_rawacf())
20-
// });
21-
// c.bench_function("Read Full-size FITACF", |b| {
22-
// b.iter(|| read_fullsize_fitacf())
23-
// });
2419

2520
// let records = read_iqdat();
2621
// c.bench_with_input(
@@ -30,52 +25,34 @@ fn criterion_benchmark(c: &mut Criterion) {
3025
// );
3126
}
3227

33-
fn read_fitacf() -> Vec<FitacfRecord> {
34-
let file = File::open("tests/test_files/test.fitacf").expect("Test file not found");
35-
FitacfRecord::read_records(file).unwrap()
28+
/// Generates benchmark functions for a given DMAP record type.
29+
macro_rules! read_type {
30+
($type:ident) => {
31+
paste! {
32+
fn [< read_ $type >]() -> Vec<[< $type:camel Record >]> {
33+
let file = File::open(format!("tests/test_files/test.{}", stringify!($type))).expect("Test file not found");
34+
[< $type:camel Record >]::read_records(file).unwrap()
35+
}
36+
}
37+
}
3638
}
3739

38-
fn read_rawacf() -> Vec<RawacfRecord> {
39-
let file = File::open("tests/test_files/test.rawacf").expect("Test file not found");
40-
RawacfRecord::read_records(file).unwrap()
41-
}
42-
43-
fn read_fullsize_rawacf() -> Vec<RawacfRecord> {
44-
let file = File::open("tests/test_files/20210607.1801.00.cly.a.rawacf.mean")
45-
.expect("Test file not found");
46-
RawacfRecord::read_records(file).unwrap()
47-
}
48-
49-
fn read_fullsize_fitacf() -> Vec<FitacfRecord> {
50-
let file =
51-
File::open("tests/test_files/20210607.1801.00.cly.a.fitacf").expect("Test file not found");
52-
FitacfRecord::read_records(file).unwrap()
53-
}
54-
55-
fn read_iqdat() -> Vec<IqdatRecord> {
56-
let file = File::open("tests/test_files/test.iqdat").expect("Test file not found");
57-
IqdatRecord::read_records(file).unwrap()
58-
}
40+
read_type!(iqdat);
41+
read_type!(rawacf);
42+
read_type!(fitacf);
43+
read_type!(grid);
44+
read_type!(map);
45+
read_type!(snd);
5946

6047
// fn write_iqdat(records: &Vec<RawDmapRecord>) {
6148
// let file = File::open("tests/test_files/test.iqdat").expect("Test file not found");
6249
// dmap::read_records(file).unwrap();
6350
// dmap::to_file("tests/test_files/temp.iqdat", records).unwrap();
6451
// }
6552

66-
fn read_grid() -> Vec<GridRecord> {
67-
let file = File::open("tests/test_files/test.grid").expect("Test file not found");
68-
GridRecord::read_records(file).unwrap()
69-
}
70-
71-
fn read_map() -> Vec<MapRecord> {
72-
let file = File::open("tests/test_files/test.map").expect("Test file not found");
73-
MapRecord::read_records(file).unwrap()
74-
}
75-
76-
fn read_snd() -> Vec<SndRecord> {
77-
let file = File::open("tests/test_files/test.snd").expect("Test file not found");
78-
SndRecord::read_records(file).unwrap()
53+
criterion_group! {
54+
name = benches;
55+
config = Criterion::default();
56+
targets = criterion_benchmark
7957
}
80-
criterion_group!(benches, criterion_benchmark);
8158
criterion_main!(benches);

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "darn-dmap"
7-
version = "0.1.6"
7+
version = "0.2.0"
88
requires-python = ">=3.8"
99
authors = [
1010
{ name = "Remington Rohel" }

src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub enum DmapError {
3333

3434
/// Errors when reading in multiple records
3535
#[error("First error: {1}\nRecords with errors: {0:?}")]
36-
BadRecords(Vec<usize>, String)
36+
BadRecords(Vec<usize>, String),
3737
}
3838

3939
impl From<DmapError> for PyErr {

0 commit comments

Comments
 (0)