|
2 | 2 |
|
3 | 3 | del absolute_import, division, print_function |
4 | 4 |
|
| 5 | +# Custom families (other packages can define custom families) |
| 6 | +MAIN_FAMILY = object() # This family will be used as a fallback |
| 7 | +CPP_FAMILY = object() |
5 | 8 |
|
6 | | -def cpp_module(cls): |
| 9 | +# These are not exported because custom user classes do not need to |
| 10 | +# add to the original families, they should make their own. |
| 11 | + |
| 12 | + |
| 13 | +def set_module(name): |
| 14 | + """ |
| 15 | + Set the __module__ attribute on a class. Very |
| 16 | + similar to numpy.core.overrides.set_module. |
| 17 | + """ |
| 18 | + |
| 19 | + def add_module(cls): |
| 20 | + cls.__module__ = name |
| 21 | + return cls |
| 22 | + |
| 23 | + return add_module |
| 24 | + |
| 25 | + |
| 26 | +def set_family(family): |
7 | 27 | """ |
8 | | - Simple decorator to declare a class to be in the cpp |
9 | | - module. |
| 28 | + Decorator to set the family of a class. |
10 | 29 | """ |
11 | | - cls._cpp_module = True |
12 | | - return cls |
13 | 30 |
|
| 31 | + def add_family(cls): |
| 32 | + cls._family = family |
| 33 | + return cls |
| 34 | + |
| 35 | + return add_family |
14 | 36 |
|
15 | | -def register(*args): |
| 37 | + |
| 38 | +def register(cpp_types=None): |
16 | 39 | """ |
17 | 40 | Decorator to register a C++ type to a Python class. |
18 | 41 | Each class given will be added to a lookup list "_types" |
19 | | - that cast knows about. |
| 42 | + that cast knows about. It should also part of a "family", |
| 43 | + and any class in a family will cast to the same family. |
| 44 | + See set_family. |
| 45 | +
|
| 46 | + For example, internally this call: |
| 47 | +
|
| 48 | + ax = hist._axis(0) |
| 49 | +
|
| 50 | + which will get a raw C++ object and need to cast it to a Python |
| 51 | + wrapped object. There will be at least two candidates (users |
| 52 | + could add more): MAIN_FAMILY and CPP_FAMILY. Cast will use the |
| 53 | + parent class's family to return the correct family. If the |
| 54 | + requested family is not found, then the regular family is the |
| 55 | + fallback. |
20 | 56 |
|
21 | 57 | This decorator, like other decorators in boost-histogram, |
22 | 58 | is safe for pickling since it does not replace the |
23 | 59 | original class. |
| 60 | +
|
| 61 | + If nothing or an empty set is passed, this will ensure that this |
| 62 | + class is not selected during the cast process. This can be |
| 63 | + used for simple renamed classes that inject warnings, etc. |
24 | 64 | """ |
25 | 65 |
|
26 | 66 | def add_registration(cls): |
27 | | - if len(args) == 0: |
| 67 | + if cpp_types is None or len(cpp_types) == 0: |
28 | 68 | cls._types = set() |
29 | 69 | return cls |
30 | 70 |
|
31 | 71 | if not hasattr(cls, "_types"): |
32 | 72 | cls._types = set() |
33 | 73 |
|
34 | | - for cpp_type in args: |
| 74 | + for cpp_type in cpp_types: |
35 | 75 | if cpp_type in cls._types: |
36 | 76 | raise TypeError("You are trying to register {} again".format(cpp_type)) |
37 | 77 |
|
38 | 78 | cls._types.add(cpp_type) |
39 | | - if not hasattr(cls, "_cpp_module"): |
40 | | - cls._cpp_module = False |
41 | | - return cls |
| 79 | + |
| 80 | + return cls |
42 | 81 |
|
43 | 82 | return add_registration |
44 | 83 |
|
45 | 84 |
|
46 | | -def cast(cpp_object, parent_class, cpp=False, is_class=False): |
| 85 | +def _cast_make_object(canidate_class, cpp_object, is_class): |
| 86 | + "Make an object for cast" |
| 87 | + if is_class: |
| 88 | + return canidate_class |
| 89 | + elif hasattr(canidate_class, "_convert_cpp"): |
| 90 | + return canidate_class._convert_cpp(cpp_object) |
| 91 | + else: |
| 92 | + return canidate_class(cpp_object) |
| 93 | + |
| 94 | + |
| 95 | +def cast(self, cpp_object, parent_class): |
47 | 96 | """ |
48 | 97 | This converts a C++ object into a Python object. |
49 | | - This takes the parent Python class, and an optional |
50 | | - base parameter, which will only return classes that |
51 | | - are in the base module. |
52 | | -
|
53 | | - Can also return the class directly with find_class=True. |
| 98 | + This takes the parent object, the C++ object, |
| 99 | + the Python class. If a class is passed in instead of |
| 100 | + an object, this will return a class instead. The parent |
| 101 | + object (self) can be either a registered class or an |
| 102 | + instance of a registered class. |
54 | 103 |
|
55 | 104 | If a class does not support direction conversion in |
56 | 105 | the constructor, it should have _convert_cpp class |
57 | 106 | method instead. |
58 | 107 |
|
59 | | - cpp setting must match the register setting. |
| 108 | + Example: |
| 109 | +
|
| 110 | + cast(self, hist.cpp_axis(), Axis) |
| 111 | + # -> returns Regular(...) if regular axis, etc. |
| 112 | +
|
| 113 | + If self is None, just use the MAIN_FAMILY. |
60 | 114 | """ |
61 | | - cpp_class = cpp_object if is_class else cpp_object.__class__ |
| 115 | + if self is None: |
| 116 | + family = MAIN_FAMILY |
| 117 | + else: |
| 118 | + family = self._family |
| 119 | + |
| 120 | + # Convert objects to classes, and remember if we did so |
| 121 | + if isinstance(cpp_object, type): |
| 122 | + is_class = True |
| 123 | + cpp_class = cpp_object |
| 124 | + else: |
| 125 | + is_class = False |
| 126 | + cpp_class = cpp_object.__class__ |
| 127 | + |
| 128 | + # Remember the fallback class if a class in the same family does not exist |
| 129 | + fallback_class = None |
62 | 130 |
|
63 | 131 | for canidate_class in _walk_subclasses(parent_class): |
| 132 | + # If a class was registered with this c++ type |
64 | 133 | if ( |
65 | 134 | hasattr(canidate_class, "_types") |
66 | 135 | and cpp_class in canidate_class._types |
67 | | - and canidate_class._cpp_module == cpp |
| 136 | + and hasattr(canidate_class, "_family") |
68 | 137 | ): |
69 | | - if is_class: |
70 | | - return canidate_class |
71 | | - elif hasattr(canidate_class, "_convert_cpp"): |
72 | | - return canidate_class._convert_cpp(cpp_object) |
73 | | - else: |
74 | | - return canidate_class(cpp_object) |
| 138 | + # Return immediately if the family is right |
| 139 | + if canidate_class._family is family: |
| 140 | + return _cast_make_object(canidate_class, cpp_object, is_class) |
| 141 | + |
| 142 | + # Or remember the class if it was from the main family |
| 143 | + if canidate_class._family is MAIN_FAMILY: |
| 144 | + fallback_class = canidate_class |
| 145 | + |
| 146 | + # If no perfect match was registered, return the main family |
| 147 | + if fallback_class is not None: |
| 148 | + return _cast_make_object(fallback_class, cpp_object, is_class) |
| 149 | + |
75 | 150 | raise TypeError( |
76 | 151 | "No conversion to {0} from {1} found.".format(parent_class.__name__, cpp_object) |
77 | 152 | ) |
|
0 commit comments