Sentinel Objects¶
pcapkit.corekit.sentinels is the shared home of every module-level
singleton sentinel this package defines for itself: a value recognised only by
identity (value is SENTINEL), so it cannot be confused with a value a
caller might legitimately pass.
They are all defined here, and this is their only import path: the modules that use them do not re-export them (#1744).
Each behaves like a built-in singleton such as None: calling its type,
copying it and pickling it give back the one instance, it reprs as its own
name, neither it nor its type can be changed, and its type cannot be
subclassed (#1842).
Added in version 1.5.0: NoValueType moves here from pcapkit.corekit.fields.field
(#911), and NO_VALUE replaces its NoValue there
(#937). Sentinel is the public base of every sentinel
type (#1842).
- class pcapkit.corekit.sentinels.Sentinel[source]¶
Bases:
objectAbstract base of every sentinel type, so each behaves like the type of
None.A subclass sets
_nameto the name of the module-level variable that holds its one instance, which is what makes it concrete, and gets the rest from here and fromSentinelMeta:__new__()hands back the one instance, so calling the type again cannot produce a second object that fails anischeck. It returns the existing instance rather than raising, astype(None)()returnsNone, so a caller who builds one by mistake still gets the sentinel.__copy__()and__deepcopy__()returnself.__reduce__()returns_name, asEllipsis.__reduce__()returns'Ellipsis', sopicklestores the instance as a reference to that module-level name and loads it back as the same object, at every protocol. Guarding__new__()alone would not cover protocols 0 and 1, which rebuild throughcopyreg._reconstructor()and so callobject.__new__()directly.__repr__()returns_nameitself, asrepr(None)is'None'.strgives the same, since nothing here overridesobject.__str__().__setattr__()and__delattr__()raiseAttributeErrorfor every name, asNone.x = 1does, and__slots__is empty, so an instance has no__dict__to change either.SentinelMetakeeps the type itself immutable, and a concrete one free of subclasses.
__bool__is left to each sentinel, since only some of them stand for an absent value.Public, as the library’s other abstract bases are, so that an annotation or an
isinstance()check can name every sentinel at once. Every sentinel the library itself uses is defined in this module (#911).A pickle written before #1842 still loads. A pickle of
NULLnames_get_null, kept for it, and a pickle of another sentinel at protocol 2 or above rebuilds through__new__(); both load as the sentinel. A pickle of another sentinel at protocol 0 or 1 rebuilds throughcopyreg._reconstructor(), which no hook on the class can reach, so it loads as a second instance of the type.Added in version 1.5.0.
- static __new__(cls)[source]¶
Return the one instance of this class there will ever be.
- Raises:
TypeError – If
clsis abstract, asobject.__new__()does.- Return type:
- __deepcopy__(memo)[source]¶
Return
self, for the same reason as__copy__().- Parameters:
memo (
dict[int,Any]) – Thecopy.deepcopy()memo table. Unused: returningselfneeds no entry, since nothing about this object is ever copied.- Return type:
- __setattr__(name, value)[source]¶
Refuse, as
None.x = 1does: a sentinel has no state to change.- Parameters:
- Raises:
AttributeError – For every
name.- Return type:
- __delattr__(name)[source]¶
Refuse, as
del None.xdoes.- Parameters:
name (
str) – Attribute name.- Raises:
AttributeError – For every
name.- Return type:
- class pcapkit.corekit.sentinels.NullType[source]¶
Bases:
SentinelType of
NULL, the omitted-class_/modulesentinel.A distinct class rather than a plain
strsuch as'(null)', so thatiscomparisons against it mean what they say: nostra caller passes, including one that happens to spell'(null)'itself, can be this sentinel. See GitHub issue #833.Sentinelkeeps it to one instance.ModuleDescriptor.klasschecksis NULL; a second instance would slip past it togetattr()and downgrade a cleanProtocolErrorinto a bareTypeError(attribute name must be string, not 'NullType').A caveat rather than a defect:
importlib.reload()on this module re-executes theclassstatement andNULL = NullType()below, producing a second singleton that the reloaded code compares against correctly but that every module which already imported the pre-reloadNULLstill holds – so a comparison spanning the reload sees two “singletons” that are not each other.ModuleDescriptor.klassfaces exactly this class of problem for the class it resolves, which is why it re-readssys.moduleson every call rather than memoising; nothing equivalent is possible here, because unlike a resolved class there is no live registry this sentinel could be re-read from. It is a property of sharing one module-level binding across a reload, not something this class’s singleton guarantees claim to solve, and nothing in this package reloadspcapkit.corekit.sentinelsafter import. Pickling a pre-reloadNULLraisespickle.PicklingError, since it is no longer the object its name refers to.Reloading
pcapkit.corekit.moduleinstead has no effect on the singleton at all. That module only importsNULLfrom here, and re-running an already-satisfiedfrom ... importreads the current binding insys.modulesrather than re-executing anything, so it neither mints a new instance nor loses the old one.
- pcapkit.corekit.sentinels.NULL = NULL¶
Sentinel for an omitted
class_argument to theregister_*helpers inpcapkit.foundation.registry.protocolsandpcapkit.foundation.registry.foundation. Housed here with the package’s other sentinels, per the module docstring above.- Type:
- class pcapkit.corekit.sentinels.NoValueType[source]¶
Bases:
SentinelType of
NO_VALUE, the default value forpcapkit.corekit.fields.Housed here per GitHub issue #911;
FieldBase.defaultdocuments it as the field-default sentinel.Changed in version 1.5.0: A singleton: calling this type,
copy.deepcopy()andpicklegive backNO_VALUErather than a second instance (#1842). It reprs asNO_VALUE, its own name, asrepr(None)is'None', rather than as an object address, which is what a field constructor’s signature and a parsed schema’s repr show for an unset value (#1841). Its attributes, and its type’s, cannot be set or deleted, and the type cannot be subclassed.
- pcapkit.corekit.sentinels.NO_VALUE = NO_VALUE¶
Default value for
FieldBase.default.- Type:
- class pcapkit.corekit.sentinels.NoDefaultType[source]¶
Bases:
SentinelType of
NO_DEFAULT, the omitted-defaultsentinel forEnumLookup.get.A dedicated class rather than a bare
object, as review of the work for #857 asked, following the house convention. A bareobjectcompares underisexactly as safely as a dedicated class with no__eq__of its own does; what it lacks is a readable representation: it prints as<object object at 0x...>in a signature, inhelp(), and in a traceback, whereNoDefaultType()– via the__repr__Sentinelgives it – prints asNO_DEFAULT. The names follow the convention every sentinel here shares, settled by GitHub issue #937:<Name>Typefor the class and SCREAMING_SNAKE for the instance.Sentinelkeeps it to one instance. That guards against a caller writingregistry.get(key, default=NoDefaultType())– perhaps not realisingNO_DEFAULTalready exists – and getting back a second, non-identical sentinel that silently failsis NO_DEFAULTinsideEnumLookup.get, so their call is treated as supplying a real (if useless) default instead of the no default they meant.A reload caveat that is dormant rather than live.
importlib.reload()on this module re-executes bothclass NoDefaultType:andNO_DEFAULT = NoDefaultType()below, producing a fresh, distinct object; any consumer that had already captured the pre-reload one – as a bound parameter default, say – goes on holding the stale one, and a bareheld_default is NO_DEFAULTcomparison against the post-reload global then readsFalsewhere it once readTrue.EnumLookup.getis such a consumer but is not affected: its fallback is guarded bydefault not in cls._value2member_map_(added by GitHub issue #864 to stopdefaultminting a new member), and aNoDefaultTypeinstance, stale or fresh, is never a registered enum value, so the guard readsTruefor it either way andgetre-raises the original lookup error regardless of whichNO_DEFAULTa caller’s default is stale against. Measured:>>> Hardware.get('Definitely-Not-A-Member') # before reload EnumKeyError: "'Definitely-Not-A-Member' is not a valid Hardware" >>> importlib.reload(pcapkit.corekit.sentinels) >>> Hardware.get('Definitely-Not-A-Member') # after reload EnumKeyError: "'Definitely-Not-A-Member' is not a valid Hardware"
The hazard is not theoretical elsewhere: reload staleness is a tracked defect class here for other constructs – see
pcapkit.protocols.protocol.ProtocolBase._lookup_next_layer()’s own docstring note on why it declines to memoise a resolved class, andtests.protocols.test_dispatch_default_resolution_unit’s owntest_no_stale_class_survives_a_module_reload, which reloads a module deliberately to pin the fix for exactly that class of bug. A future comparison site written the vulnerable way – a bareis NO_DEFAULTwith no independent guard behind it – would reproduce it. Nothing in this package reloadspcapkit.corekit.sentinelsafter import, but that is a caveat to keep honest rather than a guarantee this class enforces.Note
Reloading
pcapkit.corekit.enumalone does not produce a freshNO_DEFAULT. That module does not defineNoDefaultType; it readsNO_DEFAULToff this module once, at its own import time, into its own module global, and a reload just re-runs that read, which – so long as this module has not also been reloaded – fetches back the identical object. Producing a fresh one takes reloading this module, where the class statement lives; and sincefrom ... importbinds a copy rather than a live alias,pcapkit.corekit.enum’s own global stays pointed at whatever it read until something re-runs that import.Unlike both
NullTypeandNoValueType, this deliberately does not define__bool__. Both of those model an absent value, so reading falsy in a boolean context is the point.NO_DEFAULTmodels something different: a marker meaning no default was supplied, checked exclusively byisatEnumLookup.get’s comparison sites – nothing here ever evaluates it for truthiness. Giving it__bool__ -> Falsefor symmetry with those two would invite exactly the conflation this sentinel exists to rule out: code that writesif not default:instead ofif default is NO_DEFAULT:would then readNO_DEFAULTthe same way it reads a caller’s genuine falsy default –0,'',NoneorFalse– which is the collision with a genuine default that #857 exists to rule out. Leaving__bool__undefined makesNoDefaultType()truthy (the default for any object defining neither__bool__nor__len__), which at least does not look like one of the falsy values it must never be mistaken for.
- pcapkit.corekit.sentinels.NO_DEFAULT = NO_DEFAULT¶
The
defaultargument value that means no default, i.e. let an unresolvable key propagate its lookup error rather than falling back. SeeNoDefaultTypefor why this is a dedicated class rather than a bareobject, and why it does not define the__bool__that itspcapkit.corekitsiblings do.- Type:
Note
AbsentType and ABSENT are private – never imported outside
pcapkit.protocols.protocol, and named in no module’s __all__. For
SCREAMING_SNAKE consistency with the other sentinels (see
Naming a Sentinel), neither carries the leading underscore that would
hide it from Sphinx, so both are documented below and marked private.
- class pcapkit.corekit.sentinels.AbsentType[source]¶
Bases:
SentinelType of
ABSENT, the absent-key sentinel.A distinct class rather than a bare
objectso that the sentinel has a name of its own in a traceback or a debugger, and so that a type checker has something to name whereobject()would give it nothing. It followsNoValueType, which does the same job for an unset field default; this is a sibling of it rather than a reuse, since that one is documented as the default value ofFieldBase.defaultand means “no value was given”, not “this key is not here”.Defined here, alongside the package’s other sentinels, per the owner’s ruling on GitHub issue #911. Private, although the names carry no leading underscore: GitHub issue #937 put every sentinel on the CamelCase/SCREAMING_SNAKE convention, and the owner’s ruling on GitHub issue #719 held that documenting
ABSENTas a private type and class, not for public use, is enough to replace the underscore. Nothing outsidepcapkit.protocols.protocolreadsABSENT, from here or from there, and neither this module’s nor that module’s__all__names either one. This docstring, and the “Naming a Sentinel” section ofdocs/source/contributing/conventions/sentinel-convention.rst, are what record that.
- pcapkit.corekit.sentinels.ABSENT = ABSENT¶
Absent-versus-
Nonesentinel forpcapkit.protocols.protocol._declared_keywords()to read a class’s own__keywords__out of its__dict__, whereNoneis itself a meaningful value – the opt-out that says the class cannot enumerate its keywords, cf.ProtocolBase.__keywords__. Never leavespcapkit.protocols.protocol, which imports it from here for exactly that one read. Private by documentation rather than by a leading underscore – seeAbsentType’s own docstring.- Type:
Internal Definitions¶
- class pcapkit.corekit.sentinels.SentinelMeta(name, bases, attrs, /, **kwargs)[source]¶
Bases:
ABCMetaMetaclass of
Sentinel, so every sentinel type acts astype(None)does.A concrete sentinel type – one that sets
_name, and so has no abstract member left – cannot be subclassed, either by aclassstatement or byregister(). Both raiseTypeError, asclass X(type(None))does. An abstract one,Sentinelincluded, can be.A concrete sentinel type makes its one instance while its
classstatement runs, and keeps it as_instance.Every sentinel type is immutable once its
classstatement has run. Setting or deleting any attribute on it raisesTypeError, astype(None).x = 1does, so_name,_instanceand the methods every sentinel inherits stay as they were defined. The one exception is_abc_negative_cacheand_abc_negative_cache_version, which_py_abc, the fallbackabcuses without the C_abcmodule, rebinds during anisinstance()check (#1960).
A concrete sentinel type also gets
__final__set toTruehere.final()would record the same, but a class decorator runs only after theclassstatement, when the type already refuses the write, andtyping.final()skips an attribute it cannot set.These guards, and
Sentinel’s own, are Python-level hooks. Sotype.__setattr__()called directly on the type, orobject.__setattr__()setting__class__on an instance, still reaches past them, where nothing reaches past a built-in type’s.Added in version 1.5.0.