# -*- coding: utf-8 -*-
# pylint: disable=line-too-long
"""HTTP Method
=================
.. module:: pcapkit.const.http.method
This module contains the constant enumeration for **HTTP Method**,
which is automatically generated from :class:`pcapkit.vendor.http.method.Method`.
"""
from typing import TYPE_CHECKING
from aenum import StrEnum
from pcapkit.corekit.enum import EnumRegistry
if TYPE_CHECKING:
from typing import Optional, Type
__all__ = ['Method']
[docs]
class Method(EnumRegistry, StrEnum):
"""[Method] HTTP Method
.. note::
Neither ``_missing_`` nor ``get()`` mints any more. The owner ruled on
GitHub issue :issue:`860` that ``get`` should not mint: only IANA-registered
values are legitimate members, a new one is properly created through
:meth:`register`, and ``get`` is not given enough information to create
one. Concretely true here -- a bare wire method verb carries no
:attr:`safe`/:attr:`idempotent`, so minting one used to register a
permanent member with both hollowed out to their defaults;
:meth:`register` is the path that can actually supply them.
"""
if TYPE_CHECKING:
#: Safe method.
safe: 'bool'
#: Idempotent method.
idempotent: 'bool'
def __new__(cls, value: 'str', safe: 'bool' = False,
idempotent: 'bool' = False) -> 'Type[Method]':
obj = str.__new__(cls, value)
obj._value_ = value
obj.safe = safe
obj.idempotent = idempotent
return obj
def __repr__(self) -> 'str':
return f'<{self.__class__.__name__}.{self._value_}>'
#: ACL [:rfc:`3744#section-8.1`]
ACL = 'ACL', False, True
#: BASELINE-CONTROL [:rfc:`3253#section-12.6`]
BASELINE_CONTROL = 'BASELINE-CONTROL', False, True
#: BIND [:rfc:`5842#section-4`]
BIND = 'BIND', False, True
#: CHECKIN [:rfc:`3253#section-9.4`]
CHECKIN = 'CHECKIN', False, True
#: CHECKOUT [:rfc:`3253#section-8.8`]
CHECKOUT = 'CHECKOUT', False, True
#: CONNECT [:rfc:`9110#section-9.3.6`]
CONNECT = 'CONNECT', False, False
#: COPY [:rfc:`4918#section-9.8`]
COPY = 'COPY', False, True
#: DELETE [:rfc:`9110#section-9.3.5`]
DELETE = 'DELETE', False, True
#: GET [:rfc:`9110#section-9.3.1`]
GET = 'GET', True, True
#: HEAD [:rfc:`9110#section-9.3.2`]
HEAD = 'HEAD', True, True
#: LABEL [:rfc:`3253#section-8.2`]
LABEL = 'LABEL', False, True
#: LINK [:rfc:`2068#section-19.6.1.2`]
LINK = 'LINK', False, True
#: LOCK [:rfc:`4918#section-9.10`]
LOCK = 'LOCK', False, False
#: MERGE [:rfc:`3253#section-11.2`]
MERGE = 'MERGE', False, True
#: MKACTIVITY [:rfc:`3253#section-13.5`]
MKACTIVITY = 'MKACTIVITY', False, True
#: MKCALENDAR [:rfc:`4791#section-5.3.1`][:rfc:`8144#section-2.3`]
MKCALENDAR = 'MKCALENDAR', False, True
#: MKCOL
#: [:rfc:`4918#section-9.3`][:rfc:`5689#section-3`][:rfc:`8144#section-2.3`]
MKCOL = 'MKCOL', False, True
#: MKREDIRECTREF [:rfc:`4437#section-6`]
MKREDIRECTREF = 'MKREDIRECTREF', False, True
#: MKWORKSPACE [:rfc:`3253#section-6.3`]
MKWORKSPACE = 'MKWORKSPACE', False, True
#: MOVE [:rfc:`4918#section-9.9`]
MOVE = 'MOVE', False, True
#: OPTIONS [:rfc:`9110#section-9.3.7`]
OPTIONS = 'OPTIONS', True, True
#: ORDERPATCH [:rfc:`3648#section-7`]
ORDERPATCH = 'ORDERPATCH', False, True
#: PATCH [:rfc:`5789#section-2`]
PATCH = 'PATCH', False, False
#: POST [:rfc:`9110#section-9.3.3`]
POST = 'POST', False, False
#: PRI [:rfc:`9113#section-3.4`]
PRI = 'PRI', True, True
#: PROPFIND [:rfc:`4918#section-9.1`][:rfc:`8144#section-2.1`]
PROPFIND = 'PROPFIND', True, True
#: PROPPATCH [:rfc:`4918#section-9.2`][:rfc:`8144#section-2.2`]
PROPPATCH = 'PROPPATCH', False, True
#: PUT [:rfc:`9110#section-9.3.4`]
PUT = 'PUT', False, True
#: QUERY [:rfc:`10008#section-2`]
QUERY = 'QUERY', True, True
#: REBIND [:rfc:`5842#section-6`]
REBIND = 'REBIND', False, True
#: REPORT [:rfc:`3253#section-3.6`][:rfc:`8144#section-2.1`]
REPORT = 'REPORT', True, True
#: SEARCH [:rfc:`5323#section-2`]
SEARCH = 'SEARCH', True, True
#: TRACE [:rfc:`9110#section-9.3.8`]
TRACE = 'TRACE', True, True
#: UNBIND [:rfc:`5842#section-5`]
UNBIND = 'UNBIND', False, True
#: UNCHECKOUT [:rfc:`3253#section-4.5`]
UNCHECKOUT = 'UNCHECKOUT', False, True
#: UNLINK [:rfc:`2068#section-19.6.1.3`]
UNLINK = 'UNLINK', False, True
#: UNLOCK [:rfc:`4918#section-9.11`]
UNLOCK = 'UNLOCK', False, True
#: UPDATE [:rfc:`3253#section-7.1`]
UPDATE = 'UPDATE', False, True
#: UPDATEREDIRECTREF [:rfc:`4437#section-7`]
UPDATEREDIRECTREF = 'UPDATEREDIRECTREF', False, True
#: VERSION-CONTROL [:rfc:`3253#section-3.5`]
VERSION_CONTROL = 'VERSION-CONTROL', False, True
[docs]
@classmethod
def _unregistered_member(cls, value: 'str', name: 'str') -> 'Method':
"""Build a member absent from this registry's own lookup tables.
Leaves :attr:`safe` and :attr:`idempotent` at the same defaults
:meth:`__new__` itself would, rather than missing entirely --
:meth:`~pcapkit.corekit.enum.EnumRegistry._unregistered_member`
bypasses :meth:`__new__` (it calls :class:`str`'s directly), so
those two attributes would otherwise be absent. There is no more
specific value to reconstruct them from -- a bare wire method verb
carries neither, which is exactly the owner's reasoning for why
``get()``/``_missing_`` must not mint one: :meth:`register` is the
path that can actually supply them.
Args:
value: Value to get enum item -- the convention here, shared with
:class:`~pcapkit.const.ftp.command.FEATCode` and
:class:`~pcapkit.const.ftp.command.Command`, is the caller's
own casing, unchanged: an unregistered member's *value* is
exactly what was observed on the wire, matching how a
*registered* member's own value is exactly what was
declared, never reformatted. Only :attr:`name` -- the
identifier, not the value -- is canonicalised.
A *registered* member of this class used to carry something
different here: GitHub issue :issue:`870` found that
:meth:`__new__` called ``str.__new__(cls)`` with no
argument at all, so every one of the 40 declared members'
own :class:`str` payload was permanently empty regardless
of ``value`` (``str(Method.GET) == ''``, and
``Method.GET == 'GET'`` was :obj:`False`) -- true on
``main`` at ``60b85e3a4`` as well as when this docstring
was first written. :issue:`870` fixed :meth:`__new__` to
``str.__new__(cls, value)``, mirroring
:class:`~pcapkit.const.ftp.command.Command`'s own
``__new__``, so a registered member's payload now agrees
with an *unregistered* member built through this method:
both carry their own value as real :class:`str` content
(``str(Method('frob')) == 'frob'``).
name: Bare label for the unregistered member -- here, the
canonical upper-case form of ``value``, matching the name
every *registered* member of this class is looked up by,
since :issue:`860`'s open-vocabulary registries have no manufactured
placeholder label to fall back to.
"""
obj = super()._unregistered_member(value, name)
obj.safe = False
obj.idempotent = False
return obj
@classmethod
def get(cls, key: 'str', default: 'Optional[str]' = None) -> 'Method':
"""Backport support for original codes.
Delegates to :meth:`~pcapkit.corekit.enum.EnumLookup.get` for the
lookup itself, per GitHub issue :issue:`908`: the previous override checked
only ``_member_map_`` (names), never ``_value2member_map_``
(values), so the two IANA methods whose member *name* differs from
their *value* -- ``BASELINE_CONTROL`` / ``'BASELINE-CONTROL'`` and
``VERSION_CONTROL`` / ``'VERSION-CONTROL'``, a hyphen being unusable
in a Python identifier -- failed to resolve through ``get`` even
though :meth:`_missing_` (and so the constructor) already found
them via that same value-side lookup.
The base is a :class:`classmethod`
(:meth:`~pcapkit.corekit.enum.EnumLookup.get`), and a zero-argument
``super()`` needs a first argument to bind, so this override had to
move from :class:`staticmethod` to :class:`classmethod` to delegate
at all -- see GitHub issue :issue:`908`'s own correction of the fix it
originally proposed. Callers are unaffected by the switch itself:
``Method.get('X')`` binds identically either way.
``default`` keeps its existing meaning here rather than adopting
the base's ``NO_DEFAULT`` sentinel and its value-only fallback --
widening it would be caller-visible, since the base's ``default``
must already name a *registered* value (resolved through
``_value2member_map_``), while this override's ``default`` instead
supplies the *value* of a freshly minted unregistered member.
Nothing in this tree calls ``get`` with a non-``None`` ``default``
to notice today, but widening the signature is a separate change
from this defect and stays out of scope.
Args:
key: Key to get enum item. Looked up case-**sensitively**,
per :rfc:`9110#section-9.1` -- the method token is
case-sensitive, unlike :meth:`~pcapkit.const.ftp.command.
Command.get`'s equivalent override, which stays
case-insensitive because :rfc:`959#section-5` says FTP
command codes are not. Checked against both member names
and values, via the base's own precedence -- name before
value -- so a value-only match such as
``'BASELINE-CONTROL'`` now resolves too, closing GitHub
issue :issue:`908`.
default: Value for the unregistered member built when ``key``
matches neither a name nor a value. ``None``, the
default, uses ``key`` itself -- unchanged from before
GitHub issue :issue:`908`.
Raises:
ValueError: If ``key`` is not a :class:`str`. This reaches the
caller from the base's non-``str`` branch, which calls
``cls(key)`` and so ``_missing_``, and is **not** caught by
the ``except KeyError`` fallback below -- only a failed
*name* lookup is. The non-``str`` surface therefore moved
with GitHub issue :issue:`908`: ``get(42)`` and ``get(None)`` used
to raise :exc:`AttributeError` from ``key.upper()``, and
``get(b'GET')`` used to *return* a member whose name and
value were both the :class:`bytes` object. Raising is the
intended behaviour -- a ``bytes``-valued ``Method`` is not a
thing this registry should hand back -- but it is a change,
so it is stated rather than left to be discovered.
:meta private:
"""
try:
return super().get(key)
except KeyError:
# NOTE: the value is ``default`` if the caller supplied one, or
# else ``key`` exactly as given -- never its upper-cased form --
# so an unregistered member's value is the caller's own casing,
# the same convention :meth:`_unregistered_member` documents and
# :class:`~pcapkit.const.ftp.command.FEATCode` already followed.
# The name is always the canonical upper-case form, matching
# :meth:`_missing_` and every registered member's own name. Two
# calls naming the same method in different case, e.g.
# ``get('frob')`` and ``get('FROB')``, therefore build results
# that are *not* equal -- each is exactly what its own caller
# passed, per GitHub issue #860's conversion away from minting.
return cls._unregistered_member(default if default is not None else key, key.upper())
[docs]
@classmethod
def _missing_(cls, value: 'str') -> 'Method':
"""Lookup function used when value is not found.
Args:
value: Value to get enum item. Matched case-insensitively against
the canonical upper-case member names -- deliberately unlike
:meth:`get`, which is case-**sensitive** per
:rfc:`9110#section-9.1`. The split was ruled deliberate on
GitHub issue :issue:`896`; GitHub issue :issue:`908` is the pointer between
the two, so a reader of this one file is not left with two
contradictory rationales and nothing tying them together.
"""
if not isinstance(value, str):
raise ValueError(f'{value!r} is not a valid {cls.__name__}')
name = value.upper()
if name in cls._member_map_:
return cls._member_map_[name] # type: ignore[return-value]
return cls._unregistered_member(value, name)