Skip to content

Commit

Permalink
PROTOTYPE: Use dynamic types and mapping with a factory
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidFair committed Sep 15, 2023
1 parent 8d4b2e8 commit 3fdfef6
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 158 deletions.
7 changes: 7 additions & 0 deletions lib/openstack_query/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import sys
import logging

from .api import query_objects as _query_objects

# Create logger
openstack_query_loggers = logging.getLogger(__name__)
openstack_query_loggers.setLevel(logging.DEBUG)
Expand All @@ -17,3 +19,8 @@

# Add the Handler to the Logger
openstack_query_loggers.addHandler(logger_handler)

# Register dynamic types, these include ServerQuery, UserQuery, etc.
_dynamic_types = _query_objects.export_query_types()
for _type in _dynamic_types:
globals()[_type.__name__] = _type
Empty file.
Original file line number Diff line number Diff line change
@@ -1,55 +1,38 @@
import time
import abc
import logging
import time
from typing import Union, List, Any, Optional, Dict, Tuple

from custom_types.openstack_query.aliases import OpenstackResourceObj, PropValue
from enums.cloud_domains import CloudDomains
from enums.query.props.prop_enum import PropEnum
from enums.query.query_presets import QueryPresets
from enums.cloud_domains import CloudDomains

from openstack_query.query_output import QueryOutput
from openstack_query.query_builder import QueryBuilder
from openstack_query.query_parser import QueryParser
from openstack_query.runners.server_runner import QueryRunner

from exceptions.parse_query_error import ParseQueryError
from custom_types.openstack_query.aliases import OpenstackResourceObj, PropValue
from structs.query.query_impl import QueryImpl

logger = logging.getLogger(__name__)


class QueryMethods:
"""
Interface for Query Classes. This class exposes all public methods for query api.
"""

def __init__(
self,
builder: QueryBuilder,
runner: QueryRunner,
parser: QueryParser,
output: QueryOutput,
):
self.builder = builder
self.runner = runner
self.parser = parser
self.output = output
self._query_results = None
self._query_results_as_objects = None
class QueryAPI(abc.ABC):
def __init__(self, impl: QueryImpl = None):
self._impl = QueryAPI._inject_deps() if impl is None else impl

def select(self, *props: PropEnum):
@staticmethod
@abc.abstractmethod
def _inject_deps() -> QueryImpl:
"""
Public method used to 'select' properties that the query will return the value of.
Mutually exclusive to returning objects using select_all()
:param props: one or more properties to collect described as enum
Injects dependencies for the query implementation
from the dynamic type that was registered
"""

def select(self, *props: PropEnum):
# is an idempotent function
# an be called multiple times with should aggregate properties to select
logger.debug("select() called, with props: %s", [prop.name for prop in props])
if not props:
raise ParseQueryError("provide at least one property to select")

