Use generated list of services instead of metaclass

The metaclass makes it pretty impossible to do type annotations on
anything. Turn the metaclass into a utility script and then generate
the list of mappings.

Change-Id: Iccd5ee34e364cd6f53c0a8934e20442c022844e8
This commit is contained in:
Monty Taylor 2019-10-04 09:59:11 +02:00
parent 8b85e8c954
commit c9fba05bf6
6 changed files with 279 additions and 145 deletions

View File

@ -1,126 +0,0 @@
# Copyright 2018 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import importlib
import warnings
import os_service_types
from openstack import _log
from openstack import service_description
_logger = _log.setup_logging('openstack')
_service_type_manager = os_service_types.ServiceTypes()
_DOC_TEMPLATE = (
":class:`{class_name}` for {service_type} aka {project}")
_PROXY_TEMPLATE = """Proxy for {service_type} aka {project}
This proxy object could be an instance of
{class_doc_strings}
depending on client configuration and which version of the service is
found on remotely on the cloud.
"""
class ConnectionMeta(type):
def __new__(meta, name, bases, dct):
for service in _service_type_manager.services:
service_type = service['service_type']
if service_type == 'ec2-api':
# NOTE(mordred) It doesn't make any sense to use ec2-api
# from openstacksdk. The credentials API calls are all calls
# on identity endpoints.
continue
desc_class = _find_service_description_class(service_type)
descriptor_args = {'service_type': service_type}
if not desc_class.supported_versions:
doc = _DOC_TEMPLATE.format(
class_name="{service_type} Proxy".format(
service_type=service_type),
**service)
elif len(desc_class.supported_versions) == 1:
supported_version = list(
desc_class.supported_versions.keys())[0]
doc = _DOC_TEMPLATE.format(
class_name="{service_type} Proxy <{name}>".format(
service_type=service_type, name=supported_version),
**service)
else:
class_doc_strings = "\n".join([
":class:`{class_name}`".format(
class_name=proxy_class.__name__)
for proxy_class in desc_class.supported_versions.values()])
doc = _PROXY_TEMPLATE.format(
class_doc_strings=class_doc_strings, **service)
descriptor = desc_class(**descriptor_args)
descriptor.__doc__ = doc
st = service_type.replace('-', '_')
dct[st] = descriptor
# Register the descriptor class with every known alias. Don't
# add doc strings though - although they are supported, we don't
# want to give anybody any bad ideas. Making a second descriptor
# does not introduce runtime cost as the descriptors all use
# the same _proxies dict on the instance.
for alias_name in _get_aliases(st):
if alias_name[-1].isdigit():
continue
alias_descriptor = desc_class(**descriptor_args)
dct[alias_name.replace('-', '_')] = alias_descriptor
return super(ConnectionMeta, meta).__new__(meta, name, bases, dct)
def _get_aliases(service_type, aliases=None):
# We make connection attributes for all official real type names
# and aliases. Three services have names they were called by in
# openstacksdk that are not covered by Service Types Authority aliases.
# Include them here - but take heed, no additional values should ever
# be added to this list.
# that were only used in openstacksdk resource naming.
LOCAL_ALIASES = {
'baremetal': 'bare_metal',
'block_storage': 'block_store',
'clustering': 'cluster',
}
all_types = set(_service_type_manager.get_aliases(service_type))
if aliases:
all_types.update(aliases)
if service_type in LOCAL_ALIASES:
all_types.add(LOCAL_ALIASES[service_type])
return all_types
def _find_service_description_class(service_type):
package_name = 'openstack.{service_type}'.format(
service_type=service_type).replace('-', '_')
module_name = service_type.replace('-', '_') + '_service'
class_name = ''.join(
[part.capitalize() for part in module_name.split('_')])
try:
import_name = '.'.join([package_name, module_name])
service_description_module = importlib.import_module(import_name)
except ImportError as e:
# ImportWarning is ignored by default. This warning is here
# as an opt-in for people trying to figure out why something
# didn't work.
warnings.warn(
"Could not import {service_type} service description: {e}".format(
service_type=service_type, e=str(e)),
ImportWarning)
return service_description.ServiceDescription
# There are no cases in which we should have a module but not the class
# inside it.
service_description_class = getattr(service_description_module, class_name)
return service_description_class

View File

