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.
Files changed (66) hide show
  1. divejson-0.2.0/.github/workflows/ci.yml +119 -0
  2. divejson-0.2.0/.github/workflows/pr-title.yml +65 -0
  3. divejson-0.2.0/.github/workflows/release.yml +65 -0
  4. divejson-0.2.0/.gitignore +9 -0
  5. divejson-0.2.0/CHANGELOG.md +51 -0
  6. divejson-0.2.0/CONTRIBUTING.md +97 -0
  7. divejson-0.2.0/LICENSE +21 -0
  8. divejson-0.2.0/PKG-INFO +119 -0
  9. divejson-0.2.0/README.md +101 -0
  10. divejson-0.2.0/SPEC_REF +1 -0
  11. divejson-0.2.0/divejson/__init__.py +64 -0
  12. divejson-0.2.0/divejson/cli.py +281 -0
  13. divejson-0.2.0/divejson/conform.py +450 -0
  14. divejson-0.2.0/divejson/py.typed +0 -0
  15. divejson-0.2.0/divejson/uddf.py +1361 -0
  16. divejson-0.2.0/divejson/validate.py +410 -0
  17. divejson-0.2.0/docs/uddf-mapping.md +479 -0
  18. divejson-0.2.0/fixtures/README.md +87 -0
  19. divejson-0.2.0/fixtures/invalid/agency-other-missing.divejson +459 -0
  20. divejson-0.2.0/fixtures/invalid/avg-depth-exceeds-max.divejson +460 -0
  21. divejson-0.2.0/fixtures/invalid/bad-version.divejson +460 -0
  22. divejson-0.2.0/fixtures/invalid/bbox-missing-corner.divejson +459 -0
  23. divejson-0.2.0/fixtures/invalid/bbox-south-exceeds-north.divejson +460 -0
  24. divejson-0.2.0/fixtures/invalid/bbox-without-position.divejson +456 -0
  25. divejson-0.2.0/fixtures/invalid/channel-length-mismatch.divejson +461 -0
  26. divejson-0.2.0/fixtures/invalid/course-dates-reversed.divejson +460 -0
  27. divejson-0.2.0/fixtures/invalid/dangling-reference.divejson +461 -0
  28. divejson-0.2.0/fixtures/invalid/duplicate-json-member.divejson +6 -0
  29. divejson-0.2.0/fixtures/invalid/duplicate-uuid.divejson +460 -0
  30. divejson-0.2.0/fixtures/invalid/event-other-without-label.divejson +459 -0
  31. divejson-0.2.0/fixtures/invalid/missing-format.divejson +459 -0
  32. divejson-0.2.0/fixtures/invalid/naive-exported-at.divejson +460 -0
  33. divejson-0.2.0/fixtures/invalid/non-increasing-samples.divejson +460 -0
  34. divejson-0.2.0/fixtures/invalid/null-member.divejson +460 -0
  35. divejson-0.2.0/fixtures/invalid/oxygen-helium-sum.divejson +460 -0
  36. divejson-0.2.0/fixtures/invalid/position-incomplete.divejson +459 -0
  37. divejson-0.2.0/fixtures/invalid/pressure-order.divejson +460 -0
  38. divejson-0.2.0/fixtures/invalid/profile-duration-short.divejson +460 -0
  39. divejson-0.2.0/fixtures/invalid/species-no-identity.divejson +460 -0
  40. divejson-0.2.0/fixtures/invalid/trailing-newline-datetime.divejson +460 -0
  41. divejson-0.2.0/fixtures/invalid/trip-dates-reversed.divejson +460 -0
  42. divejson-0.2.0/fixtures/invalid/undefined-member.divejson +461 -0
  43. divejson-0.2.0/fixtures/invalid/version-before-format.divejson +5 -0
  44. divejson-0.2.0/fixtures/invalid/version-not-second.divejson +5 -0
  45. divejson-0.2.0/fixtures/uddf/divelogs.divejson +95 -0
  46. divejson-0.2.0/fixtures/uddf/divelogs.uddf +112 -0
  47. divejson-0.2.0/fixtures/uddf/legacy-writer.divejson +64 -0
  48. divejson-0.2.0/fixtures/uddf/legacy-writer.uddf +66 -0
  49. divejson-0.2.0/fixtures/uddf/mix-only-cylinder.divejson +67 -0
  50. divejson-0.2.0/fixtures/uddf/mix-only-cylinder.uddf +70 -0
  51. divejson-0.2.0/fixtures/uddf/opendiving.divejson +197 -0
  52. divejson-0.2.0/fixtures/uddf/opendiving.uddf +161 -0
  53. divejson-0.2.0/fixtures/uddf/subsurface.divejson +129 -0
  54. divejson-0.2.0/fixtures/uddf/subsurface.uddf +163 -0
  55. divejson-0.2.0/fixtures/valid/demo-logbook.divejson +4164 -0
  56. divejson-0.2.0/fixtures/valid/minimal.divejson +5 -0
  57. divejson-0.2.0/fixtures/valid/technical-dive.divejson +465 -0
  58. divejson-0.2.0/pyproject.toml +61 -0
  59. divejson-0.2.0/schema/1.0/divejson.schema.json +450 -0
  60. divejson-0.2.0/tests/helpers.py +57 -0
  61. divejson-0.2.0/tests/test_cli.py +149 -0
  62. divejson-0.2.0/tests/test_conform.py +271 -0
  63. divejson-0.2.0/tests/test_package.py +62 -0
  64. divejson-0.2.0/tests/test_uddf_fixtures.py +92 -0
  65. divejson-0.2.0/tests/test_uddf_parsing.py +510 -0
  66. 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,9 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ .pytest_cache/
9
+ uv.lock
@@ -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.
@@ -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.
@@ -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.
@@ -0,0 +1 @@
1
+ 9dfba28c950113b5e3e5ae9b48eddb256bfa93fa