Skip to content Skip to sidebar Skip to footer

Best Way To Extend Httpstatus With Custom Value

I am extending HTTPStatus with a custom value: from http import HTTPStatus HTTPStatus.MY_CUSTOM_SERVICE_TIMEOUT = 573 I am wondering why I do not see that value when inspecting H

Solution 1:

That's because http.HTTPStatus is an Enum and Python doesn't really, trully have Enum as a generic type (which is why you can do what you're doing - languages that actually recognize Enum as something special wouldn't let you mess with it like this in general). Of course, Python does its best to make Enums behave as they should (immutable, iterable, mappable...).

There is actually a collections.OrderedDict underneath (Enum._member_map_) that gets created when you create a new Enum type - it reads in the members, aliases the duplicates and adds an additional value -> enum member map as Enum._value2member_map_ (all of that is done by the enum.EnumMeta metaclass). When you dir() an enum - you get that map (or more precisely, the enum names list available in the Enum._member_names_ list) and any changes you may have applied at runtime doesn't count (otherwise it wouldn't be immutable). In other words, when you do HTTPStatus.MY_CUSTOM_SERVICE_TIMEOUT = 573 you're not extending the Enum, you're just adding a dynamic property to the Enum object in question.

You should extend your Enum types the regular, OOP way if you want to add custom members... except Python won't let you do this either. So if you really insist doing it run-time you can, kind of, hack the internal structure to make Python believe your enum value was there all along:

# HERE BE DRAGONS!# DO NOT do this unless you absolutely have to.from http import HTTPStatus

defadd_http_status(name, value, phrase, description=''):
    # call our new member factory, it's essentially the `HTTPStatus.__new__` method
    new_status = HTTPStatus.__new_member__(HTTPStatus, value, phrase, description)
    new_status._name_ = name  # store the enum's member internal name
    new_status.__objclass__ = HTTPStatus.__class__  # store the enum's member parent classsetattr(HTTPStatus, name, new_status)  # add it to the global HTTPStatus namespace
    HTTPStatus._member_map_[name] = new_status  #  add it to the name=>member map
    HTTPStatus._member_names_.append(name)  # append the names so it appears in __members__
    HTTPStatus._value2member_map_[value] = new_status  # add it to the value=>member map

And now you can 'really' extend the HTTPStatus at runtime:

try:
    print(HTTPStatus(573))
except ValueError as e:
    print(e)
print("MY_CUSTOM_SERVICE_TIMEOUT" in dir(HTTPStatus))
add_http_status("MY_CUSTOM_SERVICE_TIMEOUT", 573, "Custom service timeout")
print("MY_CUSTOM_SERVICE_TIMEOUT" in dir(HTTPStatus))
print(HTTPStatus(573))
print(HTTPStatus.MY_CUSTOM_SERVICE_TIMEOUT.value)
print(HTTPStatus(573).phrase)

Which will give you:

573 is not a valid HTTPStatus
False
True
HTTPStatus.MY_CUSTOM_SERVICE_TIMEOUT
573
Custom service timeout

Keep in mind that this code doesn't handle aliasing, de-duplication and other nice things that you should absolutely be doing if you want to arbitrary extend an Enum so don't use duplicate or invalid values or you'll break it (in a sense it won't work as expected afterwards). Check the additional steps taken in enum.EnumMeta.__new__() to ensure its validity.

Solution 2:

Use the extend_enum function from the aenum library:

import aenum
import http

aenum.extend_enum(http.HTTPStatus, 'CustomTimeout', 537, 'more helpful phrase here')

Which results in:

>>> list(http.HTTPStatus)
[<HTTPStatus.CONTINUE: 100>,
...,
<HTTPStatus.CustomTimeout: 537>]

Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

Post a Comment for "Best Way To Extend Httpstatus With Custom Value"