Skip to content

fix(corekit): export the sentinel objects, not their types - #916

Merged
JarryShaw merged 1 commit into
mainfrom
fix/911-export-sentinel-objects
Sep 29, 2026
Merged

JarryShaw merged 1 commit into
mainfrom
fix/911-export-sentinel-objects

Conversation

@JarryShaw

Copy link
Copy Markdown
Owner

Please follow the guide below

What is the purpose of your pull request?

  • fix
  • feat
  • perf
  • refactor
  • test
  • docs
  • ci
  • chore

Description of your pull request and other information

The unblocked half of #911. The housing question -- one module per sentinel, or one module for all -- is still open, so nothing is moved here, no sentinels.py is created and no re-export shim is added.

Per the ruling, "we should ONLY export the objects (like NULL) to users", which cuts both ways:

module __all__ before __all__ after
pcapkit/corekit/module.py ['NULL', 'NullType', 'ModuleDescriptor'] ['NULL', 'ModuleDescriptor']
pcapkit/corekit/enum.py ['NO_DEFAULT', 'NoDefaultType', 'EnumLookup', 'EnumRegistry'] ['NO_DEFAULT', 'EnumLookup', 'EnumRegistry']
pcapkit/corekit/fields/field.py ['Field'] ['NoValue', 'Field']

_Absent / _AbsentType in pcapkit/protocols/protocol.py is untouched: private-named, exported neither way, and the ruling is about what users see.

breaking, but narrowly. Nothing in the tree star-imports any of these three modules (git grep 'from pcapkit.corekit.module import \*' and the other two: no hits), and every in-tree user names the types inside if TYPE_CHECKING: -- NullType in foundation/registry/{foundation,protocols}.py, NoValueType in corekit/fields/{ipaddress,misc,numbers,strings}.py -- so import * is the only surface that narrows. NoDefaultType has no in-tree user outside enum.py.

docs/source/conventions.rst's sentinel section documented three sentinels; there are four. Adds the _Absent row, corrects both counts ("four in the tree", "the four sentinels"), notes why a leading-underscore sentinel was missed by a sweep filtering on capitalised names, records the export rule the code half implements, and carves out _NOT_FOUND = object() at pcapkit/utilities/compat.py:73 -- inside the vendored cached_property backport -- as exempt, which the "why a class and not object()" subsection previously read as a blanket rule with no exception.

One existing test encoded the old contract: tests/const/test_const_registry_protocol.py::NoDefaultSentinelTests asserted 'NoDefaultType' in enum.__all__. It now asserts the opposite, plus that the type is still reachable as an attribute. tests/project/test_public_api.py is unaffected -- its converse assertion (test_every_public_package_exports_every_public_attribute) covers packages, and none of the three is one; pcapkit/corekit/__init__.py and pcapkit/corekit/fields/__init__.py import by name, never *, so no package namespace changes.

New tests/corekit/test_sentinel_exports_unit.py: 18 tests, 21 subtests. Nine fail on origin/main -- five on the __all__ shape (module, enum, field, the star-import that actually reads __all__, and the identity of the object it binds) and four on the documentation half (the table rows, the counts, the export rule, the carve-out). The rest are regression guards: the sibling exports that must survive the edit (ModuleDescriptor, Field, EnumLookup, EnumRegistry), the types staying importable by dotted path, and the naming convention itself. Two also pin the contract that makes NoValue public in the first place -- it is documented as the value of FieldBase.default, and the default setter and deleter were both uncovered.

sphinx-build -b html into two fresh BUILDDIRs: 58 warnings before, 58 after, identical sets. NullType and NoValueType are still documented despite leaving __all__, because conf.py sets ignore-module-all: True and both have an explicit autoclass directive.

tests/corekit/, tests/project/test_public_api.py, tests/const/test_const_registry_protocol.py and tests/protocols/test_construction_keyword_check_unit.py under coverage run -m pytest: 395 passed, 16 skipped. tests/project/ separately: 178 passed. Coverage over the touched files, same selection before and after: corekit/fields/field.py 87% -> 88%, corekit/enum.py and corekit/module.py 100% -> 100% (already no headroom), protocols/protocol.py 59% unchanged (documented here, not edited). Did not run the full suite.

@JarryShaw JarryShaw added fix Pull requests that fix a defect (fix: subject prefix) breaking Breaks public-facing behaviour or API (apply alongside the type label) docs Pull requests that change documentation only (docs: subject prefix) test Pull requests that add or correct tests (test: subject prefix) review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Sep 29, 2026
@JarryShaw

