Carry the OWID source inside the published package - #65
Merged
Conversation
The published package could not be imported at all. Installing fiftyone-pipeline-did 4.5.12 from PyPI into a clean environment and running "import fiftyone_pipeline_did" raised ModuleNotFoundError for "owid", because the package imported the OWID library and nothing installed it. The published metadata declared no dependencies at all. Version 4.5.11 of 8 August fails the same way, and 4.5.12 carries the new cloud client, so the new client is live and unusable. The dependency cannot come from a package registry. The 51Degrees fork of OWID is not published to PyPI, and the name "owid" there belongs to an unrelated project, so declaring a dependency on that name would install the wrong thing. The build now copies the OWID source out of the owid-python submodule into the package as the private module fiftyone_pipeline_did._owid before the source distribution and the wheel are built. The leading underscore keeps the top level name "owid" free on the consumer's machine. The Apache-2.0 licence, and a notice naming the upstream repository and the commit the copy was taken from, travel with the source into both distributions. The one third party requirement the copied source has, cryptography, is now declared. The envelope and error types the public API refers to are exported from the package itself, because a caller who has only the published package has no other supported name for them. Nothing changes in the repository, where the submodule stays the only copy of the OWID code and the copy the build makes is ignored. The same copy step runs before the project is built, so a developer checkout and the unit test job produce the package the same way the publish does.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The live breakage
The published Python package cannot be imported at all. Installing
fiftyone-pipeline-did4.5.12from PyPI into a clean virtual environment and running
import fiftyone_pipeline_didgives:importlib.metadata.requires("fiftyone_pipeline_did")returnsNone, so thepublished metadata declares no dependencies, and the only files in the
installed package are
__init__.py,did_client.py,fod_id.pyandid_type.py. Version 4.5.11 of 8 August fails the same way, so the fault hasbeen live since then. Version 4.5.12 was published automatically at 01:29 on
27 August 2026 carrying the new
DidClientfrom#64, so the new client
is live and unusable.
The dependency cannot come from a package registry. The 51Degrees fork,
51Degrees/owid-python, is not
published to PyPI, and the name
owidon PyPIbelongs to an unrelated COVID data project, so declaring a dependency on that
name would install the wrong thing.
The fix
The OWID source is copied into the package as the private module
fiftyone_pipeline_did._owidbefore the distributions are built.ci/copy-owid-source.ps1(new) copiesowid-python/owid/*.pyintofiftyone_pipeline_did/src/fiftyone_pipeline_did/_owid/, copies theApache-2.0
LICENSEbeside it and writes aNOTICEnaming the upstreamrepository and the exact submodule commit the copy came from. It throws with
a clear message if the submodule is not checked out. Nothing is fetched over
the network, because CI clones with
--recurse-submodules, soowid-pythonis already on disk.
ci/build-package.ps1calls it before the distributions are built, andci/build-project.ps1calls it before the project is built, which is thestep that precedes the unit tests in the pull request and nightly flows.
setup.ps1calls it for a developer checkout, and also installs theowid-pythonfork editable, which is what the tests and examples importunder the plain name
owidto build signed envelopes.setup.pyaddsfiftyone_pipeline_did._owidtopackages, declarescryptography>=41, which is the only third party requirement the copiedsource has, and ships the licence and notice through
package_data.MANIFEST.incarries the same two files into the source distribution, whichit did not do before, so the Apache-2.0 attribution now travels with the
code.
fod_id.pyanddid_client.pyimport from._owidrather thanowid.fiftyone_pipeline_didnow exportsOwidandOwidError. The public APIrefers to both of them (
FodId.from_owidtakes an envelope, and the parsemethods raise the error), and after this change a caller who has only the
published package has no other supported name for them. The docstrings that
named
owid.OwidErrornow name the exported one.The leading underscore is deliberate. Shipping a top level
owidpackage wouldclaim that name on the consumer's machine and collide with the unrelated PyPI
project.
The OWID source is not committed. The submodule stays the only copy of it in
the repository, and the copy the build makes is added to
.gitignore.Evidence
Every check below was run on this branch.
The tests.
python -m tox -e pyinfiftyone_pipeline_did, which is howci/run-unit-tests.ps1runs them. Before the change: 111 collected, 109passed, 2 skipped. After: 111 collected, 109 passed, 2 skipped. The two skips
are the live cloud tests, which need a resource key.
One test needed changing.
tests/test_fodid.pybuilds its fixtures with thefork imported as
owid, which still works and is untouched, buttest_invalid_base64_raisescaughtowid.OwidError, and the package nowraises the copy's error class, which is a different class object. The test
catches the exported
fiftyone_pipeline_did.OwidErrorinstead, which is what acaller of the published package has to catch.
The distributions.
python -m buildproduces both. The wheel containsfiftyone_pipeline_did/_owid/with the eight modules,LICENSEandNOTICE,and the source distribution contains the same under
src/.A clean install. The wheel installed into a fresh virtual environment
pulled in
cryptographyon its own,importlib.metadata.requiresreturns['cryptography>=41'], and there is no top levelowidpackage in thatenvironment. A round trip in that environment signs a real envelope with a
fresh ECDSA P-256 key pair, parses it back with
FodId.from_base64in bothbase64 alphabets, checks the flags, licence id, value and identifier type,
verifies the signature against the public key, and confirms that invalid base64
raises the exported
OwidError. The same round trip passes against an installfrom the source distribution.
Python 3.9, the declared floor. The package byte-compiles under 3.9, and
the same round trip passes in a 3.9 virtual environment with
cryptography==41.0.7, which is the lower bound the new requirement allows.The 3.9 build on this machine is 3.9.1, whose Rust extension loader cannot load
current
cryptographyreleases, and that failure is incryptographyitselfrather than in this package.
A developer checkout. The submodule is untouched,
git ls-filesshows noOWID source in the tree, and the copy is ignored.
pwsh ./ci/copy-owid-source.ps1with no arguments works from the repository root, and with
-RepoRoot <name>from the workspace above the clone, which is the shape CI uses. Pointed at a
directory with no submodule it exits 1 with a message naming the missing path
and the command to fix it.
What is not covered
The other five packages in this repository were not rebuilt or retested, as
nothing in this change touches them beyond the one added line in
ci/build-project.ps1, which calls the new script. The nightly pull requestrun will cover them.
This change was produced with AI assistance under James Rosewell's direction
and needs human review before merge.