Skip to content

Commit

Permalink
SHOT-4404: Performance (#74)
Browse files Browse the repository at this point in the history
* Add AliasApiRequestListWrapper class for lists of api requests.
* Execute multiple Alias API requests in a single Qt event
  • Loading branch information
staceyoue authored Dec 4, 2024
1 parent ca66b39 commit e353c41
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 54 deletions.
116 changes: 94 additions & 22 deletions python/tk_framework_alias/server/socketio/api_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,9 @@ class AliasApiRequestWrapper:
def create_wrapper(cls, data):
"""Create and return a new object of this type from the given data, if possible."""

if isinstance(data, list):
return [cls.create_wrapper(item) for item in data]

if not isinstance(data, dict):
return None

for subclass in cls.__subclasses__():
if subclass.needs_wrapping(data):
return subclass(data)

return None

@classmethod
Expand Down Expand Up @@ -74,20 +67,87 @@ def needs_wrapping(cls, value):

raise NotImplementedError("Subclass must implement")

@classmethod
def _create(cls, data):
# ----------------------------------------------------------------------------------------
# Public methods

def validate(self, request_name):
"""
Create and return a new object of this type from the given data, if possible.
Validate the request against this wrapper.
:param data: The data to create the object from.
:type data: dict
:param request_name: The name of the api request.
:type request_name: str
:raises: AliasApiRequestNotValid if request not valid.
"""

:return: An instance of this class.
:rtype: AliasApiRequestWrapper
raise NotImplementedError("Subclass must implement")

def execute(self, request_name):
"""
Execute the api request for this wrapper object.
:param request: The api request name.
:type request: str
"""

raise NotImplementedError("Subclass must implement")


class AliasApiRequestListWrapper(AliasApiRequestWrapper):
"""A wrapper for a list of Alias API requests."""

def __init__(self, data):
"""Initialize the wrapper data."""

self.__requests = data

def __str__(self) -> str:
"""Return a string representation for the Alias Api request object."""

return f"[{', '.join([str(r[1]) for r in self.__requests])}]"

# ----------------------------------------------------------------------------------------
# Class methods

@classmethod
def required_data(cls):
"""
Return the set of required data dictionary keys to create an instance of this class.
:return: The set of required keys.
:rtype: set
"""

# No required data, the list wrapper is a list of wrappers
return set()

@classmethod
def needs_wrapping(cls, value):
"""
Check if the value represents an object that needs to be wrapped by this proxy class.
:param value: The value to check if needs wrapping.
:type value: any
:return: True if the value should be wrapped by this class, else False.
:rtype: bool
"""

# The list wrapper expects a list of values which are each a list
# containing (1) request name and (2) the AliasApiRequestWrapper object
# corresponding to the request name
if not isinstance(value, list):
return False
for item in value:
if not isinstance(item, list):
return False
if not len(item) == 2:
return False
if not isinstance(item[0], str):
return False
if not isinstance(item[1], AliasApiRequestWrapper):
return False
return True

# ----------------------------------------------------------------------------------------
# Public methods

Expand All @@ -99,17 +159,26 @@ def validate(self, request_name):
:type request_name: str
"""

raise NotImplementedError("Subclass must implement")
return request_name == "batch_requests"

def execute(self, request_name):
"""
Execute the api request for this wrapper object.
Execute the Alias API request to execute the api function.
:param request: The api request name.
:type request: str
:param request_name: The api request name.
:type request_name: str
:return: The return value api function.
:rtype: any
"""

raise NotImplementedError("Subclass must implement")
self.validate(request_name)

results = []
for request_object_name, request_object in self.__requests:
result = request_object.execute(request_object_name)
results.append(result)
return results


class AliasApiRequestFunctionWrapper(AliasApiRequestWrapper):
Expand Down Expand Up @@ -198,6 +267,8 @@ def needs_wrapping(cls, value):
:rtype: bool
"""

if not isinstance(value, dict):
return False
return cls.required_data().issubset(set(value.keys()))

# ----------------------------------------------------------------------------------------
Expand Down Expand Up @@ -248,7 +319,6 @@ def validate(self, request_name):
raise AliasApiRequestNotValid(
f"Requested '{request_name}' but should be '{self.func_name}'"
)
return True

def execute(self, request_name):
"""
Expand Down Expand Up @@ -331,6 +401,8 @@ def needs_wrapping(cls, value):
:rtype: bool
"""

if not isinstance(value, dict):
return False
return cls.required_data() == set(value.keys())

# ----------------------------------------------------------------------------------------
Expand Down Expand Up @@ -366,7 +438,6 @@ def validate(self, request_name):
raise AliasApiRequestNotValid(
f"Requested '{request_name}' but should be '{self.property_name}'"
)
return True

def execute(self, request_name):
"""
Expand Down Expand Up @@ -441,6 +512,8 @@ def needs_wrapping(cls, value):
:rtype: bool
"""

if not isinstance(value, dict):
return False
return cls.required_data() == set(value.keys())

# ----------------------------------------------------------------------------------------
Expand Down Expand Up @@ -481,7 +554,6 @@ def validate(self, request_name):
raise AliasApiRequestNotValid(
f"Requested '{request_name}' but should be '{self.property_name}'"
)
return True

def execute(self, request_name):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,41 +289,15 @@ def _trigger_api_event(self, event, sid, *args):
if self.client_sid is None or sid != self.client_sid:
return

# Execute the Alias API request
# Get the request data from the args
request = args[0] if args else None
if isinstance(request, list):
# Handle multiple requests at once. Return a list of results.
total_requests = len(request)
results = []
for i, (event_name, request_data) in enumerate(request, start=1):
self._log_message(
None, f"Request ({i} of {total_requests})...", logging.INFO
)
results.append(self._handle_api_event(event_name, sid, request_data))
return results
else:
# Make a signle request and return the result.
return self._handle_api_event(event, sid, request)

def _handle_api_event(self, event, sid, request):
"""
An Alias API event was triggered by the client.
Execute the Alias API request from the given data. The event should match an api
function (e.g. module function, instance method), and the data contains all the
necesasry information to execute the api request.
:param event: The event corresponding to an api request.
:type event: str
:param sid: The session id of the client that triggered the event.
:type sid: str
:param *args: The data list to pass on to the event handler method.
:type *args: List[any]

:return: The return value of the api request.
:rtype: any
"""
# For multiple requests, create a wrapper to ensure all requests are
# invoked in a single Qt event
if isinstance(request, list):
request = AliasApiRequestWrapper.create_wrapper(request)

# Execute the request(s)
self._log_message(None, f"Excuting Alias API request: {request}", logging.INFO)
result = self._execute_request(event, request)

Expand Down

0 comments on commit e353c41

Please sign in to comment.