Skip to content

Commit 593554f

Browse files
committed
Make the method of extending enums a bit more sane
This new way doesn't involve nearly as much hackery. However, enum extensions must be in a separate submodule (a module under `gssapi.raw._enum_extensions`), and should be called the same thing as the corresponding extension (due to the way that `setup.py` is written). Additionally, the import of the aforementioned submodules *must* happen before importing any other python-gssapi modules (hence they have to happen at the top of 'gssapi/raw/__init__.py'). This limits the ability to allow for third parties writing extension bindings (although that's probably not something that should be done). Finally, due to the way it works, each extenable enum must be decorated beforehand, and must not share a name with any other extendable enum.
1 parent 8cd2806 commit 593554f

File tree

11 files changed

+94
-164
lines changed

11 files changed

+94
-164
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
dist
1212
gssapi/**/*.c
1313
docs/build
14+
__dont_use_cython__.txt

gssapi/raw/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
import pkgutil
2+
import importlib
3+
4+
from gssapi.raw import _enum_extensions
5+
6+
# NB(directxman12): the enum extensions must be imported BEFORE ANYTHING ELSE!
7+
for modinf in pkgutil.iter_modules(_enum_extensions.__path__):
8+
name = modinf[1]
9+
importlib.import_module('{0}._enum_extensions.{1}'.format(__name__, name))
10+
11+
del pkgutil
12+
del importlib
13+
114
from gssapi.raw.creds import * # noqa
215
from gssapi.raw.message import * # noqa
316
from gssapi.raw.misc import * # noqa
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from enum import EnumMeta
2+
3+
4+
_extra_values = {}
5+
6+
7+
def register_value(cl_str, name, value):
8+
_extra_values[cl_str] = _extra_values.get(cl_str, {})
9+
_extra_values[cl_str][name] = value
10+
11+
12+
class ExtendableEnum(EnumMeta):
13+
def __new__(metacl, name, bases, classdict):
14+
extra_vals = _extra_values.get(name)
15+
16+
if extra_vals is not None:
17+
for extra_name, extra_val in list(extra_vals.items()):
18+
if extra_name in classdict:
19+
raise AttributeError(
20+
"Enumeration extensions cannot override existing "
21+
"enumeration members")
22+
else:
23+
classdict[extra_name] = extra_val
24+
25+
return super(ExtendableEnum, metacl).__new__(metacl, name,
26+
bases, classdict)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from gssapi.raw.cython_types cimport OM_uint32
2+
3+
import gssapi.raw._enum_extensions as ext_registry
4+
5+
6+
cdef extern from "python_gssapi_ext.h":
7+
OM_uint32 GSS_C_DCE_STYLE
8+
OM_uint32 GSS_C_IDENTIFY_FLAG
9+
OM_uint32 GSS_C_EXTENDED_ERROR_FLAG
10+
11+
ext_registry.register_value('RequirementFlag', 'dce_style', GSS_C_DCE_STYLE)
12+
ext_registry.register_value('RequirementFlag', 'identify', GSS_C_IDENTIFY_FLAG)
13+
ext_registry.register_value('RequirementFlag', 'extended_error',
14+
GSS_C_EXTENDED_ERROR_FLAG)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from gssapi.raw.cython_types cimport OM_uint32
2+
3+
from gssapi.raw import _enum_extensions as ext_registry
4+
5+
6+
cdef extern from "python_gssapi_ext.h":
7+
OM_uint32 GSS_IOV_BUFFER_TYPE_MIC_TOKEN
8+
9+
ext_registry.register_value('IOVBufferType', 'mic_token',
10+
GSS_IOV_BUFFER_TYPE_MIC_TOKEN)

gssapi/raw/ext_dce.pyx

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ from libc.string cimport memcpy
66
from gssapi.raw.cython_types cimport *
77
from gssapi.raw.sec_contexts cimport SecurityContext
88

9-
from gssapi.raw.misc import GSSError, _EnumExtension
9+
from gssapi.raw.misc import GSSError
1010
from gssapi.raw import types as gssapi_types
1111
from gssapi.raw.named_tuples import IOVUnwrapResult, WrapResult, UnwrapResult
1212
from collections import namedtuple, Sequence
1313

