Skip to content

Commit 3b43873

Browse files
committed
Changes to class visibility (e.g. _RMApp=>RMApp), to allow loading local extensions which are added as mixins
1 parent 5cfe004 commit 3b43873

24 files changed

+2353
-2308
lines changed

elmclient/__init__.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,48 @@
2424
__license__ = __meta__.license
2525
__author__ = __meta__.author
2626

27+
import importlib, os, inspect, glob
28+
29+
# scan for extensions to import and extend the specified class
30+
# extensions are .py file with a name like <classname>-something.py e.g RMApp-extension.py
31+
# the classes in the file are added as base classes to the extended class
32+
# so it's dynamic mixins :-) A mixin may override existing methods
33+
def load_extensions( *, path=None, folder=None ):
34+
path = path or os.getcwd()
35+
folder = folder or "extensions"
36+
37+
# find public elmclient names+classes - these are extension points
38+
extendableclasses = {n:c for n,c in inspect.getmembers(importlib.import_module("elmclient"), inspect.isclass) }
39+
40+
# look for extension files
41+
searchdir = os.path.join( path, folder )
42+
if os.access( searchdir, os.F_OK ):
43+
# folder doesn't exist - nothing to load
44+
pass
45+
else:
46+
files = glob.glob( os.path.join( searchdir, "**/*.py" ), recursive=True )
47+
for file in files:
48+
filename = os.path.split( file )[1]
49+
# get the extended class
50+
extended = filename.split( "-", 1 )[0]
51+
if extended not in extendableclasses:
52+
print( "No matching class to extend for {filename}" )
53+
else:
54+
extendedclass=extendableclasses[extended]
55+
# Import source file - from https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
56+
loader = importlib.machinery.SourceFileLoader( filename, file )
57+
spec = importlib.util.spec_from_loader( filename, loader )
58+
mymodule = importlib.util.module_from_spec( spec )
59+
loader.exec_module( mymodule )
60+
# find the classes in the extension
61+
extenderclasses = { n:c for n,c in inspect.getmembers( mymodule, inspect.isclass ) }
62+
# add them to the extended class so they precede (i.e. may override) the previous base classes
63+
for n,c in extenderclasses.items():
64+
# add to bases
65+
extendedclass.__bases__ = (c,)+extendedclass.__bases__
66+
67+
68+
# load any local extensions
69+
load_extensions( extendableclasses )
70+
71+

elmclient/_ccm.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def callers():
3939

4040
#################################################################################################
4141

42-
class _CCMProject(_project._Project):
42+
class CCMProject(_project._Project):
4343
def __init__(self, name, project_uri, app, is_optin,singlemode):
4444
super().__init__(name, project_uri, app, is_optin,singlemode)
4545
self.default_query_resource = 'oslc_cm1:ChangeRequest'
@@ -295,9 +295,9 @@ def resolve_uri_to_name(self, uri, trytouseasid=False):
295295
#################################################################################################
296296

297297
@utils.mixinomatic
298-
class _CCMApp(_app._App, _typesystem.No_Type_System_Mixin):
298+
class CCMApp(_app._App, _typesystem.No_Type_System_Mixin):
299299
domain = 'ccm'
300-
project_class = _CCMProject
300+
project_class = CCMProject
301301
supports_configs = False
302302
supports_components = False
303303
reportablerestbase='rpt/repository'
@@ -453,17 +453,17 @@ def process_represt_arguments( self, args, allapps ):
453453

454454
#################################################################################################
455455

456-
class _AMProject(_CCMProject):
456+
class AMProject(CCMProject):
457457
def __init__(self, name, project_uri, app, is_optin,singlemode):
458458
super().__init__(name, project_uri, app, is_optin,singlemode)
459459
self.default_query_resource = 'oslc_am:Resource'
460460

461461
#################################################################################################
462462

463463
@utils.mixinomatic
464-
class _AMApp(_app._App, _typesystem.No_Type_System_Mixin):
464+
class AMApp(_app._App, _typesystem.No_Type_System_Mixin):
465465
domain = 'am'
466-
project_class = _AMProject
466+
project_class = AMProject
467467
supports_configs = False
468468
supports_components = False
469469
supports_reportable_rest = False

elmclient/_gcm.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def _hook_beforequery(querydetails):
4848

4949
#################################################################################################
5050