self.output.parse_select(*props, select_all=False)
self.impl.output.parse_select(*props, select_all=False)
logger.debug(
"selected props are now: %s",
[prop.name for prop in self.output.selected_props],
Expand Down
33 changes: 33 additions & 0 deletions lib/openstack_query/api/query_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import types
from typing import Type, TypeVar, List

from openstack_query.api.query_api import QueryAPI
from openstack_query.mappings import MappingInterface, MAPPING_REGISTRY
from openstack_query.queries.query_factory import QueryFactory
from structs.query.query_impl import QueryImpl

# Hide the fact that a mapping interface is being used
# which also decouples the mapping from the dynamic query
# type in the IDE inspection
QueryT = TypeVar("QueryT", bound=MappingInterface)


def export_query_types() -> list[type]:
"""
Exports a list of callable methods dynamically, e.g. ServerQuery, UserQuery, etc.
These will use the factory to inject their implementation on the fly, separating
the implementation from the public API.
"""
registered_mappings = []
for mapping_cls in MAPPING_REGISTRY:

def _inject_deps() -> QueryImpl:
return QueryFactory.build_query_deps(mapping_cls)

mapping_name = mapping_cls.__name__.replace("Mapping", "")
new_type = type(
f"{mapping_name}Query", (QueryAPI,), {"_inject_deps": _inject_deps}
)
registered_mappings.append(new_type)

return registered_mappings
2 changes: 2 additions & 0 deletions lib/openstack_query/mappings/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .mapping_interface import MappingInterface
from .mapping_registry import MAPPING_REGISTRY
29 changes: 29 additions & 0 deletions lib/openstack_query/mappings/mapping_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import abc
from typing import Type

from enums.query.props.prop_enum import PropEnum
from openstack_query.handlers.server_side_handler import ServerSideHandler
from structs.query.query_client_side_handlers import QueryClientSideHandlers


class MappingInterface(abc.ABC):
@staticmethod
@abc.abstractmethod
def get_prop_mapping() -> Type[PropEnum]:
"""
Returns a mapping of valid presets for server side attributes
"""

@staticmethod
@abc.abstractmethod
def get_server_side_handler() -> ServerSideHandler:
"""
Returns a mapping of valid presets for server side attributes
"""

@staticmethod
@abc.abstractmethod
def get_client_side_handlers() -> QueryClientSideHandlers:
"""
Returns a mapping of valid presets for client side attributes
"""
8 changes: 8 additions & 0 deletions lib/openstack_query/mappings/mapping_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from typing import Dict, TypeVar, List, Type

from openstack_query.mappings.mapping_interface import MappingInterface
from openstack_query.mappings.server_mapping import ServerMapping

MappingType = TypeVar("MappingType", bound=Type[MappingInterface])

MAPPING_REGISTRY: List[MappingType] = [ServerMapping]
110 changes: 110 additions & 0 deletions lib/openstack_query/mappings/server_mapping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from typing import Type

from enums.query.props.server_properties import ServerProperties
from enums.query.query_presets import (
QueryPresetsGeneric,
QueryPresetsDateTime,
QueryPresetsString,
)
from openstack_query.handlers.client_side_handler_datetime import (
ClientSideHandlerDateTime,
)
from openstack_query.handlers.client_side_handler_generic import (
ClientSideHandlerGeneric,
)
from openstack_query.handlers.client_side_handler_string import ClientSideHandlerString
from openstack_query.handlers.server_side_handler import ServerSideHandler
from openstack_query.mappings import MappingInterface
from openstack_query.time_utils import TimeUtils
from structs.query.query_client_side_handlers import QueryClientSideHandlers


class ServerMapping(MappingInterface):
@staticmethod
def get_prop_mapping() -> Type[ServerProperties]:
return ServerProperties

@staticmethod
def get_server_side_handler() -> ServerSideHandler:
"""
method to configure a server handler which can be used to get 'filter' keyword arguments that
can be passed to openstack function conn.compute.servers() to filter results for a valid preset-property pair
valid filters documented here:
https://docs.openstack.org/openstacksdk/latest/user/proxies/compute.html
https://docs.openstack.org/api-ref/compute/?expanded=list-servers-detail#list-server-request
"""

return ServerSideHandler(
{
QueryPresetsGeneric.EQUAL_TO: {
ServerProperties.USER_ID: lambda value: {"user_id": value},
ServerProperties.SERVER_ID: lambda value: {"uuid": value},
ServerProperties.SERVER_NAME: lambda value: {"hostname": value},
ServerProperties.SERVER_DESCRIPTION: lambda value: {
"description": value
},
ServerProperties.SERVER_STATUS: lambda value: {"vm_state": value},
ServerProperties.SERVER_CREATION_DATE: lambda value: {
"created_at": value
},
ServerProperties.FLAVOR_ID: lambda value: {"flavor": value},
ServerProperties.IMAGE_ID: lambda value: {"image": value},
ServerProperties.PROJECT_ID: lambda value: {"project_id": value},
},
QueryPresetsDateTime.OLDER_THAN_OR_EQUAL_TO: {
ServerProperties.SERVER_LAST_UPDATED_DATE: lambda **kwargs: {
"changes-before": TimeUtils.convert_to_timestamp(**kwargs)
}
},
QueryPresetsDateTime.YOUNGER_THAN_OR_EQUAL_TO: {
ServerProperties.SERVER_LAST_UPDATED_DATE: lambda **kwargs: {
"changes-since": TimeUtils.convert_to_timestamp(**kwargs)
}
},
}
)

@staticmethod
def get_client_side_handlers() -> QueryClientSideHandlers:
"""
method to configure a set of client-side handlers which can be used to get local filter functions
corresponding to valid preset-property pairs. These filter functions can be used to filter results after
listing all servers.
"""
return QueryClientSideHandlers(
# set generic query preset mappings
generic_handler=ClientSideHandlerGeneric(
{
QueryPresetsGeneric.EQUAL_TO: ["*"],
QueryPresetsGeneric.NOT_EQUAL_TO: ["*"],
}
),
# set string query preset mappings
string_handler=ClientSideHandlerString(
{QueryPresetsString.MATCHES_REGEX: [ServerProperties.SERVER_NAME]}
),
# set datetime query preset mappings
datetime_handler=ClientSideHandlerDateTime(
{
QueryPresetsDateTime.OLDER_THAN: [
ServerProperties.SERVER_CREATION_DATE,
ServerProperties.SERVER_LAST_UPDATED_DATE,
],
QueryPresetsDateTime.YOUNGER_THAN: [
ServerProperties.SERVER_CREATION_DATE,
ServerProperties.SERVER_LAST_UPDATED_DATE,
],
QueryPresetsDateTime.YOUNGER_THAN_OR_EQUAL_TO: [
ServerProperties.SERVER_CREATION_DATE,
ServerProperties.SERVER_LAST_UPDATED_DATE,
],
QueryPresetsDateTime.OLDER_THAN_OR_EQUAL_TO: [
ServerProperties.SERVER_CREATION_DATE,
ServerProperties.SERVER_LAST_UPDATED_DATE,
],
}
),
# set integer query preset mappings
integer_handler=None,
)
20 changes: 20 additions & 0 deletions lib/openstack_query/queries/query_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from openstack_query.mappings import MappingInterface
from openstack_query.query_builder import QueryBuilder
from openstack_query.query_output import QueryOutput
from openstack_query.query_parser import QueryParser
from structs.query.query_impl import QueryImpl


class QueryFactory:
@staticmethod
def build_query_deps(mapping_cls: MappingInterface) -> QueryImpl:
prop_mapping = mapping_cls.get_prop_mapping()

output = QueryOutput(prop_mapping)
parser = QueryParser(prop_mapping)
builder = QueryBuilder(
mapping_cls.get_prop_mapping(),
mapping_cls.get_client_side_handlers(),
mapping_cls.get_server_side_handler(),
)
return QueryImpl(output, parser, builder)
34 changes: 0 additions & 34 deletions lib/openstack_query/queries/query_wrapper.py

This file was deleted.

Loading

0 comments on commit 3fdfef6

Please sign in to comment.