1414
from enum import IntEnum
1515
import six
16+
from gssapi.raw._enum_extensions import ExtendableEnum
1617

1718
cdef extern from "python_gssapi_ext.h":
1819
# NB(directxman12): this wiki page has a different argument order
@@ -62,12 +63,10 @@ cdef extern from "python_gssapi_ext.h":
6263
OM_uint32 GSS_IOV_BUFFER_FLAG_ALLOCATE
6364
OM_uint32 GSS_IOV_BUFFER_FLAG_ALLOCATED
6465

65-
OM_uint32 GSS_C_DCE_STYLE
66-
OM_uint32 GSS_C_IDENTIFY_FLAG
67-
OM_uint32 GSS_C_EXTENDED_ERROR_FLAG
66+
# a few more are in the enum extension file
6867

6968

70-
class IOVBufferType(IntEnum):
69+
class IOVBufferType(IntEnum, metaclass=ExtendableEnum):
7170
"""
7271
IOV Buffer Types
7372
@@ -88,15 +87,6 @@ class IOVBufferType(IntEnum):
8887
sign_only = GSS_IOV_BUFFER_TYPE_SIGN_ONLY
8988

9089

91-
@six.add_metaclass(_EnumExtension)
92-
class RequirementFlag(object):
93-
__base__ = gssapi_types.RequirementFlag
94-
95-
dce_style = GSS_C_DCE_STYLE
96-
identify = GSS_C_IDENTIFY_FLAG
97-
extended_error = GSS_C_EXTENDED_ERROR_FLAG
98-
99-
10090
IOVBuffer = namedtuple('IOVBuffer', ['type', 'allocate', 'value'])
10191

10292

gssapi/raw/ext_iov_mic.pyx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ from gssapi.raw.cython_types cimport *
44
from gssapi.raw.sec_contexts cimport SecurityContext
55
from gssapi.raw.ext_dce cimport IOV, gss_iov_buffer_desc
66

7-
from gssapi.raw.misc import GSSError, _EnumExtension
8-
from gssapi.raw import ext_dce
7+
from gssapi.raw.misc import GSSError
8+
from gssapi.raw.ext_dce import IOVBufferType
99

10-
import six
1110

1211
cdef extern from "python_gssapi_ext.h":
1312
OM_uint32 gss_get_mic_iov(OM_uint32 *min_stat, gss_ctx_id_t context_handle,
@@ -26,13 +25,7 @@ cdef extern from "python_gssapi_ext.h":
2625
gss_iov_buffer_desc *iov,
2726
int iov_count) nogil
2827

29-
OM_uint32 GSS_IOV_BUFFER_TYPE_MIC_TOKEN
30-
31-
32-
@six.add_metaclass(_EnumExtension)
33-
class IOVBufferType(object):
34-
__base__ = ext_dce.IOVBufferType
35-
mic_token = GSS_IOV_BUFFER_TYPE_MIC_TOKEN
28+
# more in the enum extension file
3629

3730

3831
IOV.AUTO_ALLOC_BUFFERS.add(IOVBufferType.mic_token)

gssapi/raw/misc.pyx

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ from gssapi.raw.oids cimport OID
99

1010
from gssapi.raw.types import MechType
1111

12-
from enum import EnumMeta, Enum
13-
1412

1513
cdef extern from "python_gssapi.h":
1614
OM_uint32 gss_display_status(OM_uint32 *minor_status,
@@ -328,64 +326,3 @@ class GSSError(Exception, metaclass=GSSErrorRegistry):
328326
maj_str=maj_str,
329327
min_stat=self.min_code,
330328
min_str=min_str)
331-
332-
333-
# WARNING: FOR YOUR OWN PERSONAL SANITY, DO NOT TRY THIS AT HOME
334-
# This allows things like extensions declaring their own RequirementFlags
335-
# without having to worry about import order. There *might* be a cleaner
336-
# way to do this, but most of the ways probably exploit the interals of Enum
337-
class _EnumExtension(EnumMeta):
338-
def __new__(metacls, name, bases, classdict):
339-
# find the base class that this overrides
340-
341-
base_attr = classdict.get('__base__', None)
342-
343-
if len(bases) != 1 or bases[0] is not object or base_attr is None:
344-
raise TypeError("Enumeration extensions must be created as "
345-
"`ClassName(object)` and must contain the "
346-
"`__base__` attribute")
347-
348-
adds_to = base_attr
349-
del classdict['__base__']
350-
351-
bases = adds_to.__bases__
352-
353-
# we need to have the same Enum type
354-
if not issubclass(adds_to, Enum):
355-
raise TypeError("Enumeration extensions must extend a subtype "
356-
"of Enum or directly sublcass object")
357-
358-
# roughly preserve insertion order in Python 3.4+
359-
# (actually preserving order would require using parts of the Enum
360-
# implementation that aren't part of the spec)
361-
old_classdict = classdict
362-
classdict = metacls.__prepare__(name, bases)
363-
364-
# NB(directxman12): we can't use update, because that doesn't
365-
# trigger __setitem__, which thus won't update the _EnumDict correctly
366-
base_members = adds_to.__members__
367-
for k, v in base_members.items():
368-
classdict[k] = v.value
369-
370-
for k, v in old_classdict.items():
371-
if k in base_members:
372-
raise AttributeError("Enumeration extensions cannot override "
373-
"existing enumeration members")
374-
375-
classdict[k] = v
376-
377-
res = super(_EnumExtension, metacls).__new__(metacls, name,
378-
bases, classdict)
379-
380-
# replace the old with the new
381-
for k, v in res.__dict__.items():
382-
if (k not in ('__module__', '__qualname__', '__doc__') and
383-
k in adds_to.__dict__):
384-
setattr(adds_to, k, v)
385-
386-
# preserve enum semantics
387-
for member in adds_to.__members__.values():
388-
member.__class__ = adds_to
389-
390-
# always return the original
391-
return adds_to

gssapi/raw/types.pyx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ from gssapi.raw.cython_types cimport *
44
from gssapi.raw.cython_converters cimport c_make_oid
55
from gssapi.raw.oids cimport OID
66

7+
from gssapi.raw._enum_extensions import ExtendableEnum
8+
79
from enum import IntEnum
810
import collections
911
import copy
1012
import numbers
1113
import operator
14+
import six
1215

1316

1417
class NameType(object):
@@ -36,7 +39,7 @@ class NameType(object):
3639
# mech-specific name types are added automatically on import
3740

3841

39-
class RequirementFlag(IntEnum):
42+
class RequirementFlag(IntEnum, metaclass=ExtendableEnum):
4043
"""
4144
GSSAPI Requirement Flags
4245
@@ -58,7 +61,7 @@ class RequirementFlag(IntEnum):
5861
transferable = GSS_C_TRANS_FLAG
5962