Copy link
Copy Markdown
Owner Author

GOOD TO GO on 2581c780d — cross-review on sonnet (author opus). No changes needed; the three __all__ edits, the docs correction and the test file all hold up.

The risk I flagged — that removing a name from __all__ silently drops its autodoc page — does not apply, for two independent reasons. docs/source/conf.py:116 sets 'ignore-module-all': True, so autodoc never consults __all__; and NullType/NoValueType additionally carry explicit directives at docs/source/pcapkit/corekit/module.rst:30 and fields/field.rst:25. The reviewer went past the warning count to the rendered HTML and confirmed both still emit real href="…#pcapkit.corekit.module.NullType" cross-references pointing at real id= anchors. Sphinx: build succeeded, 58 warnings on both trees, warning sets identical line-for-line.

One thing neither the PR nor my brief said, and worth recording: NoDefaultType has no documentation to lose. pcapkit/corekit/enum.py has no doc page at all — no enum.rst exists and corekit/index.rst's toctree omits it, which is the pre-existing gap #902 already tracks. So its removal from __all__ costs nothing, but not because this PR established that.

Merge sequencing, verified by me rather than relayed. git merge-tree is clean both ways — #916 against #913 and #916 against #912 — and the reviewer went further than an empty conflict list, checking that the merged contributing/conventions.rst is byte-identical to #916's version and that the #913 union is the real union (627 lines) rather than a silent one-sided resolution. #916's hunks sit at old lines 137-224, clear of #913's 337-379. So #916 merges in any order. For contrast, #914 against #912 still reports CONFLICT (content): Merge conflict in docs/source/contributing/pep.rst — that one alone needs #912 first.

Confirmed independently: 9 of the 18 new tests fail against origin/main — the 5 __all__-shape ones plus 4 doc-text ones — an exact match to the claim; test_const_registry_protocol.py's edit is net-stricter, flipping assertIn to assertNotIn and adding a hasattr check rather than loosening anything; coverage rises on pcapkit/corekit/fields/field.py, with the newly covered lines being exactly the default property's getter/setter/deleter; and no sentinels.py, no moves, no shims, so the housing question is untouched.

On NoValue not being a singleton — the reviewer confirms NoValueType() is NoValue is False and that the PR does not hide it: the .. note:: sits in conventions.rst beside the export table, so a reader meets the caveat in the same breath as the export. Documenting it is the right scope here; making it a true singleton stays a follow-up.

The reviewer also reported a self-inflicted error rather than burying it: it checked out origin/main into its worktree while a background pytest was still running, which corrupted that run into looking like a real PR defect. It re-ran cleanly and the clean run is what stands. Unverified: the author's full-selection pass count, and tests/project/test_public_api.py's converse assertion, both narrowed for time.

@JarryShaw JarryShaw added review: good-to-go Cross-review at the current head says ready; CI state is separate and removed review: pending No verdict for the current head - never reviewed, or the head moved since the last one labels Sep 29, 2026
Per the owner's ruling on #911 -- "we should ONLY export the objects (like
`NULL`) to users" -- which cuts both ways:

- `corekit/module.py`: drop `'NullType'` from `__all__`.
- `corekit/enum.py`: drop `'NoDefaultType'` from `__all__`.
- `corekit/fields/field.py`: **add** `'NoValue'`, exported neither way before.
- `docs/source/conventions.rst`: the sentinel section documented three
  sentinels; there are four. Adds `_Absent`/`_AbsentType`, fixes both counts,
  records the export rule, and carves out `_NOT_FOUND = object()` in the
  vendored `cached_property` backport as exempt from it.
- `tests/const/test_const_registry_protocol.py`: the one test asserting
  `'NoDefaultType' in enum.__all__` now asserts the opposite.

Breaking only for `import *`: every type stays importable by its dotted path,
which is how every in-tree user already names them.

`tests/corekit/` and `tests/project/test_public_api.py` pass; Sphinx warnings
unchanged at 58.
@JarryShaw
JarryShaw force-pushed the fix/911-export-sentinel-objects branch from 2581c78 to d2011a0 Compare September 29, 2026 13:08
@JarryShaw

Copy link
Copy Markdown
Owner Author

