|
| 1 | +{% extends '_base.py.j2' %} |
| 2 | + |
| 3 | +{% block content %} |
| 4 | +{% if opts.lazy_import -%} {# lazy import #} |
| 5 | +import importlib |
| 6 | +import re |
| 7 | +import sys |
| 8 | + |
| 9 | +from itertools import chain |
| 10 | + |
| 11 | +def to_snake_case(s: str) -> str: |
| 12 | + s = re.sub(r'(?<=[a-z])([A-Z])', r'_\1', str(s)) |
| 13 | + s = re.sub(r'(?<=[^_])([A-Z])(?=[a-z])', r'_\1', s) |
| 14 | + |
| 15 | + # Numbers are a weird case; the goal is to spot when they _start_ |
| 16 | + # some kind of name or acronym (e.g. 2FA, 3M). |
| 17 | + # |
| 18 | + # Find cases of a number preceded by a lower-case letter _and_ |
| 19 | + # followed by at least two capital letters or a single capital and |
| 20 | + # end of string. |
| 21 | + s = re.sub(r'(?<=[a-z])(\d)(?=[A-Z]{2})', r'_\1', s) |
| 22 | + s = re.sub(r'(?<=[a-z])(\d)(?=[A-Z]$)', r'_\1', s) |
| 23 | + |
| 24 | + return s.lower() |
| 25 | + |
| 26 | + |
| 27 | +def from_snake_case(s): |
| 28 | + _CHARS_TO_UPCASE_RE = re.compile(r'(?:_|^)([a-z])') |
| 29 | + return _CHARS_TO_UPCASE_RE.sub(lambda m: m.group().replace('_', '').upper(), s) |
| 30 | + |
| 31 | + |
| 32 | +if sys.version_info < (3, 7): |
| 33 | + raise ImportError('This module requires Python 3.7 or later.') # pragma: NO COVER |
| 34 | + |
| 35 | +_lazy_name_to_package_map = { |
| 36 | + 'types': '{% if api.naming.module_namespace %}{{ api.naming.module_namespace|join(".") }}.{% endif -%}{{ api.naming.versioned_module_name }}.types', |
| 37 | + {%- for service in api.services.values()|sort(attribute='name')|unique(attribute='name') if service.meta.address.subpackage == api.subpackage_view %} |
| 38 | + '{{ service.client_name|snake_case }}': '{% if api.naming.module_namespace %}{{ api.naming.module_namespace|join(".") }}.{% endif -%}{{ api.naming.versioned_module_name }}.services.{{ service.name|snake_case }}.client', |
| 39 | + '{{ service.transport_name|snake_case }}': '{% if api.naming.module_namespace %}{{ api.naming.module_namespace|join(".") }}.{% endif -%}{{ api.naming.versioned_module_name }}.services.{{ service.name|snake_case }}.transports.base', |
| 40 | + '{{ service.grpc_transport_name|snake_case }}': '{% if api.naming.module_namespace %}{{ api.naming.module_namespace|join(".") }}.{% endif -%}{{ api.naming.versioned_module_name }}.services.{{ service.name|snake_case }}.transports.grpc', |
| 41 | + {%- endfor %} |
| 42 | +} |
| 43 | + |
| 44 | +_lazy_type_to_package_map = { |
| 45 | +{%- filter sort_lines %} |
| 46 | +{%- for proto in api.protos.values() if proto.meta.address.subpackage == api.subpackage_view %}{%- for message in proto.messages.values() %} |
| 47 | + '{{ message.name }}':'{% if api.naming.module_namespace %}{{ api.naming.module_namespace|join(".") }}.{% endif -%}{{ api.naming.versioned_module_name }}.types.{{ proto.module_name }}', |
| 48 | +{%- endfor %} |
| 49 | +{%- for enum in proto.enums.values() %} |
| 50 | + '{{ enum.name }}': '{% if api.naming.module_namespace %}{{ api.naming.module_namespace|join(".") }}.{% endif -%}{{ api.naming.versioned_module_name }}.types.{{ proto.module_name }}', |
| 51 | +{%- endfor %}{%- endfor %}{%- endfilter %} |
| 52 | +} |
| 53 | + |
| 54 | +# Background on how this behaves: https://www.python.org/dev/peps/pep-0562/ |
| 55 | +def __getattr__(name): # Requires Python >= 3.7 |
| 56 | + if name == '__all__': |
| 57 | + all_names = globals()['__all__'] = sorted( |
| 58 | + chain( |
| 59 | + (from_snake_case(k) for k in _lazy_name_to_package_map if k != 'types'), |
| 60 | + _lazy_type_to_package_map, |
| 61 | + ['types'], |
| 62 | + ) |
| 63 | + ) |
| 64 | + return all_names |
| 65 | + elif name.endswith('Transport'): |
| 66 | + module = __getattr__(to_snake_case(name)) |
| 67 | + sub_mod_class = getattr(module, name) |
| 68 | + klass = type(name, (sub_mod_class,), {'__doc__': sub_mod_class.__doc__}) |
| 69 | + globals()[name] = klass |
| 70 | + return klass |
| 71 | + elif name.endswith('Client'): |
| 72 | + module = __getattr__(to_snake_case(name)) |
| 73 | + sub_mod_class = getattr(module, name) |
| 74 | + klass = type( |
| 75 | + name, |
| 76 | + (sub_mod_class,), |
| 77 | + {'__doc__': sub_mod_class.__doc__} |
| 78 | + ) |
| 79 | + globals()[name] = klass |
| 80 | + return klass |
| 81 | + elif name in _lazy_name_to_package_map: |
| 82 | + module = importlib.import_module(f'{_lazy_name_to_package_map[name]}') |
| 83 | + globals()[name] = module |
| 84 | + return module |
| 85 | + elif name in _lazy_type_to_package_map: |
| 86 | + module = importlib.import_module(f'{_lazy_type_to_package_map[name]}') |
| 87 | + klass = getattr(module, name) |
| 88 | + {# new_klass = type(name, (klass,), {'__doc__': klass.__doc__}) #} |
| 89 | + globals()[name] = klass |
| 90 | + return klass |
| 91 | + else: |
| 92 | + raise AttributeError(f'unknown sub-module {name!r}.') |
| 93 | + |
| 94 | + |
| 95 | +def __dir__(): |
| 96 | + return globals().get('__all__') or __getattr__('__all__') |
| 97 | +{% else -%} {# do not use lazy import #} |
| 98 | +{# Import subpackages. -#} |
| 99 | +{% for subpackage in api.subpackages.keys() -%} |
| 100 | +from . import {{ subpackage }} |
| 101 | +{% endfor -%} |
| 102 | + |
| 103 | +{# Import services for this package. -#} |
| 104 | +{% filter sort_lines -%} |
| 105 | +{% for service in api.services.values()|sort(attribute='name') |
| 106 | + if service.meta.address.subpackage == api.subpackage_view -%} |
| 107 | +from .services.{{ service.name|snake_case }} import {{ service.client_name }} |
| 108 | +{% endfor -%} |
| 109 | +{% endfilter -%} |
| 110 | + |
| 111 | +{# Import messages and enums from each proto. |
| 112 | + It is safe to import all of the messages into the same namespace here, |
| 113 | + because protocol buffers itself enforces selector uniqueness within |
| 114 | + a proto package. |
| 115 | +-#} |
| 116 | +{% filter sort_lines -%} |
| 117 | +{% for proto in api.protos.values() |
| 118 | + if proto.meta.address.subpackage == api.subpackage_view -%} |
| 119 | +{% for message in proto.messages.values() -%} |
| 120 | +from .types.{{ proto.module_name }} import {{ message.name }} |
| 121 | +{% endfor -%} |
| 122 | +{% for enum in proto.enums.values() -%} |
| 123 | +from .types.{{ proto.module_name }} import {{ enum.name }} |
| 124 | +{% endfor -%} |
| 125 | +{% endfor -%} |
| 126 | +{% endfilter %} |
| 127 | + |
| 128 | +{# Define __all__. |
| 129 | + This requires the full set of imported names, so we iterate over |
| 130 | + them again. |
| 131 | +-#} |
| 132 | +__all__ = ( |
| 133 | + {%- filter sort_lines %} |
| 134 | + {%- for subpackage in api.subpackages.keys() %} |
| 135 | + '{{ subpackage }}', |
| 136 | + {%- endfor %} |
| 137 | + {%- for service in api.services.values() |
| 138 | + if service.meta.address.subpackage == api.subpackage_view %} |
| 139 | + '{{ service.client_name }}', |
| 140 | + {%- endfor %} |
| 141 | + {%- for proto in api.protos.values() |
| 142 | + if proto.meta.address.subpackage == api.subpackage_view %} |
| 143 | + {%- for message in proto.messages.values() %} |
| 144 | + '{{ message.name }}', |
| 145 | + {%- endfor %} |
| 146 | + {%- for enum in proto.enums.values() %} |
| 147 | + '{{ enum.name }}', |
| 148 | + {%- endfor %} |
| 149 | + {%- endfor %} |
| 150 | + {%- endfilter %} |
| 151 | +) |
| 152 | +{% endif -%} {# lazy import #} |
| 153 | +{% endblock %} |
0 commit comments