Skip to content

Commit 112affc

Browse files
authored
SHOT-4407: Improve Alias start up (#77)
* Add option to load api and cache on server side * Modify the client to call server to load the api * Pop the timeout kwarg because the base class does not accept it
1 parent e353c41 commit 112affc

File tree

3 files changed

+97
-49
lines changed

3 files changed

+97
-49
lines changed

python/tk_framework_alias/client/socketio/client.py

Lines changed: 13 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,14 @@
88
# agreement to the ShotGrid Pipeline Toolkit Source Code License. All rights
99
# not expressly granted therein are reserved by Autodesk, Inc.
1010

11-
import filecmp
1211
import json
13-
import os
14-
import shutil
12+
import logging
1513
import socketio
1614
import threading
1715

1816
from .client_json import AliasClientJSON
1917
from ..utils.decorators import check_server_result, check_client_connection
2018
from tk_framework_alias_utils import utils as framework_utils
21-
from tk_framework_alias_utils import environment_utils as framework_env_utils
2219

2320

2421
class AliasSocketIoClient(socketio.Client):
@@ -58,10 +55,10 @@ def __init__(self, *args, **kwargs):
5855
self.__class__.__name__, "sio_client"
5956
)
6057

61-
super(AliasSocketIoClient, self).__init__(*args, **kwargs)
62-
6358
# The connection timeout in seconds
64-
self.__timeout = kwargs.get("timeout", 20)
59+
self.__timeout = kwargs.pop("timeout", 20)
60+
61+
super(AliasSocketIoClient, self).__init__(*args, **kwargs)
6562

6663
# The callbacks registry. Callback functions passed to the server are stored in the
6764
# client by their id, such that they can be looked up and executed when the server
@@ -328,43 +325,15 @@ def get_alias_api_module_proxy(self):
328325
:rtype: AliasClientModuleProxyWrapper
329326
"""
330327

331-
# Get information about the api module
332-
api_info = self.call_threadsafe("get_alias_api_info")
333-
334-
# Get the cached files for the api module
335-
filename = os.path.basename(api_info["file_path"]).split(".")[0]
336-
cache_filepath = framework_env_utils.get_alias_api_cache_file_path(
337-
filename, api_info["alias_version"], api_info["python_version"]
338-
)
339-
api_ext = os.path.splitext(api_info["file_path"])[1]
340-
cache_api_filepath = os.path.join(
341-
os.path.dirname(cache_filepath),
342-
f"{os.path.splitext(cache_filepath)[0]}{api_ext}",
343-
)
344-
345-
cache_loaded = False
346-
if os.path.exists(cache_filepath) and os.path.exists(cache_api_filepath):
347-
# The cache exists, check if it requires updating before using it.
348-
if filecmp.cmp(api_info["file_path"], cache_api_filepath):
349-
# The cache is still up to date, load it in.
350-
with open(cache_filepath, "r") as fp:
351-
module_proxy = json.load(fp, cls=self.get_json_decoder())
352-
cache_loaded = True
353-
354-
if not cache_loaded:
355-
cache_folder = os.path.dirname(cache_filepath)
356-
if not os.path.exists(cache_folder):
357-
os.mkdir(cache_folder)
358-
# The api was not loaded from cache, make a server request to get the api module,
359-
# and cache it
360-
module_proxy = self.call_threadsafe("get_alias_api")
361-
with open(cache_filepath, "w") as fp:
362-
json.dump(module_proxy, fp=fp, cls=self.get_json_encoder())
363-
# Copy the api module to the cache folder in order to determine next time if the
364-
# cache requies an update
365-
shutil.copyfile(api_info["file_path"], cache_api_filepath)
366-
367-
return module_proxy
328+
# The server will JSON-serialize the Alias Python API module to a local
329+
# file on disk. The server will return the path to this file for this
330+
# client to read and load the module from. This is done to avoid sending
331+
# the entire module over the network, which can be slow.
332+
module_filepath = self.call_threadsafe("load_alias_api", timeout=self.__timeout)
333+
with open(module_filepath, "r") as fp:
334+
module_proxy = json.load(fp, cls=self.get_json_decoder())
335+
self.logger.log(logging.DEBUG, module_proxy)
336+
return module_proxy
368337

369338
def get_alias_api(self):
370339
"""

python/tk_framework_alias/server/socketio/namespaces/server_namespace.py

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@
88
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
99
# not expressly granted therein are reserved by Shotgun Software Inc.
1010

11+
import filecmp
1112
import logging
13+
import json
1214
import pprint
1315
import os
16+
import shutil
1417
import socketio
1518

16-
from ...api import alias_api
19+
from tk_framework_alias_utils import environment_utils
1720

18-
from ..api_request import AliasApiRequestWrapper
1921
from ... import alias_bridge
22+
from ...api import alias_api
23+
from ..api_request import AliasApiRequestWrapper
24+
from ..server_json import AliasServerJSON
2025
from ...utils.invoker import execute_in_main_thread
2126
from ...utils.exceptions import (
2227
AliasApiRequestException,
@@ -172,7 +177,7 @@ def on_get_alias_api(self, sid):
172177
"""
173178
Get the Alias API module.
174179
175-
The module will be JSON-serialized before returning to the client.
180+
The module will be JSON-serialized and returned to the client.
176181
177182
:param sid: The client session id that made the request.
178183
:type sid: str
@@ -186,6 +191,70 @@ def on_get_alias_api(self, sid):
186191

187192
return alias_api
188193

194+
def on_load_alias_api(self, sid):
195+
"""
196+
Load the Alias API and JSON-serialize the module to file on disk.
197+
198+
This is an alternative method to getting the Alias API module
199+
and returning the module directly to the client. In this method, the
200+
module is JSON-serialized, saved locally to disk, and the file path
201+
to the JSON-serialized module is returned to the client. The client
202+
can then load the JSON-serialized module from the file.
203+
204+
This is a more efficient way for the client to obtain the Alias API
205+
module because the module data is not sent over the network, which will
206+
only become slower as the Alias API module grows (e.g. new functionality
207+
is added).
208+
209+
:param sid: The client session id that made the request.
210+
:type sid: str
211+
:return: The file path to the JSON-serialized Alias API module.
212+
:rtype: str
213+
"""
214+
215+
if self.client_sid is None or sid != self.client_sid:
216+
return
217+
218+
# Get the directory and path to the Alias API cached file. The cache
219+
# file name will have format:
220+
# {alias_api_module_name}{alias_version}_{python_version}.json
221+
api_info = self.on_get_alias_api_info(sid)
222+
api_filename, api_ext = os.path.splitext(
223+
os.path.basename(api_info["file_path"])
224+
)
225+
cache_filepath = environment_utils.get_alias_api_cache_file_path(
226+
api_filename, api_info["alias_version"], api_info["python_version"]
227+
)
228+
cache_dir = os.path.dirname(cache_filepath)
229+
230+
# Get the path to the Alias API module (.pyd) that was used to create
231+
# the cache. The cache module file name will have format:
232+
# {alias_api_module_name}{alias_version}_{python_version}.pyd
233+
base_cache_module_filename, _ = os.path.splitext(
234+
os.path.basename(cache_filepath)
235+
)
236+
cache_module_filename = f"{base_cache_module_filename}.{api_ext}"
237+
cache_module_filepath = os.path.join(cache_dir, cache_module_filename)
238+
239+
# Check if the cache is up-to-date. If not, create a new cache
240+
if (
241+
not os.path.exists(cache_filepath)
242+
or not os.path.exists(cache_module_filepath)
243+
or not filecmp.cmp(api_info["file_path"], cache_module_filepath)
244+
):
245+
# Ensure the cache directory exists
246+
if not os.path.exists(cache_dir):
247+
os.makedirs(cache_dir)
248+
# Create the Alias API cache
249+
with open(cache_filepath, "w") as fp:
250+
json.dump(alias_api, fp=fp, cls=AliasServerJSON.encoder_class())
251+
# Copy the module to the cache folder in order to determine next time if the
252+
# cache requies an update
253+
shutil.copyfile(api_info["file_path"], cache_module_filepath)
254+
255+
# Return the path to the Alias API cache file
256+
return cache_filepath
257+
189258
def on_get_alias_api_info(self, sid):
190259
"""
191260
Get the Alias API module info.

python/tk_framework_alias/server/socketio/server_json.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,23 @@
2525
class AliasServerJSON:
2626
"""A custom json module to handle serializing Alias API objects to JSON."""
2727

28+
@staticmethod
29+
def encoder_class():
30+
"""Return the encoder class used by this JSON module."""
31+
return AliasServerJSONEncoder
32+
33+
@staticmethod
34+
def decoder_class():
35+
"""Return the decoder class used by this JSON module."""
36+
return AliasServerJSONDecoder
37+
2838
@staticmethod
2939
def dumps(obj, *args, **kwargs):
30-
return json.dumps(obj, cls=AliasServerJSONEncoder, *args, **kwargs)
40+
return json.dumps(obj, cls=AliasServerJSON.encoder_class(), *args, **kwargs)
3141

3242
@staticmethod
3343
def loads(obj, *args, **kwargs):
34-
return json.loads(obj, cls=AliasServerJSONDecoder, *args, **kwargs)
44+
return json.loads(obj, cls=AliasServerJSON.decoder_class(), *args, **kwargs)
3545

3646

3747
class AliasServerJSONEncoder(json.JSONEncoder):

0 commit comments

Comments
 (0)