Rebased onto main after #912 merged — d2011a0d6. Same cause as #913: #912 moved docs/source/conventions.rst to docs/source/contributing/conventions.rst, and GitHub scores this PR's edit to the old path as DIRTY even though git's rename detection resolves it cleanly. Cherry-picked with -X find-renames; the sentinel section landed on the moved file and the old path was not recreated.

Verified blob-identical to the reviewed head — git rev-parse gives the same object for 2581c780d and d2011a0d6 on all six paths, conventions.rst included across the rename. Nothing changed but the file's location, so the review: good-to-go verdict carries. tests/corekit/test_sentinel_exports_unit.py still passes 18/18 on the rebased tree.

Worth knowing for the sequencing: the new test's _sentinel_section() helper already looked for the doc at both docs/source/conventions.rst and docs/source/contributing/conventions.rst, which the author added deliberately so #912's move could not break it. It did exactly that job.

@JarryShaw
JarryShaw merged commit d31c0aa into main Sep 29, 2026
63 checks passed
@JarryShaw
JarryShaw deleted the fix/911-export-sentinel-objects branch September 29, 2026 13:41
@JarryShaw JarryShaw removed the review: good-to-go Cross-review at the current head says ready; CI state is separate label Sep 29, 2026
JarryShaw added a commit that referenced this pull request Sep 29, 2026
The `__all__` half of #911 landed as #916; this is the housing half, per
the owner's ruling -- "Okay one module for all four it is."

- Add pcapkit/corekit/sentinels.py holding NULL/NullType, NoValue/NoValueType,
  NO_DEFAULT/NoDefaultType and _Absent/_AbsentType, moved from module.py,
  fields/field.py, enum.py and protocols/protocol.py respectively.
- Each original module keeps a re-export so every existing
  `from <module> import <name>` -- object and type, including the
  `if TYPE_CHECKING:`-only ones -- keeps working unchanged.
- No import cycle: sentinels.py depends only on pcapkit.utilities.compat.
- Fix an unrelated, pre-existing staleness found in transit: NoDefaultType's
  reload example predated #864's non-minting guard and no longer reproduced;
  replaced with a verified one.
- Update conventions.rst's sentinel table and prose to the new module.
- Add tests/corekit/test_sentinels_housing_unit.py: cross-module identity
  and no-cycle checks.

Build: isort clean; pylint/mypy unchanged from origin/main on every touched
file. tests/corekit/, tests/project/ and
tests/protocols/test_construction_keyword_check_unit.py all pass.
JarryShaw added a commit that referenced this pull request Sep 29, 2026
The `__all__` half of #911 landed as #916; this is the housing half, per
the owner's ruling -- "Okay one module for all four it is."

- Add pcapkit/corekit/sentinels.py holding NULL/NullType, NoValue/NoValueType,
  NO_DEFAULT/NoDefaultType and _Absent/_AbsentType, moved from module.py,
  fields/field.py, enum.py and protocols/protocol.py respectively.
- Each original module keeps a re-export so every existing
  `from <module> import <name>` -- object and type, including the
  `if TYPE_CHECKING:`-only ones -- keeps working unchanged.
- No import cycle: sentinels.py depends only on pcapkit.utilities.compat.
- Fix an unrelated, pre-existing staleness found in transit: NoDefaultType's
  reload example predated #864's non-minting guard and no longer reproduced;
  replaced with a verified one.
- Update conventions.rst's sentinel table and prose to the new module.
- Add tests/corekit/test_sentinels_housing_unit.py: cross-module identity
  and no-cycle checks.

Build: isort clean; pylint/mypy unchanged from origin/main on every touched
file. tests/corekit/, tests/project/ and
tests/protocols/test_construction_keyword_check_unit.py all pass.
JarryShaw added a commit that referenced this pull request Sep 29, 2026
The `__all__` half of #911 landed as #916; this is the housing half, per
the owner's ruling -- "Okay one module for all four it is."

- Add pcapkit/corekit/sentinels.py holding NULL/NullType, NoValue/NoValueType,
  NO_DEFAULT/NoDefaultType and _Absent/_AbsentType, moved from module.py,
  fields/field.py, enum.py and protocols/protocol.py respectively.
- Each original module keeps a re-export so every existing
  `from <module> import <name>` -- object and type, including the
  `if TYPE_CHECKING:`-only ones -- keeps working unchanged.
- No import cycle: sentinels.py depends only on pcapkit.utilities.compat.
- Fix an unrelated, pre-existing staleness found in transit: NoDefaultType's
  reload example predated #864's non-minting guard and no longer reproduced;
  replaced with a verified one.