6063

61-
class AddressType(IntEnum):
64+
class AddressType(IntEnum, metaclass=ExtendableEnum):
6265
"""
6366
GSSAPI Channel Bindings Address Types
6467

gssapi/tests/test_raw.py

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import unittest
66

77
import should_be.all # noqa
8-
from enum import IntEnum
9-
import six
108

119
import gssapi.raw as gb
1210
import gssapi.raw.misc as gbmisc
@@ -643,78 +641,6 @@ def test_xor_set(self):
643641
fset3.should_include(gb.RequirementFlag.out_of_sequence_detection)
644642

645643

646-
class TestEnumExtension(unittest.TestCase):
647-
def setUp(self):
648-
class ExtendedEnum(IntEnum):
649-
"""some docs"""
650-
a = 1
651-
b = 2
652-
c = 3
653-
654-
class OtherEnum(IntEnum):
655-
a = 1
656-
b = 2
657-
c = 3
658-
659-
self._ext_enum = ExtendedEnum
660-
self._plain_enum = OtherEnum
661-
662-
def test_same_dict(self):
663-
@six.add_metaclass(gbmisc._EnumExtension)
664-
class ExtendedEnum(object):
665-
__base__ = self._ext_enum
666-
d = 4
667-
e = 5
668-
669-
self._ext_enum.__dict__.keys().should_be(
670-
self._plain_enum.__dict__.keys())
671-
672-
def test_members_copy_mult(self):
673-
@six.add_metaclass(gbmisc._EnumExtension)
674-
class ExtendedEnum(object):
675-
__base__ = self._ext_enum
676-
d = 4
677-
678-
@six.add_metaclass(gbmisc._EnumExtension)
679-
class ExtendedEnum2(object):
680-
__base__ = self._ext_enum
681-
e = 5
682-
683-
self._ext_enum.__members__.should_include(
684-
['a', 'b', 'c', 'd', 'e'])
685-
686-
self._ext_enum.a.should_be(1)
687-
self._ext_enum.b.should_be(2)
688-
self._ext_enum.c.should_be(3)
689-
self._ext_enum.d.should_be(4)
690-
self._ext_enum.e.should_be(5)
691-
692-
def test_all_class_are_same(self):
693-
@six.add_metaclass(gbmisc._EnumExtension)
694-
class ExtendedEnum(object):
695-
__base__ = self._ext_enum
696-
d = 4
697-
698-
assert ExtendedEnum is self._ext_enum
699-
700-
def test_members_are_still_instance(self):
701-
@six.add_metaclass(gbmisc._EnumExtension)
702-
class EnumExt(object):
703-
__base__ = self._ext_enum
704-
d = 4
705-
706-
self._ext_enum.a.should_be_a(self._ext_enum)
707-
self._ext_enum.d.should_be_a(self._ext_enum)
708-
709-
def test_doc_is_preserved(self):
710-
@six.add_metaclass(gbmisc._EnumExtension)
711-
class ExtendedEnum(object):
712-
__base__ = self._ext_enum
713-
d = 4
714-
715-
self._ext_enum.__doc__.should_be('some docs')
716-
717-
718644
class TestInitContext(_GSSAPIKerberosTestCase):
719645
def setUp(self):
720646
self.target_name = gb.import_name(TARGET_SERVICE_NAME,

setup.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,23 @@ def main_file(module):
118118
sources=['gssapi/raw/%s.%s' % (module, SOURCE_EXT)])
119119

120120

121+
ENUM_EXTS = []
122+
123+
121124
def extension_file(module, canary):
122125
if ENABLE_SUPPORT_DETECTION and not hasattr(GSSAPI_LIB, canary):
123126
return None
124127
else:
128+
enum_ext_path = 'gssapi/raw/_enum_extensions/ext_%s.%s' % (module,
129+
SOURCE_EXT)
130+
if os.path.exists(enum_ext_path):
131+
ENUM_EXTS.append(
132+
Extension('gssapi.raw._enum_extensions.ext_%s' % module,
133+
extra_link_args=link_args,
134+
extra_compile_args=compile_args,
135+
sources=[enum_ext_path],
136+
include_dirs=['gssapi/raw/']))
137+
125138
return Extension('gssapi.raw.ext_%s' % module,
126139
extra_link_args=link_args,
127140
extra_compile_args=compile_args,
@@ -142,6 +155,9 @@ def gssapi_modules(lst):
142155
sources=['gssapi/raw/mech_%s.%s' % (mech,
143156
SOURCE_EXT)]))
144157

158+
# add in any present enum extension files
159+
res.extend(ENUM_EXTS)
160+
145161
if SOURCE_EXT == 'pyx':
146162
res = cythonize(res)
147163

@@ -157,7 +173,8 @@ def gssapi_modules(lst):
157173
version='1.0.0',
158174
author='The Python GSSAPI Team',
159175
author_email='[email protected]',
160-
packages=['gssapi', 'gssapi.raw', 'gssapi.tests'],
176+
packages=['gssapi', 'gssapi.raw', 'gssapi.raw._enum_extensions',
177+
'gssapi.tests'],
161178
description='Python GSSAPI Wrapper',
162179
long_description=long_desc,
163180
license='LICENSE.txt',

0 commit comments

Comments
 (0)