@ -0,0 +1,131 @@
# Generated file, to change, run tools/print-services.py
from openstack import service_description
from openstack.baremetal import baremetal_service
from openstack.baremetal_introspection import baremetal_introspection_service
from openstack.block_storage import block_storage_service
from openstack.clustering import clustering_service
from openstack.compute import compute_service
from openstack.database import database_service
from openstack.dns import dns_service
from openstack.identity import identity_service
from openstack.image import image_service
from openstack.instance_ha import instance_ha_service
from openstack.key_manager import key_manager_service
from openstack.load_balancer import load_balancer_service
from openstack.message import message_service
from openstack.network import network_service
from openstack.object_store import object_store_service
from openstack.orchestration import orchestration_service
from openstack.workflow import workflow_service
class ServicesMixin(object):
identity = identity_service.IdentityService(service_type='identity')
compute = compute_service.ComputeService(service_type='compute')
image = image_service.ImageService(service_type='image')
load_balancer = load_balancer_service.LoadBalancerService(service_type='load-balancer')
object_store = object_store_service.ObjectStoreService(service_type='object-store')
clustering = clustering_service.ClusteringService(service_type='clustering')
resource_cluster = clustering
cluster = clustering
data_processing = service_description.ServiceDescription(service_type='data-processing')
baremetal = baremetal_service.BaremetalService(service_type='baremetal')
bare_metal = baremetal
baremetal_introspection = baremetal_introspection_service.BaremetalIntrospectionService(service_type='baremetal-introspection')
key_manager = key_manager_service.KeyManagerService(service_type='key-manager')
resource_optimization = service_description.ServiceDescription(service_type='resource-optimization')
infra_optim = resource_optimization
message = message_service.MessageService(service_type='message')
messaging = message
application_catalog = service_description.ServiceDescription(service_type='application-catalog')
container_infrastructure_management = service_description.ServiceDescription(service_type='container-infrastructure-management')
container_infrastructure = container_infrastructure_management
container_infra = container_infrastructure_management
search = service_description.ServiceDescription(service_type='search')
dns = dns_service.DnsService(service_type='dns')
workflow = workflow_service.WorkflowService(service_type='workflow')
rating = service_description.ServiceDescription(service_type='rating')
operator_policy = service_description.ServiceDescription(service_type='operator-policy')
policy = operator_policy
shared_file_system = service_description.ServiceDescription(service_type='shared-file-system')
share = shared_file_system
data_protection_orchestration = service_description.ServiceDescription(service_type='data-protection-orchestration')
orchestration = orchestration_service.OrchestrationService(service_type='orchestration')
block_storage = block_storage_service.BlockStorageService(service_type='block-storage')
block_store = block_storage
volume = block_storage
alarm = service_description.ServiceDescription(service_type='alarm')
alarming = alarm
meter = service_description.ServiceDescription(service_type='meter')
metering = meter
telemetry = meter
event = service_description.ServiceDescription(service_type='event')
events = event
application_deployment = service_description.ServiceDescription(service_type='application-deployment')
application_deployment = application_deployment
multi_region_network_automation = service_description.ServiceDescription(service_type='multi-region-network-automation')
tricircle = multi_region_network_automation
database = database_service.DatabaseService(service_type='database')
application_container = service_description.ServiceDescription(service_type='application-container')
container = application_container
root_cause_analysis = service_description.ServiceDescription(service_type='root-cause-analysis')
rca = root_cause_analysis
nfv_orchestration = service_description.ServiceDescription(service_type='nfv-orchestration')
network = network_service.NetworkService(service_type='network')
backup = service_description.ServiceDescription(service_type='backup')
monitoring_logging = service_description.ServiceDescription(service_type='monitoring-logging')
monitoring_log_api = monitoring_logging
monitoring = service_description.ServiceDescription(service_type='monitoring')
monitoring_events = service_description.ServiceDescription(service_type='monitoring-events')
placement = service_description.ServiceDescription(service_type='placement')
instance_ha = instance_ha_service.InstanceHaService(service_type='instance-ha')
ha = instance_ha
reservation = service_description.ServiceDescription(service_type='reservation')
function_engine = service_description.ServiceDescription(service_type='function-engine')
accelerator = service_description.ServiceDescription(service_type='accelerator')
admin_logic = service_description.ServiceDescription(service_type='admin-logic')
registration = admin_logic

View File