51-
class _GCMProject(_project._Project):
51+
class GCMProject(_project._Project):
5252
def __init__(self, name, project_uri, app, is_optin=False, singlemode=False,defaultinit=True):
5353
super().__init__(name, project_uri, app, is_optin,singlemode, defaultinit=False)
5454
self.hooks = [_hook_beforequery]
@@ -369,15 +369,15 @@ def resolve_uri_to_name(self, uri, prefer_same_as=True, dontpreferhttprdfrui=Tru
369369

370370
#################################################################################################
371371

372-
class _GCMComponent(_GCMProject):
372+
class GCMComponent(GCMProject):
373373
pass
374374

375375
#################################################################################################
376376

377377
@utils.mixinomatic
378378
class GCMApp(_app._App, oslcqueryapi._OSLCOperations_Mixin, _typesystem.Type_System_Mixin):
379379
domain = 'gc'
380-
project_class = _GCMProject
380+
project_class = GCMProject
381381
supports_configs = False
382382
supports_components = True
383383
supports_reportable_rest = False

elmclient/_qm.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
#################################################################################################
3131

3232

33-
class _QMProject(_project._Project, _qmrestapi.QM_REST_API_Mixin):
33+
class QMProject(_project._Project, _qmrestapi.QM_REST_API_Mixin):
3434
# A QM project
3535
def __init__(self, name, project_uri, app, is_optin=False, singlemode=False,defaultinit=True):
3636
super().__init__(name, project_uri, app, is_optin,singlemode,defaultinit=defaultinit)
@@ -313,7 +313,7 @@ def list_components( self ):
313313

314314
def _create_component_api(self, component_prj_url, component_name):
315315
logger.info( f"CREATE QM COMPONENT {self=} {component_prj_url=} {component_name=} {self.app=} {self.is_optin=} {self.singlemode=}" )
316-
result = _QMComponent(component_name, component_prj_url, self.app, self.is_optin, self.singlemode, defaultinit=False, project=self)
316+
result = QMComponent(component_name, component_prj_url, self.app, self.is_optin, self.singlemode, defaultinit=False, project=self)
317317
return result
318318

319319

@@ -415,7 +415,7 @@ def get_default_stream_name( self ):
415415

416416
#################################################################################################
417417

418-
class _QMComponent(_QMProject):
418+
class QMComponent(QMProject):
419419
def __init__(self, name, project_uri, app, is_optin=False, singlemode=False,defaultinit=True, project=None):
420420
if not project:
421421
raise Exception( "You mist provide a project instance when creating a component" )
@@ -426,9 +426,9 @@ def __init__(self, name, project_uri, app, is_optin=False, singlemode=False,defa
426426
#################################################################################################
427427

428428
@utils.mixinomatic
429-
class _QMApp(_app._App, oslcqueryapi._OSLCOperations_Mixin, _typesystem.Type_System_Mixin):
429+
class QMApp(_app._App, oslcqueryapi._OSLCOperations_Mixin, _typesystem.Type_System_Mixin):
430430
domain = 'qm'
431-
project_class = _QMProject
431+
project_class = QMProject
432432
supports_configs = True
433433
supports_components = True
434434
supports_reportable_rest = True

elmclient/_relm.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
#################################################################################################
3434

35-
class _RELMProject(_project._Project):
35+
class RELMProject(_project._Project):
3636
def __init__(self, name, project_uri, app, is_optin=False, singlemode=False,defaultinit=False):
3737
super().__init__(name, project_uri, app, is_optin,singlemode, defaultinit=defaultinit)
3838
self.hooks = []
@@ -46,7 +46,7 @@ def __init__(self, name, project_uri, app, is_optin=False, singlemode=False,defa
4646
#@utils.mixinomatic
4747
class RELMApp(_app._App):
4848
domain = 'relm'
49-
project_class = _RELMProject
49+
project_class = RELMProject
5050
supports_configs = False
5151
supports_components = False
5252
supports_reportable_rest = False

elmclient/_rm.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class _RM_PA_changeset( _config._Changeset,_RMProject):
6565
pass
6666

6767
@utils.mixinomatic
68-
class _RMProject(_project._Project):
68+
class RMProject(_project._Project):
6969
# A project
7070
# NOTE there is a derived class RMComponent used for RM components - it doesn't offer any
7171
# functionality, and is a separate class only so it's easier to see whether an instance is a component or the overall project
@@ -947,7 +947,7 @@ def list_components( self ):
947947

948948
def _create_component_api(self, component_prj_url, component_name, confs_to_load):
949949
logger.info( f"CREATE RM COMPONENT {self=} {component_prj_url=} {component_name=} {self.app=} {self.is_optin=} {self.singlemode=}" )
950-
result = _RMComponent(component_name, component_prj_url, self.app, self.is_optin, self.singlemode, defaultinit=False, project=self)
950+
result = RMComponent(component_name, component_prj_url, self.app, self.is_optin, self.singlemode, defaultinit=False, project=self)
951951
result.confs_to_load = confs_to_load
952952
return result
953953

@@ -1125,7 +1125,7 @@ def get_default_stream_name( self ):
11251125

11261126
#################################################################################################
11271127

1128-
class _RMComponent(_RMProject):
1128+
class RMComponent(RMProject):
11291129
def __init__(self, name, project_uri, app, is_optin=False, singlemode=False,defaultinit=True, project=None):
11301130
if not project:
11311131
raise Exception( "You must provide a project instance when creating a component" )
@@ -1275,9 +1275,9 @@ def deliver_changeset( self ):
12751275
#################################################################################################
12761276

12771277
@utils.mixinomatic
1278-
class _RMApp(_app._App, oslcqueryapi._OSLCOperations_Mixin, _typesystem.Type_System_Mixin):
1278+
class RMApp(_app._App, oslcqueryapi._OSLCOperations_Mixin, _typesystem.Type_System_Mixin):
12791279
domain = 'rm'
1280-
project_class = _RMProject
1280+
project_class = RMProject
12811281
supports_configs = True
12821282
supports_components = True
12831283
supports_reportable_rest = True

0 commit comments

Comments
 (0)