- Update conventions.rst's sentinel table and prose to the new module.
- Add tests/corekit/test_sentinels_housing_unit.py: cross-module identity
  and no-cycle checks.

Build: isort clean; pylint/mypy unchanged from origin/main on every touched
file. tests/corekit/, tests/project/ and
tests/protocols/test_construction_keyword_check_unit.py all pass.
JarryShaw added a commit that referenced this pull request Sep 29, 2026
The `__all__` half of #911 landed as #916; this is the housing half, per
the owner's ruling -- "Okay one module for all four it is."

- Add pcapkit/corekit/sentinels.py holding NULL/NullType, NoValue/NoValueType,
  NO_DEFAULT/NoDefaultType and _Absent/_AbsentType, moved from module.py,
  fields/field.py, enum.py and protocols/protocol.py respectively.
- Each original module keeps a re-export so every existing
  `from <module> import <name>` -- object and type, including the
  `if TYPE_CHECKING:`-only ones -- keeps working unchanged.
- No import cycle: sentinels.py depends only on pcapkit.utilities.compat.
- Fix an unrelated, pre-existing staleness found in transit: NoDefaultType's
  reload example predated #864's non-minting guard and no longer reproduced;
  replaced with a verified one.
- Update conventions.rst's sentinel table and prose to the new module.
- Add tests/corekit/test_sentinels_housing_unit.py: cross-module identity
  and no-cycle checks.

Build: isort clean; pylint/mypy unchanged from origin/main on every touched
file. tests/corekit/, tests/project/ and
tests/protocols/test_construction_keyword_check_unit.py all pass.
JarryShaw added a commit that referenced this pull request Sep 29, 2026
The `__all__` half of #911 landed as #916; this is the housing half, per
the owner's ruling -- "Okay one module for all four it is."

- Add pcapkit/corekit/sentinels.py holding NULL/NullType, NoValue/NoValueType,
  NO_DEFAULT/NoDefaultType and _Absent/_AbsentType, moved from module.py,
  fields/field.py, enum.py and protocols/protocol.py respectively.
- Each original module keeps a re-export so every existing
  `from <module> import <name>` -- object and type, including the
  `if TYPE_CHECKING:`-only ones -- keeps working unchanged.
- No import cycle: sentinels.py depends only on pcapkit.utilities.compat.
- Fix an unrelated, pre-existing staleness found in transit: NoDefaultType's
  reload example predated #864's non-minting guard and no longer reproduced;
  replaced with a verified one.
- Update conventions.rst's sentinel table and prose to the new module.
- Add tests/corekit/test_sentinels_housing_unit.py: cross-module identity
  and no-cycle checks.

Build: isort clean; pylint/mypy unchanged from origin/main on every touched
file. tests/corekit/, tests/project/ and
tests/protocols/test_construction_keyword_check_unit.py all pass.
JarryShaw added a commit that referenced this pull request Sep 29, 2026
… (#922)

The `__all__` half of #911 landed as #916; this is the housing half, per
the owner's ruling -- "Okay one module for all four it is."

- Add pcapkit/corekit/sentinels.py holding NULL/NullType, NoValue/NoValueType,
  NO_DEFAULT/NoDefaultType and _Absent/_AbsentType, moved from module.py,
  fields/field.py, enum.py and protocols/protocol.py respectively.
- Each original module keeps a re-export so every existing
  `from <module> import <name>` -- object and type, including the
  `if TYPE_CHECKING:`-only ones -- keeps working unchanged.
- No import cycle: sentinels.py depends only on pcapkit.utilities.compat.
- Fix an unrelated, pre-existing staleness found in transit: NoDefaultType's
  reload example predated #864's non-minting guard and no longer reproduced;
  replaced with a verified one.
- Update conventions.rst's sentinel table and prose to the new module.
- Add tests/corekit/test_sentinels_housing_unit.py: cross-module identity
  and no-cycle checks.

Build: isort clean; pylint/mypy unchanged from origin/main on every touched
file. tests/corekit/, tests/project/ and
tests/protocols/test_construction_keyword_check_unit.py all pass.
@JarryShaw JarryShaw added this to the 1.5 milestone Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Breaks public-facing behaviour or API (apply alongside the type label) docs Pull requests that change documentation only (docs: subject prefix) fix Pull requests that fix a defect (fix: subject prefix) test Pull requests that add or correct tests (test: subject prefix)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant