forked from shotgunsoftware/tk-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhook.py
More file actions
719 lines (570 loc) · 28 KB
/
hook.py
File metadata and controls
719 lines (570 loc) · 28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
# Copyright (c) 2013 Shotgun Software Inc.
#
# CONFIDENTIAL AND PROPRIETARY
#
# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
# Source Code License included in this distribution package. See LICENSE.
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
"""
Defines the base class for all Tank Hooks.
"""
import os
import re
import sys
import logging
import inspect
import threading
from .util.loader import load_plugin
from . import LogManager
from .errors import (
TankError,
TankFileDoesNotExistError,
TankHookMethodDoesNotExistError,
)
log = LogManager.get_logger(__name__)
class Hook(object):
"""
Hooks are implemented in a python file and they all derive from a :class:`Hook` base class.
If you are writing an app that loads files into Maya, Nuke or other DCCs, a hook is a good
way to expose the actual loading logic, so that not only can be customized by a user, but
so that you could even add support for a new DCC to your load app without having to update it.
First, you would create a ``hooks/actions.py`` file in your app. This would contain a hook class::
import sgtk
HookBaseClass = sgtk.get_hook_baseclass()
class Actions(HookBaseClass):
def list_actions(self, sg_publish_data):
'''
Given some Shotgun publish data, return a list of
actions that can be performed
:param sg_publish_data: Dictionary of publish data from Shotgun
:returns: List of action strings
'''
# The base implementation implements an action to show
# the item in Shotgun
return ["show_in_sg"]
def run_action(self, action, sg_publish_data):
'''
Execute the given action
:param action: name of action. One of the items returned by list_actions.
:param sg_publish_data: Dictionary of publish data from Shotgun
'''
if action == "show_in_sg":
url = "%s/detail/%s/%d" % (
self.parent.shotgun.base_url,
sg_publish_data["type"],
sg_publish_data["id"]
)
QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
The above code forms a generic base for your hook with a basic implementation that works
everywhere.
In the app manifest (``info.yml``), where we define all the basic configuration properties for the app,
we define an actions hook::
configuration:
actions_hook:
type: hook
default_value: "{self}/actions.py"
description: Hook which contains all methods for action management.
From the app code itself, you can now use :meth:`~sgtk.platform.Application.execute_hook_method()`
to call out to the hook::
# when creating a list of items in the UI for your app -
# given a shotgun publish, build a menu of avaialable actions:
actions = self.execute_hook_method("actions_hook", "list_actions", sg_data)
# in a callback method once a user has selected an action -
# call out to the hook to execute the action
self.execute_hook_method("actions_hook", "run_action", sg_data, action_name)
In the configuration for your app, you can now 'glue together' different functionality
for different scenarios. For example, when you install the app to run inside the Maya
engine, you want to be able to import maya files into maya. We implement this by adding a
custom publish hook for maya. This can either be placed with the app itself, in the ``hooks``
folder in the configuration, or in the maya engine. In this case, we'll add a ``hooks/actions.py``
to the maya engine. This file looks like this::
import sgtk
HookBaseClass = sgtk.get_hook_baseclass()
class MayaActions(HookBaseClass):
def list_actions(self, sg_publish_data):
'''
Given some Shotgun publish data, return a list of
actions that can be performed
:param sg_publish_data: Dictionary of publish data from Shotgun
:returns: List of action strings
'''
# first get base class actions
actions = HookBaseClass.list_actions(sg_publish_data)
# Add maya actions
if sg_publish_data["published_file_type"]["name"] == "Maya Scene":
actions += ["reference", "import"]
def run_action(self, action, sg_publish_data):
'''
Execute the given action
:param action: name of action. One of the items returned by list_actions.
:param sg_publish_data: Dictionary of publish data from Shotgun
'''
if action == "reference":
# do maya reference operation
elif action == "import":
# do maya import operation
else:
# pass on to base class
return HookBaseClass.run_action(action, sg_publish_data)
The above hook implements a couple of actions that are designed to work in Maya.
Lastly, we need to tell the app to pick up this file. In the environment configuration for the app
running inside of maya, we point it at our engine specific hook::
tk-maya:
tk-multi-myapp:
actions_hook: '{engine}/actions.py'
When we are running the app configuration in maya, the actions hook will automatically
resolve the hook code distributed with the maya engine. The base class will be automatically
determined to be the default value set in the manifest, allowing for the app to carry a default
base implementation that is always taken into account.
.. _hook-formats:
**Resolving paths to hooks**
Several different path formats exist, making this a very powerful configuration mechanism:
- ``{self}/path/to/foo.py`` -- looks in the ``hooks`` folder in the local app, engine of framework.
- ``{self}/{engine_name}_publish.py`` -- If running in maya, looks for a ``tk-maya_publish.py`` in
the ``hooks`` folder in the local app, engine of framework. If running in Nuke, it would instead
look for ``BUNDLE_ROOT/hooks/tk-nuke_publish.py``.
- ``{config}/path/to/foo.py`` -- Looks in the ``hooks`` folder in the configuration.
- ``{$HOOK_PATH}/path/to/foo.py`` -- expression based around an environment variable.
- ``{engine}/path/to/foo.py`` -- looks in the ``hooks`` folder of the current engine.
- ``{tk-framework-perforce_v1.x.x}/path/to/foo.py`` -- looks in the ``hooks`` folder of a
framework instance that exists in the current environment. Basically, each entry inside the
frameworks section in the current environment can be specified here - all these entries are
on the form frameworkname_versionpattern, for example ``tk-framework-widget_v0.1.2`` or
``tk-framework-shotgunutils_v1.3.x``.
Supported legacy formats:
- ``foo`` -- Equivalent to ``{config}/foo.py``
You can also provide your own inheritance chains. For example, if you wanted to add your own,
project specific maya hooks to this app, you could do this by creating a hook file, placing
it in your configuration's ``hooks`` folder and then configure it like this::
tk-maya:
tk-multi-myapp:
actions_hook: '{engine}/actions.py:{config}/maya_actions.py'
This would execute your ``maya_actions.py`` hook and make sure that that hook inherits from the
engine specific hook, making sure that you get both your custom actions, the engine default actions
and the app's built-in actions.
"""
# default method to execute on hooks
DEFAULT_HOOK_METHOD = "execute"
def __init__(self, parent):
self.__parent = parent
@property
def sgtk(self):
"""
The sgtk core API instance associated with the Hook parent.
This is a convenience method for easy core API instance access.
In the case of app, engine and framework hooks, this is equivalent
to ``parent.sgtk`` and in the case of core hooks it simply returns
``parent``.
.. note:: Some low level hooks do not have a parent defined. In
such cases, ``None`` is returned.
"""
# local import to avoid cycles
from .api import Sgtk
if self.parent is None:
# system hook
return None
if isinstance(self.parent, Sgtk):
# core hook
return self.parent
else:
# look for sgtk instance on parent
try:
return self.parent.sgtk
except AttributeError:
return None
@property
def tank(self):
"""
The sgtk core API instance associated with the Hook parent.
.. deprecated::
Use :meth:`sgtk` instead.
"""
return self.sgtk
@property
def parent(self):
"""
The parent object to the executing hook. This varies with the type of
hook that is being executed. For a hook that runs inside an app or an engine,
the parent object will be the :class:`~sgtk.platform.Application` or
:class:`~sgtk.platform.Engine` instance. For core hooks, the
parent object will be :class:`sgtk`.
.. note:: If you need to access Shotgun inside your hook, you can do this by
calling ``self.parent.shotgun`` since both Apps, Engines and the Core API
has a ``shotgun`` property.
"""
return self.__parent
def get_publish_path(self, sg_publish_data):
"""
Returns the path on disk for a publish entity in Shotgun.
Convenience method that calls :meth:`sgtk.util.resolve_publish_path`.
:param sg_publish_data: Dictionaries containing Shotgun publish data.
Each dictionary needs to at least contain a code, type, id and a path key.
:returns: A path on disk to existing file or file sequence.
:raises: :class:`~sgtk.util.PublishPathNotDefinedError` if the path isn't defined.
:raises: :class:`~sgtk.util.PublishPathNotSupported` if the path cannot be resolved.
"""
# avoid cyclic refs
from .util import resolve_publish_path
return resolve_publish_path(self.sgtk, sg_publish_data)
def get_publish_paths(self, sg_publish_data_list):
"""
Returns several local paths on disk given a
list of shotgun data dictionaries representing publishes.
Convenience method that calls :meth:`sgtk.util.resolve_publish_path`.
.. deprecated:: 0.18.64
Use :meth:`get_publish_path` instead.
:param sg_publish_data_list: List of shotgun data dictionaries
containing publish data. Each dictionary
needs to at least contain a code, type,
id and a path key.
:returns: List of paths on disk to existing files or file sequences.
:raises: :class:`~sgtk.util.PublishPathNotDefinedError` if any of the paths aren't defined.
:raises: :class:`~sgtk.util.PublishPathNotSupported` if any of the paths cannot be resolved.
"""
# avoid cyclic refs
from .util import resolve_publish_path
paths = []
for sg_publish_data in sg_publish_data_list:
paths.append(resolve_publish_path(self.sgtk, sg_publish_data))
return paths
@property
def disk_location(self):
"""
The folder on disk where this item is located.
This can be useful if you want to write hook code
to retrieve a local resource::
hook_icon = os.path.join(self.disk_location, "icon.png")
"""
# NOTE: this method contains complex logic to correctly handle
# the following inheritance case:
#
# A. Hook base class >
# B. general implementation (hook_b.py) >
# C. custom implementation (hook_c.py)
#
# in (B), there is a method which returns an icon:
# return os.path.join(self.disk_location, "icon.png")
#
# in this case, we want the code in B to return a path
# relative to hook_b.py.
#
# However, although intended to be primarily used
# from within a hook, the disk_location is a public method.
# Whenever called from an external location, the
# disk location is computed to be relative to
# the leaf inheritance, e.g. hook_c.py in this case.
# in order to handle the case above, we look for the
# caller. If the caller is part of the inheritance
# chain, we simply compute the path relative to
# the caller method.
#
# however if the caller is external, we compute the path
# relative to self. Self represents the leaf in the
# inheritance tree.
#
path_to_file = None
current_frame = inspect.currentframe()
all_frames = inspect.getouterframes(current_frame)
parent_frame = all_frames[1]
if "self" in parent_frame[0].f_locals:
# the calling method is a class instance method
path_to_calling_file = parent_frame[1]
if parent_frame[0].f_locals["self"] is self:
# this call is coming from within the
# class hierarchy
path_to_file = path_to_calling_file
# first check failed. fall back on computing
# the location relative to self
if path_to_file is None:
path_to_file = os.path.abspath(sys.modules[self.__module__].__file__)
return os.path.dirname(path_to_file)
@property
def logger(self):
"""
Standard python logger handle for this hook.
The logger can be used to report
progress back to the app in a standardized fashion
and will be parented under the app/engine/framework logger::
# pattern
sgtk.env.environment_name.engine_name.app_name.hook.hook_file_name
# for example
sgtk.env.asset.tk-maya.tk-multi-loader2.hook.filter_publishes
In the case of core hooks, the logger will be parented
under ``sgtk.core.hook``. For more information, see :ref:`logging`
"""
# see if the hook parent object exists and has a logger property
# in that case make this the parent of our logger.
#
# note: core hooks have the tk instance as their parent and
# app/engine/fw hooks have the bundle as their parent.
# There are some special edge cases where we construct
# parentless hooks: the PipelineConfigurationInit hook runs
# before a tk instance exists and some of the older "studio
# hooks" such as project_name.py and sg_connection.py also exists
# with states where no parent can be defined easily.
try:
logger = self.parent.logger
except AttributeError:
# parent doesn't exist or doesn't have a logger.
# in this case use the logger for this file as a
# parent - e.g. 'sgtk.core.hook'
log_prefix = log.name
else:
log_prefix = "%s.hook" % logger.name
# name the logger after the name of the hook filename
path_to_this_file = os.path.abspath(sys.modules[self.__module__].__file__)
hook_name = os.path.splitext(os.path.basename(path_to_this_file))[0]
full_log_path = "%s.%s" % (log_prefix, hook_name)
return logging.getLogger(full_log_path)
def load_framework(self, framework_instance_name):
"""
Loads and returns a framework given an environment instance name.
.. note:: This method only works for hooks that are executed from apps and frameworks.
If you have complex logic and functionality and want to manage (and version it) as part
of a framework rather than in a hook, you can do this by calling a configured framework
from inside a hook::
import sgtk
HookBaseClass = sgtk.get_hook_baseclass()
class SomeHook(HookBaseClass):
def some_method(self):
# first get a framework handle. This object is similar to an app or engine object
fw = self.load_framework("tk-framework-library_v1.x.x")
# now just like with an app or an engine, if you want to access code in the python
# folder, you can do import_plugin
module = fw.import_module("some_module")
module.do_stuff()
Note how we are accessing the framework instance ``tk-framework-library_v1.x.x`` above.
This needs to be defined in the currently running environment, as part of the ``frameworks`` section::
engines:
# all engine and app defs here...
frameworks:
# define the framework that we are using in the hook
tk-framework-library_v1.x.x:
location: {type: git, path: 'https://github.com/foo/tk-framework-library.git', version: v1.2.6}
:param framework_instance_name: Name of the framework instance to load from the environment.
"""
# avoid circular refs
from .platform import framework
try:
engine = self.__parent.engine
except:
raise TankError("Cannot load framework %s for %r - it does not have a "
"valid engine property!" % (framework_instance_name, self.__parent))
return framework.load_framework(engine, engine.get_env(), framework_instance_name)
def execute(self):
"""
Legacy support for old style hooks
"""
return None
class _HooksCache(object):
"""
A thread-safe cache of loaded hooks. This uses the hook file path
and base class as the key to cache all hooks loaded by Toolkit in
the current session.
"""
def __init__(self):
"""
Construction
"""
self._cache = {}
self._cache_lock = threading.Lock()
def thread_exclusive(func):
"""
function decorator to ensure multiple threads can't access the cache
at the same time.
:param func: The function to wrap
:returns: The return value from func
"""
def inner(self, *args, **kwargs):
"""
Decorator inner function - executes the function within a lock.
:returns: The return value from func
"""
lock = self._cache_lock
lock.acquire()
try:
return func(self, *args, **kwargs)
finally:
lock.release()
return inner
@thread_exclusive
def clear(self):
"""
Clear the hook cache
"""
self._cache = {}
@thread_exclusive
def find(self, hook_path, hook_base_class):
"""
Find a hook in the cache using the hook path and base class
:param hook_path: The path to the hook to find
:param hook_base_class: The base class for the hook to find
:returns: The Hook class if found, None if not
"""
# The unique cache key is a tuple of the path and the base class to allow
# loading of classes with different bases from the same file
key = (hook_path, hook_base_class)
return self._cache.get(key, None)
@thread_exclusive
def add(self, hook_path, hook_base_class, hook_class):
"""
Add the specified hook to the cache if it isn't already present
:param hook_path: The path to the hook to add
:param hook_base_class: The base class for the hook to add
:param hook_class: The Hook class to add
"""
# The unique cache key is a tuple of the path and the base class to allow
# loading of classes with different bases from the same file
key = (hook_path, hook_base_class)
if key not in self._cache:
self._cache[key] = hook_class
@thread_exclusive
def __len__(self):
"""
Return the number of items currently in the hook cache
"""
return len(self._cache)
_hooks_cache = _HooksCache()
_current_hook_baseclass = threading.local()
def clear_hooks_cache():
"""
Clears the cache where tank keeps hook classes
"""
_hooks_cache.clear()
def execute_hook(hook_path, parent, **kwargs):
"""
Executes a hook, old-school style.
A hook is a python file which
contains exactly one class which derives (at some point
in its inheritance tree) from the Hook base class.
Once the file has been loaded (and cached), the execute()
method will be called and any optional arguments pass to
this method will be forwarded on to that execute() method.
:param hook_path: Full path to the hook python file
:param parent: Parent object. This will be accessible inside
the hook as self.parent, and is typically an
app, engine or core object.
:returns: Whatever the hook returns.
"""
return execute_hook_method([hook_path], parent, None, **kwargs)
def execute_hook_method(hook_paths, parent, method_name, base_class=None, **kwargs):
"""
New style hook execution, with method arguments and support for inheritance.
This method takes a list of hook paths and will load each of the classes
in, while maintaining the correct state of the class returned via
get_hook_baseclass(). Once all classes have been successfully loaded,
the last class in the list is instantiated and the specified method
is executed.
Example: ["/tmp/a.py", "/tmp/b.py", "/tmp/c.py"]
1. The code in a.py is loaded in. get_hook_baseclass() will return Hook
at this point. class HookA is returned from our plugin loader.
2. /tmp/b.py is loaded in. get_hook_baseclass() now returns HookA, so
if the hook code in B utilises get_hook_baseclass, this will will
set up an inheritance relationship with A
3. /tmp/c.py is finally loaded in, get_hook_baseclass() now returns HookB.
4. HookC class is instantiated and method method_name is executed.
An optional `base_class` can be provided to override the default ``Hook``
base class. This is useful for bundles that wish to execute a hook method
while providing a default implementation without the need to configure a
base hook.
:param hook_paths: List of full paths to hooks, in inheritance order.
:param parent: Parent object. This will be accessible inside
the hook as self.parent, and is typically an
app, engine or core object.
:param method_name: method to execute. If None, the default method will be executed.
:param base_class: A python class to use as the base class for the hook
class. This will override the default hook base class, ``Hook``. The
class should derive from ``Hook``.
:returns: Whatever the hook returns.
"""
hook = create_hook_instance(hook_paths, parent, base_class=base_class)
# get the method
method_name = method_name or Hook.DEFAULT_HOOK_METHOD
try:
hook_method = getattr(hook, method_name)
except AttributeError:
raise TankHookMethodDoesNotExistError(
"Cannot execute hook '%s' - the hook class does not have a '%s' "
"method!" % (hook, method_name)
)
# execute the method
ret_val = hook_method(**kwargs)
return ret_val
def create_hook_instance(hook_paths, parent, base_class=None):
"""
New style hook execution, with method arguments and support for inheritance.
This method takes a list of hook paths and will load each of the classes
in, while maintaining the correct state of the class returned via
get_hook_baseclass(). Once all classes have been successfully loaded,
an instance of the last class in the list is returned.
Example: ["/tmp/a.py", "/tmp/b.py", "/tmp/c.py"]
1. The code in a.py is loaded in. get_hook_baseclass() will return Hook
at this point (or a custom base class, if supplied, that derives from
Hook). class HookA is returned from our plugin loader.
2. /tmp/b.py is loaded in. get_hook_baseclass() now returns HookA, so
if the hook code in B utilises get_hook_baseclass, this will will
set up an inheritance relationship with A
3. /tmp/c.py is finally loaded in, get_hook_baseclass() now returns HookB.
4. An instance of the HookC class is returned.
An optional `base_class` can be provided to override the default ``Hook``
base class. This is useful for bundles that create hook instances at
execution time and wish to provide default implementation without the need
to configure the base hook.
:param hook_paths: List of full paths to hooks, in inheritance order.
:param base_class: A python class to use as the base class for the created
hook. This will override the default hook base class, ``Hook``.
:returns: Instance of the hook.
"""
if base_class:
# ensure the supplied base class is a subclass of Hook
if not issubclass(base_class, Hook):
raise TankError(
"Invalid custom hook base class. The supplied class '%s' does "
"not inherit from Hook." % (Hook,)
)
else:
base_class = Hook
# keep track of the current base class - this is used when loading hooks to dynamically
# inherit from the correct base.
_current_hook_baseclass.value = base_class
for hook_path in hook_paths:
if not os.path.exists(hook_path):
raise TankFileDoesNotExistError(
"Cannot execute hook '%s' - this file does not exist on disk!" % hook_path
)
# look to see if we've already loaded this hook into the cache
found_hook_class = _hooks_cache.find(hook_path, _current_hook_baseclass.value)
if not found_hook_class:
# load the hook class from the hook file and cache it - this explicitly looks for a
# single class from the hook file that is derived from the current base (or 'Hook' for
# backwards compatibility).
# determine any alternate base classes to look for in addition to the current base:
alternate_base_classes = []
if _current_hook_baseclass.value != Hook:
# allow deriving from the Hook base class - this is to support the legacy method of
# overriding hooks but without sub-classing them.
alternate_base_classes.append(Hook)
# try to load the hook class:
loaded_hook_class = load_plugin(
hook_path,
valid_base_class=_current_hook_baseclass.value,
alternate_base_classes=alternate_base_classes
)
# add it to the cache...
_hooks_cache.add(hook_path, _current_hook_baseclass.value, loaded_hook_class)
# ...and find it again - this is to avoid different threads ending up using
# different instances of the loaded class.
found_hook_class = _hooks_cache.find(hook_path, _current_hook_baseclass.value)
# keep track of the current base class:
_current_hook_baseclass.value = found_hook_class
# all class construction done. _current_hook_baseclass contains the last
# class we iterated over. An instance of this is what we want to return
return _current_hook_baseclass.value(parent)
def get_hook_baseclass():
"""
Returns the base class to use for the hook currently
being loaded. For more details and examples, see the :class:`Hook` documentation.
"""
return _current_hook_baseclass.value