@ -183,7 +183,7 @@ import requestsexceptions
import six
from openstack import _log
from openstack._meta import connection as _meta
from openstack import _services_mixin
from openstack.cloud import openstackcloud as _cloud
from openstack.cloud import _baremetal
from openstack.cloud import _block_storage
@ -245,23 +245,24 @@ def from_config(cloud=None, config=None, options=None, **kwargs):
return Connection(config=config)
class Connection(six.with_metaclass(_meta.ConnectionMeta,
_cloud._OpenStackCloudMixin,
_baremetal.BaremetalCloudMixin,
_block_storage.BlockStorageCloudMixin,
_compute.ComputeCloudMixin,
_clustering.ClusteringCloudMixin,
_coe.CoeCloudMixin,
_dns.DnsCloudMixin,
_floating_ip.FloatingIPCloudMixin,
_identity.IdentityCloudMixin,
_image.ImageCloudMixin,
_network.NetworkCloudMixin,
_network_common.NetworkCommonCloudMixin,
_object_store.ObjectStoreCloudMixin,
_orchestration.OrchestrationCloudMixin,
_security_group.SecurityGroupCloudMixin
)):
class Connection(
_services_mixin.ServicesMixin,
_cloud._OpenStackCloudMixin,
_baremetal.BaremetalCloudMixin,
_block_storage.BlockStorageCloudMixin,
_compute.ComputeCloudMixin,
_clustering.ClusteringCloudMixin,
_coe.CoeCloudMixin,
_dns.DnsCloudMixin,
_floating_ip.FloatingIPCloudMixin,
_identity.IdentityCloudMixin,
_image.ImageCloudMixin,
_network.NetworkCloudMixin,
_network_common.NetworkCommonCloudMixin,
_object_store.ObjectStoreCloudMixin,
_orchestration.OrchestrationCloudMixin,
_security_group.SecurityGroupCloudMixin
):
def __init__(self, cloud=None, config=None, session=None,
app_name=None, app_version=None,

128
tools/print-services.py Normal file
View File

@ -0,0 +1,128 @@
# Copyright 2018 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import importlib
import warnings
import os_service_types
from openstack import _log
from openstack import service_description
_logger = _log.setup_logging('openstack')
_service_type_manager = os_service_types.ServiceTypes()
def make_names():
imports = ['from openstack import service_description']
services = []
for service in _service_type_manager.services:
service_type = service['service_type']
if service_type == 'ec2-api':
# NOTE(mordred) It doesn't make any sense to use ec2-api
# from openstacksdk. The credentials API calls are all calls
# on identity endpoints.
continue
desc_class = _find_service_description_class(service_type)
st = service_type.replace('-', '_')
if desc_class.__module__ != 'openstack.service_description':
base_mod, dm = desc_class.__module__.rsplit('.', 1)
imports.append(
'from {base_mod} import {dm}'.format(
base_mod=base_mod,
dm=dm))
else:
dm = 'service_description'
dc = desc_class.__name__
services.append(
"{st} = {dm}.{dc}(service_type='{service_type}')".format(
st=st, dm=dm, dc=dc, service_type=service_type),
)
# Register the descriptor class with every known alias. Don't
# add doc strings though - although they are supported, we don't
# want to give anybody any bad ideas. Making a second descriptor
# does not introduce runtime cost as the descriptors all use
# the same _proxies dict on the instance.
for alias_name in _get_aliases(st):
if alias_name[-1].isdigit():
continue
services.append(
'{alias_name} = {st}'.format(
alias_name=alias_name,
st=st))
services.append('')
print("# Generated file, to change, run tools/print-services.py")
for imp in sorted(imports):
print(imp)
print('\n')
print("class ServicesMixin(object):\n")
for service in services:
if service:
print(" {service}".format(service=service))
else:
print()
def _get_aliases(service_type, aliases=None):
# We make connection attributes for all official real type names
# and aliases. Three services have names they were called by in
# openstacksdk that are not covered by Service Types Authority aliases.
# Include them here - but take heed, no additional values should ever
# be added to this list.
# that were only used in openstacksdk resource naming.
LOCAL_ALIASES = {
'baremetal': 'bare_metal',
'block_storage': 'block_store',
'clustering': 'cluster',
}
all_types = set(_service_type_manager.get_aliases(service_type))
if aliases:
all_types.update(aliases)
if service_type in LOCAL_ALIASES:
all_types.add(LOCAL_ALIASES[service_type])
all_aliases = set()
for alias in all_types:
all_aliases.add(alias.replace('-', '_'))
return all_aliases
def _find_service_description_class(service_type):
package_name = 'openstack.{service_type}'.format(
service_type=service_type).replace('-', '_')
module_name = service_type.replace('-', '_') + '_service'
class_name = ''.join(
[part.capitalize() for part in module_name.split('_')])
try:
import_name = '.'.join([package_name, module_name])
service_description_module = importlib.import_module(import_name)
except ImportError as e:
# ImportWarning is ignored by default. This warning is here
# as an opt-in for people trying to figure out why something
# didn't work.
warnings.warn(
"Could not import {service_type} service description: {e}".format(
service_type=service_type, e=str(e)),
ImportWarning)
return service_description.ServiceDescription
# There are no cases in which we should have a module but not the class
# inside it.
service_description_class = getattr(service_description_module, class_name)
return service_description_class
make_names()

View File

@ -101,7 +101,7 @@ commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasen
# breaks should occur before the binary operator for readability.
ignore = H306,H4,W503
show-source = True
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,openstack/_services_mixin.py
[doc8]
extensions = .rst, .yaml