divejson 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- divejson-0.2.0/.github/workflows/ci.yml +119 -0
- divejson-0.2.0/.github/workflows/pr-title.yml +65 -0
- divejson-0.2.0/.github/workflows/release.yml +65 -0
- divejson-0.2.0/.gitignore +9 -0
- divejson-0.2.0/CHANGELOG.md +51 -0
- divejson-0.2.0/CONTRIBUTING.md +97 -0
- divejson-0.2.0/LICENSE +21 -0
- divejson-0.2.0/PKG-INFO +119 -0
- divejson-0.2.0/README.md +101 -0
- divejson-0.2.0/SPEC_REF +1 -0
- divejson-0.2.0/divejson/__init__.py +64 -0
- divejson-0.2.0/divejson/cli.py +281 -0
- divejson-0.2.0/divejson/conform.py +450 -0
- divejson-0.2.0/divejson/py.typed +0 -0
- divejson-0.2.0/divejson/uddf.py +1361 -0
- divejson-0.2.0/divejson/validate.py +410 -0
- divejson-0.2.0/docs/uddf-mapping.md +479 -0
- divejson-0.2.0/fixtures/README.md +87 -0
- divejson-0.2.0/fixtures/invalid/agency-other-missing.divejson +459 -0
- divejson-0.2.0/fixtures/invalid/avg-depth-exceeds-max.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/bad-version.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/bbox-missing-corner.divejson +459 -0
- divejson-0.2.0/fixtures/invalid/bbox-south-exceeds-north.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/bbox-without-position.divejson +456 -0
- divejson-0.2.0/fixtures/invalid/channel-length-mismatch.divejson +461 -0
- divejson-0.2.0/fixtures/invalid/course-dates-reversed.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/dangling-reference.divejson +461 -0
- divejson-0.2.0/fixtures/invalid/duplicate-json-member.divejson +6 -0
- divejson-0.2.0/fixtures/invalid/duplicate-uuid.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/event-other-without-label.divejson +459 -0
- divejson-0.2.0/fixtures/invalid/missing-format.divejson +459 -0
- divejson-0.2.0/fixtures/invalid/naive-exported-at.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/non-increasing-samples.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/null-member.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/oxygen-helium-sum.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/position-incomplete.divejson +459 -0
- divejson-0.2.0/fixtures/invalid/pressure-order.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/profile-duration-short.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/species-no-identity.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/trailing-newline-datetime.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/trip-dates-reversed.divejson +460 -0
- divejson-0.2.0/fixtures/invalid/undefined-member.divejson +461 -0
- divejson-0.2.0/fixtures/invalid/version-before-format.divejson +5 -0
- divejson-0.2.0/fixtures/invalid/version-not-second.divejson +5 -0
- divejson-0.2.0/fixtures/uddf/divelogs.divejson +95 -0
- divejson-0.2.0/fixtures/uddf/divelogs.uddf +112 -0
- divejson-0.2.0/fixtures/uddf/legacy-writer.divejson +64 -0
- divejson-0.2.0/fixtures/uddf/legacy-writer.uddf +66 -0
- divejson-0.2.0/fixtures/uddf/mix-only-cylinder.divejson +67 -0
- divejson-0.2.0/fixtures/uddf/mix-only-cylinder.uddf +70 -0
- divejson-0.2.0/fixtures/uddf/opendiving.divejson +197 -0
- divejson-0.2.0/fixtures/uddf/opendiving.uddf +161 -0
- divejson-0.2.0/fixtures/uddf/subsurface.divejson +129 -0
- divejson-0.2.0/fixtures/uddf/subsurface.uddf +163 -0
- divejson-0.2.0/fixtures/valid/demo-logbook.divejson +4164 -0
- divejson-0.2.0/fixtures/valid/minimal.divejson +5 -0
- divejson-0.2.0/fixtures/valid/technical-dive.divejson +465 -0
- divejson-0.2.0/pyproject.toml +61 -0
- divejson-0.2.0/schema/1.0/divejson.schema.json +450 -0
- divejson-0.2.0/tests/helpers.py +57 -0
- divejson-0.2.0/tests/test_cli.py +149 -0
- divejson-0.2.0/tests/test_conform.py +271 -0
- divejson-0.2.0/tests/test_package.py +62 -0
- divejson-0.2.0/tests/test_uddf_fixtures.py +92 -0
- divejson-0.2.0/tests/test_uddf_parsing.py +510 -0
- divejson-0.2.0/tests/test_uddf_units.py +164 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
tests:
|
|
10
|
+
name: Tests and the vendored corpus
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
# The floor, a current version, and the one the applications that install this
|
|
15
|
+
# package run. 3.10's stricter datetime.fromisoformat is exactly the kind of
|
|
16
|
+
# thing only running the floor catches, and until 3.14 was in this matrix the
|
|
17
|
+
# converter had never been run on the interpreter its first consumer uses.
|
|
18
|
+
python-version: ["3.10", "3.12", "3.14"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: ${{ matrix.python-version }}
|
|
24
|
+
|
|
25
|
+
# Editable, which is the install that has no `divejson/_schema/`: a `force-include`
|
|
26
|
+
# reaches a built wheel and not an editable install, so this leg is what exercises
|
|
27
|
+
# the vendored corpus as the schema's source. The `wheel` job below covers the other.
|
|
28
|
+
- name: Install the package
|
|
29
|
+
run: pip install -e ".[dev]"
|
|
30
|
+
|
|
31
|
+
- name: The tests pass
|
|
32
|
+
run: pytest
|
|
33
|
+
|
|
34
|
+
# The conformance runner over this repository's own copy of the corpus, exactly as
|
|
35
|
+
# the specification repository runs it over the original against a pinned release.
|
|
36
|
+
# `--strict`, so that a format this implementation reads and the corpus has no
|
|
37
|
+
# pairs for fails the build rather than warning into a log nobody reads.
|
|
38
|
+
- name: The vendored corpus conforms
|
|
39
|
+
run: divejson conform fixtures --strict
|
|
40
|
+
|
|
41
|
+
specification:
|
|
42
|
+
name: The vendored tree is the specification at SPEC_REF
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
|
|
47
|
+
- name: Check the specification out at SPEC_REF
|
|
48
|
+
run: |
|
|
49
|
+
ref="$(tr -d '[:space:]' < SPEC_REF)"
|
|
50
|
+
echo "SPEC_REF=$ref" >> "$GITHUB_ENV"
|
|
51
|
+
git clone --quiet https://github.com/divejson/divejson.git "$RUNNER_TEMP/spec"
|
|
52
|
+
git -C "$RUNNER_TEMP/spec" checkout --quiet "$ref"
|
|
53
|
+
|
|
54
|
+
# A subset check, not an equality one: this repository may carry pairs and mapping
|
|
55
|
+
# documents the specification has not adopted yet, and that is how an adapter lands
|
|
56
|
+
# self-contained. What it may never do is *change* a file the specification owns —
|
|
57
|
+
# that always goes to the specification first, and comes back on the next pin bump.
|
|
58
|
+
- name: Every file the specification owns is byte-identical here
|
|
59
|
+
run: |
|
|
60
|
+
differing=0
|
|
61
|
+
while IFS= read -r file; do
|
|
62
|
+
if ! cmp -s "$RUNNER_TEMP/spec/$file" "$file"; then
|
|
63
|
+
echo "::error file=$file::differs from the specification at $SPEC_REF"
|
|
64
|
+
differing=1
|
|
65
|
+
fi
|
|
66
|
+
done < <(git -C "$RUNNER_TEMP/spec" ls-files -- schema fixtures docs)
|
|
67
|
+
exit $differing
|
|
68
|
+
|
|
69
|
+
# A pin on a commit that never landed — a branch someone force-pushed away, a PR
|
|
70
|
+
# head that was rewritten — would leave this implementing a specification nobody
|
|
71
|
+
# else has, and the byte check above would pass happily.
|
|
72
|
+
- name: SPEC_REF is an ancestor of the specification's main
|
|
73
|
+
run: git -C "$RUNNER_TEMP/spec" merge-base --is-ancestor "$SPEC_REF" origin/main
|
|
74
|
+
|
|
75
|
+
wheel:
|
|
76
|
+
name: An installed wheel carries its own schema
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@v4
|
|
80
|
+
- uses: actions/setup-python@v5
|
|
81
|
+
with:
|
|
82
|
+
python-version: "3.12"
|
|
83
|
+
- run: pip install build hatchling
|
|
84
|
+
|
|
85
|
+
# `python -m build` builds the sdist and then builds the wheel *from the sdist*,
|
|
86
|
+
# which is the artefact question worth asking: a wheel built from the source tree
|
|
87
|
+
# can carry files the sdist forgot. `--no-isolation`, with the backend installed
|
|
88
|
+
# above, so nothing is fetched while building — an sdist that needed the network
|
|
89
|
+
# would make that fetch part of every `pip install` from source.
|
|
90
|
+
- name: Build the sdist, and the wheel from the sdist
|
|
91
|
+
run: python -m build --no-isolation
|
|
92
|
+
|
|
93
|
+
- name: Install the wheel into a virtualenv of its own
|
|
94
|
+
run: |
|
|
95
|
+
python -m venv "$RUNNER_TEMP/venv"
|
|
96
|
+
"$RUNNER_TEMP/venv/bin/pip" install dist/*.whl
|
|
97
|
+
|
|
98
|
+
# Run from `$RUNNER_TEMP`, where there is no checkout to fall back to: this is the
|
|
99
|
+
# position every application that installs the package is in.
|
|
100
|
+
- name: The installed package resolves the schema it carries, with no checkout near
|
|
101
|
+
working-directory: ${{ runner.temp }}
|
|
102
|
+
run: |
|
|
103
|
+
cp "$GITHUB_WORKSPACE/fixtures/valid/technical-dive.divejson" .
|
|
104
|
+
"$RUNNER_TEMP/venv/bin/divejson" validate technical-dive.divejson
|
|
105
|
+
"$RUNNER_TEMP/venv/bin/divejson" conform "$GITHUB_WORKSPACE/fixtures" --strict
|
|
106
|
+
"$RUNNER_TEMP/venv/bin/python" - <<'EOF'
|
|
107
|
+
import os, pathlib
|
|
108
|
+
import divejson
|
|
109
|
+
|
|
110
|
+
installed = pathlib.Path(divejson.__file__).resolve().parent
|
|
111
|
+
assert "site-packages" in str(installed), installed
|
|
112
|
+
vendored = pathlib.Path(os.environ["GITHUB_WORKSPACE"]) / "schema"
|
|
113
|
+
sources = sorted(vendored.glob("*/divejson.schema.json"))
|
|
114
|
+
assert sources, "the vendored tree carries no schema at all"
|
|
115
|
+
for source in sources:
|
|
116
|
+
packaged = installed / "_schema" / source.parent.name / "divejson.schema.json"
|
|
117
|
+
assert packaged.read_bytes() == source.read_bytes(), packaged
|
|
118
|
+
print(f"schemas checked against the vendored tree: {len(sources)}")
|
|
119
|
+
EOF
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: PR Title
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
# `edited` is the one that matters: it re-runs the check when a title is
|
|
6
|
+
# corrected, so a failing PR goes green without an empty commit to push.
|
|
7
|
+
#
|
|
8
|
+
# `synchronize` is not about the title at all - it is what would let this be
|
|
9
|
+
# a required check. A required check has to have passed on the PR's *latest*
|
|
10
|
+
# head SHA, so without a run per push, a PR that gains a second commit would
|
|
11
|
+
# sit at "Expected - waiting for status" for good. The title is read from the
|
|
12
|
+
# event payload rather than from the tree, so the extra runs reach the same
|
|
13
|
+
# verdict. Nothing is required on `main` today; this keeps the option open at
|
|
14
|
+
# no cost.
|
|
15
|
+
types: [opened, edited, reopened, synchronize]
|
|
16
|
+
|
|
17
|
+
# The one list of types. CONTRIBUTING.md spells the same list out in prose -
|
|
18
|
+
# keep the two in step.
|
|
19
|
+
env:
|
|
20
|
+
TYPES: feat|fix|refactor|docs|test|chore|perf|ci|build|revert
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
semantic-title:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
|
|
26
|
+
# Nothing is checked out and no API call is made - the title arrives in the
|
|
27
|
+
# event payload - so this job needs no token at all.
|
|
28
|
+
permissions: {}
|
|
29
|
+
|
|
30
|
+
steps:
|
|
31
|
+
# `main`'s ruleset allows only squash merges, and the repository's squash
|
|
32
|
+
# subject source is PR_TITLE rather than COMMIT_OR_PR_TITLE, so this title
|
|
33
|
+
# *is* the commit subject that lands. It is the only part of a branch that
|
|
34
|
+
# outlives the branch, which is why it is checked and the individual
|
|
35
|
+
# commits are not. That setting was corrected by hand after a non-semantic
|
|
36
|
+
# subject reached a default branch in this org; this check is the half that
|
|
37
|
+
# stops the bad title from being written in the first place.
|
|
38
|
+
#
|
|
39
|
+
# The title is passed through `env` rather than interpolated into the
|
|
40
|
+
# script. `${{ github.event.pull_request.title }}` written inside `run:`
|
|
41
|
+
# is substituted before bash parses the line, so a title containing
|
|
42
|
+
# backticks or `$(...)` would execute as shell.
|
|
43
|
+
- name: Check title is a conventional commit subject
|
|
44
|
+
env:
|
|
45
|
+
TITLE: ${{ github.event.pull_request.title }}
|
|
46
|
+
run: |
|
|
47
|
+
PATTERN="^($TYPES)(\([a-z0-9._-]+\))?!?: .+"
|
|
48
|
+
|
|
49
|
+
if printf '%s' "$TITLE" | grep -Eq "$PATTERN"; then
|
|
50
|
+
echo "OK: $TITLE"
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
echo "PR title is not a semantic commit subject:"
|
|
55
|
+
echo " $TITLE"
|
|
56
|
+
echo
|
|
57
|
+
echo "Expected: <type>[(scope)][!]: <description>"
|
|
58
|
+
echo "Types: ${TYPES//|/, }"
|
|
59
|
+
echo
|
|
60
|
+
echo "Examples: feat(schema): a cylinder may record a mix without a vessel"
|
|
61
|
+
echo " fix(spec): profile.duration spans the samples, not the events"
|
|
62
|
+
echo " docs(spec)!: the draft becomes 1.0"
|
|
63
|
+
echo
|
|
64
|
+
echo "Edit the title - this check re-runs on edit, no push needed."
|
|
65
|
+
exit 1
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# A release is a `v*` tag on `main`, pushed by a maintainer. Nothing else publishes: the
|
|
4
|
+
# filename of this workflow, the repository and the environment below are three of the
|
|
5
|
+
# four things PyPI's trusted publisher for this project is registered against, so a
|
|
6
|
+
# rename here is a release that stops working rather than one that publishes wrongly.
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: Build and check what is about to be published
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
- run: pip install build hatchling
|
|
21
|
+
|
|
22
|
+
- name: Build the sdist, and the wheel from the sdist
|
|
23
|
+
run: python -m build --no-isolation
|
|
24
|
+
|
|
25
|
+
# The version is declared once, in the package, and the tag is written by hand — so
|
|
26
|
+
# this is the one place they can disagree. A tag that names a version nobody built
|
|
27
|
+
# would otherwise publish the previous one under the new name.
|
|
28
|
+
- name: The tag names the version that was built
|
|
29
|
+
run: |
|
|
30
|
+
version="${GITHUB_REF_NAME#v}"
|
|
31
|
+
if [ ! -f "dist/divejson-$version.tar.gz" ]; then
|
|
32
|
+
echo "::error::$GITHUB_REF_NAME does not name the version that was built"
|
|
33
|
+
ls -1 dist
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Against the built wheel rather than the checkout: what gets published is what has
|
|
38
|
+
# to run the corpus, and an artefact that cannot is one nobody should be able to
|
|
39
|
+
# `pip install` a minute later.
|
|
40
|
+
- name: The wheel that is about to be published runs the corpus
|
|
41
|
+
run: |
|
|
42
|
+
python -m venv "$RUNNER_TEMP/venv"
|
|
43
|
+
"$RUNNER_TEMP/venv/bin/pip" install dist/*.whl
|
|
44
|
+
"$RUNNER_TEMP/venv/bin/divejson" conform fixtures --strict
|
|
45
|
+
|
|
46
|
+
- uses: actions/upload-artifact@v4
|
|
47
|
+
with:
|
|
48
|
+
name: dist
|
|
49
|
+
path: dist/
|
|
50
|
+
|
|
51
|
+
publish:
|
|
52
|
+
name: Publish to PyPI
|
|
53
|
+
needs: build
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
environment: pypi
|
|
56
|
+
permissions:
|
|
57
|
+
# Trusted publishing: PyPI mints a short-lived token from this job's OIDC identity,
|
|
58
|
+
# so there is no API token anywhere in this repository's secrets.
|
|
59
|
+
id-token: write
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/download-artifact@v4
|
|
62
|
+
with:
|
|
63
|
+
name: dist
|
|
64
|
+
path: dist/
|
|
65
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to the DiveJSON tools for Python. The format's own version
|
|
4
|
+
(`major.minor`, declared in every document) is what readers and writers depend on, and it
|
|
5
|
+
is versioned in [the specification repository](https://github.com/divejson/divejson);
|
|
6
|
+
this file is about the package, whose version moves independently.
|
|
7
|
+
|
|
8
|
+
## 0.2.0
|
|
9
|
+
|
|
10
|
+
- **The package moved here**, out of the specification repository. That repository keeps
|
|
11
|
+
what a conformance suite is — the prose, the schema, the fixture pairs and the mapping
|
|
12
|
+
documents — and runs a released implementation against them; every implementation, in
|
|
13
|
+
whatever language, is a repository of its own. One of them living beside the
|
|
14
|
+
specification was an asymmetry every port would have inherited.
|
|
15
|
+
|
|
16
|
+
- **First release to PyPI**: `pip install divejson`, where the only way to install it
|
|
17
|
+
before was from git. It is 0.2.0 rather than 0.1.0 because 0.1.0 already exists, as the
|
|
18
|
+
package in the specification repository's history — two different trees under one
|
|
19
|
+
version number is worth a number rather than an explanation.
|
|
20
|
+
|
|
21
|
+
- **`divejson conform <corpus>`**, the conformance runner an implementation of the format
|
|
22
|
+
provides, and what both this repository's CI and the specification repository's run. It
|
|
23
|
+
walks `valid/`, `invalid/`, a directory of reader pairs per source format and
|
|
24
|
+
`write/<format>/` of writer pairs, and separates a case that **failed** (status 1) from
|
|
25
|
+
a corpus whose **shape** stopped cases from running at all (status 2) — an empty
|
|
26
|
+
directory, a pair missing one of its halves, pairs for a format the implementation does
|
|
27
|
+
not register. A suite that read "nothing to run" as success is what that distinction is
|
|
28
|
+
for, and it replaces the hand-kept fixture count that used to guard the same thing.
|
|
29
|
+
|
|
30
|
+
- **The specification is vendored and pinned.** `SPEC_REF` names the commit that
|
|
31
|
+
`schema/`, `fixtures/` and `docs/` were taken from, and CI checks on every pull request
|
|
32
|
+
that every file the specification owns is byte-identical here and that the pin is an
|
|
33
|
+
ancestor of its `main`. The copy may run ahead — an adapter lands with its own pairs and
|
|
34
|
+
mapping document before the specification adopts them — but it may never contradict.
|
|
35
|
+
|
|
36
|
+
- **`load_schema()` resolves a schema per minor version**, and a built wheel carries every
|
|
37
|
+
minor the vendored tree has rather than only the one this package validates against.
|
|
38
|
+
Spec §7 gives each minor its own schema, and the directories were already named for
|
|
39
|
+
them.
|
|
40
|
+
|
|
41
|
+
- **`fitdecode` is a core dependency**, before anything imports it and deliberately not
|
|
42
|
+
behind an extra: `pip install divejson==<release>` has to be the whole install
|
|
43
|
+
everywhere, and a format behind an extra is one every consumer has to know to ask for
|
|
44
|
+
and every runner can quietly skip.
|
|
45
|
+
|
|
46
|
+
- **`divejson.conform.compared`** is now the package's own rule for what a converted
|
|
47
|
+
document is compared on — everything except `exported_at` and `generator`, which are
|
|
48
|
+
facts about the run rather than about the input. It was a test helper, where a port or
|
|
49
|
+
an application checking its own determinism could not reach it.
|
|
50
|
+
|
|
51
|
+
- `py.typed`, and an `__all__` on the package naming what it exports.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Issues and pull requests on GitHub are the whole process — there is no mailing list, no
|
|
4
|
+
CLA, and no meeting. This repository is an *implementation*; the format itself is decided
|
|
5
|
+
in [divejson/divejson](https://github.com/divejson/divejson), whose `GOVERNANCE.md` says
|
|
6
|
+
who decides and whose `CONTRIBUTING.md` says how a change to the format lands.
|
|
7
|
+
|
|
8
|
+
## What is here, and what is only borrowed
|
|
9
|
+
|
|
10
|
+
| path | what |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `divejson/` | the package: the validator, the UDDF reader, the conformance runner, the CLI |
|
|
13
|
+
| `tests/` | its tests |
|
|
14
|
+
| `schema/`, `fixtures/`, `docs/` | **vendored** from the specification repository at the commit `SPEC_REF` names |
|
|
15
|
+
| `SPEC_REF` | that commit, on one line — a tag once the format has one |
|
|
16
|
+
|
|
17
|
+
The vendored three are not editable here. CI checks out the specification at `SPEC_REF`
|
|
18
|
+
and asserts that every file it owns is byte-identical in this tree, and that `SPEC_REF`
|
|
19
|
+
is an ancestor of its `main`. What the copy *may* do is run ahead: it can carry pairs and
|
|
20
|
+
mapping documents the specification has not adopted yet, which is what lets a new reader
|
|
21
|
+
land here self-contained, with the expected documents it produces and the document that
|
|
22
|
+
explains them. It can never carry a different version of a file the specification already
|
|
23
|
+
has.
|
|
24
|
+
|
|
25
|
+
So a change lands in one of two orders.
|
|
26
|
+
|
|
27
|
+
**A change to what validates** — the schema, or a rule the fixtures encode — starts in
|
|
28
|
+
the specification repository, whose pull request is red against the current release and
|
|
29
|
+
says what is missing. The change here then sets `SPEC_REF` to that pull request's head,
|
|
30
|
+
which passes the byte check and fails only the ancestor one; the specification's pull
|
|
31
|
+
request points its own pin at this one and goes green; it merges; this one re-pins
|
|
32
|
+
`SPEC_REF` to the merged commit, merges, and is released. `main` here never pins a commit
|
|
33
|
+
that is not on the specification's `main`.
|
|
34
|
+
|
|
35
|
+
**A new reader, or a change to a mapping** goes the other way: it lands here first,
|
|
36
|
+
self-contained and green, then a release, and then the specification adopts the pairs and
|
|
37
|
+
the mapping document and moves its pin. The next change here bumps `SPEC_REF` and
|
|
38
|
+
re-vendors whatever moved.
|
|
39
|
+
|
|
40
|
+
## Working on the package
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install -e ".[dev]"
|
|
44
|
+
pytest
|
|
45
|
+
divejson conform fixtures --strict
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The tests cover the converter — the unit conversions above all, where a wrong factor
|
|
49
|
+
produces a document that validates perfectly and describes a dive nobody took. CI runs
|
|
50
|
+
them on the Python floor, on a current version, and on the version the applications that
|
|
51
|
+
install this package run; it also builds the sdist, builds the wheel from *that*, and
|
|
52
|
+
checks that an installed copy resolves the schema it carries with no checkout anywhere
|
|
53
|
+
near it. That last one is the failure this arrangement is most exposed to: the package
|
|
54
|
+
and the corpus sit in one tree here and in different worlds everywhere else.
|
|
55
|
+
|
|
56
|
+
## Regenerating an expected document
|
|
57
|
+
|
|
58
|
+
Each input under a pair directory is paired with the document it must produce, so a
|
|
59
|
+
mapping change shows up as a failing pair. Regenerate the expected side, and read the
|
|
60
|
+
diff before committing it — the point of the pair is that a human agreed with the new
|
|
61
|
+
answer:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
divejson convert fixtures/<format>/<name>.<ext> --force \
|
|
65
|
+
--exported-at "$(grep -m1 exported_at fixtures/<format>/<name>.divejson | cut -d'"' -f4)"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Both flags matter. Without `--force` the command refuses to replace a file that exists,
|
|
69
|
+
which is the right default everywhere except here. And `exported_at` is one of the two
|
|
70
|
+
members a converted document asserts about its own run rather than about the source, so
|
|
71
|
+
left to default it moves every time — reusing the value already in the file you are
|
|
72
|
+
replacing keeps the moving lines to the ones your change actually moved. A test runs this
|
|
73
|
+
recipe over the whole corpus, writing elsewhere and comparing, because a documented
|
|
74
|
+
command nobody runs is a command that stops working.
|
|
75
|
+
|
|
76
|
+
The other such member is `generator`, which carries this package's version. Both are
|
|
77
|
+
excluded from the comparison — `divejson.conform.compared` is the rule, and it is in the
|
|
78
|
+
package because a port and an application checking its own determinism need the same one
|
|
79
|
+
— so a release never regenerates anything, and the committed expectations keep whatever
|
|
80
|
+
version wrote them. Regenerate when the *mapping* changes, and remember that a pair the
|
|
81
|
+
specification already owns cannot be regenerated here: that change starts over there.
|
|
82
|
+
|
|
83
|
+
## Pull request titles
|
|
84
|
+
|
|
85
|
+
Use a semantic **PR title** — `<type>[(scope)][!]: <description>`, where type is one of
|
|
86
|
+
`feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`, `build`, `revert`.
|
|
87
|
+
Pull requests are squash-merged with the title as the commit subject, so it is the only
|
|
88
|
+
part of a branch that outlives the branch.
|
|
89
|
+
|
|
90
|
+
`.github/workflows/pr-title.yml` checks the format and re-runs when a title is edited —
|
|
91
|
+
a failing check is fixed by correcting the title, with nothing to push.
|
|
92
|
+
|
|
93
|
+
## Licensing of contributions
|
|
94
|
+
|
|
95
|
+
Everything in this repository is MIT, including the vendored schema, fixtures and
|
|
96
|
+
documents, which are MIT in the specification repository too. By contributing you license
|
|
97
|
+
your contribution under that license.
|
divejson-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The DiveJSON Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
divejson-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: divejson
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Validator, converters and conformance runner for DiveJSON, an open dive-log interchange format
|
|
5
|
+
Project-URL: Homepage, https://divejson.org
|
|
6
|
+
Project-URL: Repository, https://github.com/divejson/divejson-py
|
|
7
|
+
Project-URL: Specification, https://github.com/divejson/divejson/blob/main/spec/divejson.md
|
|
8
|
+
Project-URL: Changelog, https://github.com/divejson/divejson-py/blob/main/CHANGELOG.md
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: dive-log,diving,interchange,json,scuba
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: fitdecode>=0.11.0
|
|
14
|
+
Requires-Dist: jsonschema>=4.18
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# divejson
|
|
20
|
+
|
|
21
|
+
Python tools for [DiveJSON](https://divejson.org), an open interchange format for scuba
|
|
22
|
+
dive logs: the validator, the converters that read other dive-log formats into DiveJSON,
|
|
23
|
+
and `divejson conform`, the conformance runner an implementation of the format is checked
|
|
24
|
+
with.
|
|
25
|
+
|
|
26
|
+
The format itself — the normative specification, the JSON Schema and the conformance
|
|
27
|
+
corpus — lives in [divejson/divejson](https://github.com/divejson/divejson). This is an
|
|
28
|
+
implementation of it, in a repository of its own, and it **vendors** that repository's
|
|
29
|
+
schema, fixtures and mapping documents from the commit named in
|
|
30
|
+
[`SPEC_REF`](https://github.com/divejson/divejson-py/blob/main/SPEC_REF). CI checks the
|
|
31
|
+
copy against the specification at that commit on every pull request, so which version of
|
|
32
|
+
the format this package implements is a fact in the tree rather than a claim in a
|
|
33
|
+
sentence.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install divejson
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Python 3.10 or newer.
|
|
42
|
+
|
|
43
|
+
## Validate a document
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
divejson validate my-logbook.divejson
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The JSON Schema, and then the requirements the specification states in prose and a schema
|
|
50
|
+
cannot — identifier uniqueness, referential closure, profile-series integrity, the member
|
|
51
|
+
order, the UTC offset on `exported_at`. Exit status is non-zero if any file fails, with
|
|
52
|
+
one line per violation.
|
|
53
|
+
|
|
54
|
+
## Convert a logbook into DiveJSON
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
divejson convert my-logbook.uddf
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
writes `my-logbook.divejson` beside the input and reports, line by line, what the source
|
|
61
|
+
did not carry — no UTC offsets, a cylinder whose size nobody recorded, coordinates that
|
|
62
|
+
were `0.000000`. **Nothing absent is filled in**: that report is the other half of the
|
|
63
|
+
output, not a diagnostic, and it is what tells a diver which parts of their history their
|
|
64
|
+
old application never kept. The mapping rules, the three places UDDF is genuinely
|
|
65
|
+
ambiguous, and what is deliberately left unmapped are in
|
|
66
|
+
[`docs/uddf-mapping.md`](https://github.com/divejson/divejson-py/blob/main/docs/uddf-mapping.md).
|
|
67
|
+
|
|
68
|
+
## Run a conformance corpus
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
divejson conform fixtures --strict
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Walks a corpus — `valid/` documents that must validate, `invalid/` ones that must not,
|
|
75
|
+
and one directory of reader pairs per source format, named for the format — and says what
|
|
76
|
+
this implementation makes of it. The exit status distinguishes two ways of not passing:
|
|
77
|
+
|
|
78
|
+
| status | meaning |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| 0 | every case passed |
|
|
81
|
+
| 1 | a case **failed**: a valid document that does not validate, an invalid one that does, a pair whose produced document differs from the expected one |
|
|
82
|
+
| 2 | the corpus's **shape** is wrong: an empty directory, a pair missing one of its halves, or pairs for a format this implementation does not register — cases that never ran, which is not the same answer as cases that failed |
|
|
83
|
+
|
|
84
|
+
`--only <format>` and `--skip <format>` narrow the run to a format's pairs, and neither
|
|
85
|
+
reaches `valid/` or `invalid/`. `--strict` turns a format this implementation reads and
|
|
86
|
+
the corpus has no pairs for from a warning into an error.
|
|
87
|
+
|
|
88
|
+
## What it reads
|
|
89
|
+
|
|
90
|
+
| format | what this package does | versions |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| DiveJSON | validates | 1.0 |
|
|
93
|
+
| UDDF | reads into DiveJSON | 3.0 – 3.2.3 |
|
|
94
|
+
|
|
95
|
+
The UDDF reader matches element names rather than the declared version, so older
|
|
96
|
+
documents using the same names are read too: the corpus it is checked against carries
|
|
97
|
+
2.2.0, 3.2.0, 3.2.1 and 3.2.2, under three different root shapes.
|
|
98
|
+
|
|
99
|
+
## Releasing
|
|
100
|
+
|
|
101
|
+
A release is a tag. Move `__version__` in `divejson/__init__.py` — the build reads the
|
|
102
|
+
version from there, so it is the only place it lives — head the changelog's new entries
|
|
103
|
+
with it, land that, and push `v<version>`.
|
|
104
|
+
`.github/workflows/release.yml` then builds the sdist and, from it, the wheel; checks
|
|
105
|
+
that the tag names the version it built and that the wheel actually runs the corpus; and
|
|
106
|
+
publishes to PyPI by trusted publishing, with no API token anywhere. Nothing else
|
|
107
|
+
publishes, and a release is the only thing another repository can pin.
|
|
108
|
+
|
|
109
|
+
## Notices
|
|
110
|
+
|
|
111
|
+
`fitdecode` (MIT) is a dependency of this package. The FIT Protocol and FIT file format
|
|
112
|
+
are proprietary to Garmin; this project is not affiliated with or endorsed by Garmin, and
|
|
113
|
+
carries no part of the FIT SDK.
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT — see [`LICENSE`](https://github.com/divejson/divejson-py/blob/main/LICENSE). The
|
|
118
|
+
vendored `schema/`, `fixtures/` and `docs/` are MIT in the specification repository too;
|
|
119
|
+
the specification prose itself, which is CC BY 4.0, is not vendored here.
|
divejson-0.2.0/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# divejson
|
|
2
|
+
|
|
3
|
+
Python tools for [DiveJSON](https://divejson.org), an open interchange format for scuba
|
|
4
|
+
dive logs: the validator, the converters that read other dive-log formats into DiveJSON,
|
|
5
|
+
and `divejson conform`, the conformance runner an implementation of the format is checked
|
|
6
|
+
with.
|
|
7
|
+
|
|
8
|
+
The format itself — the normative specification, the JSON Schema and the conformance
|
|
9
|
+
corpus — lives in [divejson/divejson](https://github.com/divejson/divejson). This is an
|
|
10
|
+
implementation of it, in a repository of its own, and it **vendors** that repository's
|
|
11
|
+
schema, fixtures and mapping documents from the commit named in
|
|
12
|
+
[`SPEC_REF`](https://github.com/divejson/divejson-py/blob/main/SPEC_REF). CI checks the
|
|
13
|
+
copy against the specification at that commit on every pull request, so which version of
|
|
14
|
+
the format this package implements is a fact in the tree rather than a claim in a
|
|
15
|
+
sentence.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install divejson
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Python 3.10 or newer.
|
|
24
|
+
|
|
25
|
+
## Validate a document
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
divejson validate my-logbook.divejson
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The JSON Schema, and then the requirements the specification states in prose and a schema
|
|
32
|
+
cannot — identifier uniqueness, referential closure, profile-series integrity, the member
|
|
33
|
+
order, the UTC offset on `exported_at`. Exit status is non-zero if any file fails, with
|
|
34
|
+
one line per violation.
|
|
35
|
+
|
|
36
|
+
## Convert a logbook into DiveJSON
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
divejson convert my-logbook.uddf
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
writes `my-logbook.divejson` beside the input and reports, line by line, what the source
|
|
43
|
+
did not carry — no UTC offsets, a cylinder whose size nobody recorded, coordinates that
|
|
44
|
+
were `0.000000`. **Nothing absent is filled in**: that report is the other half of the
|
|
45
|
+
output, not a diagnostic, and it is what tells a diver which parts of their history their
|
|
46
|
+
old application never kept. The mapping rules, the three places UDDF is genuinely
|
|
47
|
+
ambiguous, and what is deliberately left unmapped are in
|
|
48
|
+
[`docs/uddf-mapping.md`](https://github.com/divejson/divejson-py/blob/main/docs/uddf-mapping.md).
|
|
49
|
+
|
|
50
|
+
## Run a conformance corpus
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
divejson conform fixtures --strict
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Walks a corpus — `valid/` documents that must validate, `invalid/` ones that must not,
|
|
57
|
+
and one directory of reader pairs per source format, named for the format — and says what
|
|
58
|
+
this implementation makes of it. The exit status distinguishes two ways of not passing:
|
|
59
|
+
|
|
60
|
+
| status | meaning |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| 0 | every case passed |
|
|
63
|
+
| 1 | a case **failed**: a valid document that does not validate, an invalid one that does, a pair whose produced document differs from the expected one |
|
|
64
|
+
| 2 | the corpus's **shape** is wrong: an empty directory, a pair missing one of its halves, or pairs for a format this implementation does not register — cases that never ran, which is not the same answer as cases that failed |
|
|
65
|
+
|
|
66
|
+
`--only <format>` and `--skip <format>` narrow the run to a format's pairs, and neither
|
|
67
|
+
reaches `valid/` or `invalid/`. `--strict` turns a format this implementation reads and
|
|
68
|
+
the corpus has no pairs for from a warning into an error.
|
|
69
|
+
|
|
70
|
+
## What it reads
|
|
71
|
+
|
|
72
|
+
| format | what this package does | versions |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| DiveJSON | validates | 1.0 |
|
|
75
|
+
| UDDF | reads into DiveJSON | 3.0 – 3.2.3 |
|
|
76
|
+
|
|
77
|
+
The UDDF reader matches element names rather than the declared version, so older
|
|
78
|
+
documents using the same names are read too: the corpus it is checked against carries
|
|
79
|
+
2.2.0, 3.2.0, 3.2.1 and 3.2.2, under three different root shapes.
|
|
80
|
+
|
|
81
|
+
## Releasing
|
|
82
|
+
|
|
83
|
+
A release is a tag. Move `__version__` in `divejson/__init__.py` — the build reads the
|
|
84
|
+
version from there, so it is the only place it lives — head the changelog's new entries
|
|
85
|
+
with it, land that, and push `v<version>`.
|
|
86
|
+
`.github/workflows/release.yml` then builds the sdist and, from it, the wheel; checks
|
|
87
|
+
that the tag names the version it built and that the wheel actually runs the corpus; and
|
|
88
|
+
publishes to PyPI by trusted publishing, with no API token anywhere. Nothing else
|
|
89
|
+
publishes, and a release is the only thing another repository can pin.
|
|
90
|
+
|
|
91
|
+
## Notices
|
|
92
|
+
|
|
93
|
+
`fitdecode` (MIT) is a dependency of this package. The FIT Protocol and FIT file format
|
|
94
|
+
are proprietary to Garmin; this project is not affiliated with or endorsed by Garmin, and
|
|
95
|
+
carries no part of the FIT SDK.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT — see [`LICENSE`](https://github.com/divejson/divejson-py/blob/main/LICENSE). The
|
|
100
|
+
vendored `schema/`, `fixtures/` and `docs/` are MIT in the specification repository too;
|
|
101
|
+
the specification prose itself, which is CC BY 4.0, is not vendored here.
|
divejson-0.2.0/SPEC_REF
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
9dfba28c950113b5e3e5ae9b48eddb256bfa93fa
|