cubevis 0.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cubevis/LICENSE.rst +500 -0
- cubevis/__icons__/20px/fast-backward.svg +13 -0
- cubevis/__icons__/20px/fast-forward.svg +13 -0
- cubevis/__icons__/20px/step-backward.svg +12 -0
- cubevis/__icons__/20px/step-forward.svg +12 -0
- cubevis/__icons__/add-chan.png +0 -0
- cubevis/__icons__/add-chan.svg +84 -0
- cubevis/__icons__/add-cube.png +0 -0
- cubevis/__icons__/add-cube.svg +186 -0
- cubevis/__icons__/drag.png +0 -0
- cubevis/__icons__/drag.svg +109 -0
- cubevis/__icons__/mask-selected.png +0 -0
- cubevis/__icons__/mask.png +0 -0
- cubevis/__icons__/mask.svg +1 -0
- cubevis/__icons__/new-layer-sm-selected.png +0 -0
- cubevis/__icons__/new-layer-sm-selected.svg +88 -0
- cubevis/__icons__/new-layer-sm.png +0 -0
- cubevis/__icons__/new-layer-sm.svg +15 -0
- cubevis/__icons__/reset.png +0 -0
- cubevis/__icons__/reset.svg +11 -0
- cubevis/__icons__/sub-chan.png +0 -0
- cubevis/__icons__/sub-chan.svg +71 -0
- cubevis/__icons__/sub-cube.png +0 -0
- cubevis/__icons__/sub-cube.svg +95 -0
- cubevis/__icons__/zoom-to-fit.png +0 -0
- cubevis/__icons__/zoom-to-fit.svg +21 -0
- cubevis/__init__.py +58 -0
- cubevis/__js__/bokeh-3.6.1.min.js +728 -0
- cubevis/__js__/bokeh-tables-3.6.1.min.js +119 -0
- cubevis/__js__/bokeh-widgets-3.6.1.min.js +141 -0
- cubevis/__js__/casalib.min.js +1 -0
- cubevis/__js__/cubevisjs.min.js +62 -0
- cubevis/__version__.py +1 -0
- cubevis/apps/__init__.py +44 -0
- cubevis/apps/_createmask.py +461 -0
- cubevis/apps/_createregion.py +513 -0
- cubevis/apps/_interactiveclean.py +3260 -0
- cubevis/apps/_interactiveclean_wrappers.py +130 -0
- cubevis/apps/_ms_raster.py +815 -0
- cubevis/apps/_plotants.py +286 -0
- cubevis/apps/_plotbandpass.py +7 -0
- cubevis/bokeh/__init__.py +29 -0
- cubevis/bokeh/annotations/__init__.py +1 -0
- cubevis/bokeh/annotations/_ev_poly_annotation.py +6 -0
- cubevis/bokeh/components/__init__.py +28 -0
- cubevis/bokeh/format/__init__.py +31 -0
- cubevis/bokeh/format/_time_ticks.py +44 -0
- cubevis/bokeh/format/_wcs_ticks.py +45 -0
- cubevis/bokeh/models/__init__.py +4 -0
- cubevis/bokeh/models/_edit_span.py +7 -0
- cubevis/bokeh/models/_ev_text_input.py +6 -0
- cubevis/bokeh/models/_tip.py +37 -0
- cubevis/bokeh/models/_tip_button.py +50 -0
- cubevis/bokeh/sources/__init__.py +35 -0
- cubevis/bokeh/sources/_data_pipe.py +258 -0
- cubevis/bokeh/sources/_image_data_source.py +83 -0
- cubevis/bokeh/sources/_image_pipe.py +581 -0
- cubevis/bokeh/sources/_spectra_data_source.py +55 -0
- cubevis/bokeh/sources/_updatable_data_source.py +189 -0
- cubevis/bokeh/state/__init__.py +34 -0
- cubevis/bokeh/state/_initialize.py +164 -0
- cubevis/bokeh/state/_javascript.py +53 -0
- cubevis/bokeh/state/_palette.py +58 -0
- cubevis/bokeh/state/_session.py +44 -0
- cubevis/bokeh/state/js/bokeh-2.4.1.min.js +596 -0
- cubevis/bokeh/state/js/bokeh-gl-2.4.1.min.js +74 -0
- cubevis/bokeh/state/js/bokeh-tables-2.4.1.min.js +132 -0
- cubevis/bokeh/state/js/bokeh-widgets-2.4.1.min.js +118 -0
- cubevis/bokeh/state/js/casaguijs-v0.0.4.0-b2.4.min.js +49 -0
- cubevis/bokeh/state/js/casaguijs-v0.0.5.0-b2.4.min.js +49 -0
- cubevis/bokeh/state/js/casaguijs-v0.0.6.0-b2.4.min.js +49 -0
- cubevis/bokeh/state/js/casalib-v0.0.1.min.js +1 -0
- cubevis/bokeh/tools/__init__.py +31 -0
- cubevis/bokeh/tools/_cbreset_tool.py +52 -0
- cubevis/bokeh/tools/_drag_tool.py +61 -0
- cubevis/bokeh/utils/__init__.py +35 -0
- cubevis/bokeh/utils/_axes_labels.py +94 -0
- cubevis/bokeh/utils/_svg_icon.py +136 -0
- cubevis/data/__init__.py +1 -0
- cubevis/data/casaimage/__init__.py +114 -0
- cubevis/data/measurement_set/__init__.py +7 -0
- cubevis/data/measurement_set/_ms_data.py +178 -0
- cubevis/data/measurement_set/processing_set/__init__.py +30 -0
- cubevis/data/measurement_set/processing_set/_ps_concat.py +98 -0
- cubevis/data/measurement_set/processing_set/_ps_coords.py +78 -0
- cubevis/data/measurement_set/processing_set/_ps_data.py +213 -0
- cubevis/data/measurement_set/processing_set/_ps_io.py +55 -0
- cubevis/data/measurement_set/processing_set/_ps_raster_data.py +154 -0
- cubevis/data/measurement_set/processing_set/_ps_select.py +91 -0
- cubevis/data/measurement_set/processing_set/_ps_stats.py +218 -0
- cubevis/data/measurement_set/processing_set/_xds_data.py +149 -0
- cubevis/plot/__init__.py +1 -0
- cubevis/plot/ms_plot/__init__.py +29 -0
- cubevis/plot/ms_plot/_ms_plot.py +242 -0
- cubevis/plot/ms_plot/_ms_plot_constants.py +22 -0
- cubevis/plot/ms_plot/_ms_plot_selectors.py +348 -0
- cubevis/plot/ms_plot/_raster_plot.py +292 -0
- cubevis/plot/ms_plot/_raster_plot_inputs.py +116 -0
- cubevis/plot/ms_plot/_xds_plot_axes.py +110 -0
- cubevis/private/__java__/xml-casa-assembly-1.86.jar +0 -0
- cubevis/private/_gclean.py +798 -0
- cubevis/private/casashell/createmask.py +332 -0
- cubevis/private/casashell/iclean.py +4432 -0
- cubevis/private/casatasks/__init__.py +140 -0
- cubevis/private/casatasks/createmask.py +86 -0
- cubevis/private/casatasks/createregion.py +83 -0
- cubevis/private/casatasks/iclean.py +1831 -0
- cubevis/readme.rst +16 -0
- cubevis/remote/__init__.py +10 -0
- cubevis/remote/_gclean.py +61 -0
- cubevis/remote/_local.py +287 -0
- cubevis/remote/_remote_kernel.py +80 -0
- cubevis/toolbox/__init__.py +32 -0
- cubevis/toolbox/_app_context.py +74 -0
- cubevis/toolbox/_cube.py +3457 -0
- cubevis/toolbox/_region_list.py +197 -0
- cubevis/utils/_ResourceManager.py +86 -0
- cubevis/utils/__init__.py +620 -0
- cubevis/utils/_contextmgrchain.py +84 -0
- cubevis/utils/_conversion.py +93 -0
- cubevis/utils/_copydoc.py +55 -0
- cubevis/utils/_docenum.py +25 -0
- cubevis/utils/_import_protected_module.py +35 -0
- cubevis/utils/_logging.py +85 -0
- cubevis/utils/_pkgs.py +77 -0
- cubevis/utils/_regions.py +40 -0
- cubevis/utils/_static.py +66 -0
- cubevis/utils/_tiles.py +167 -0
- cubevis-0.5.2.dist-info/METADATA +151 -0
- cubevis-0.5.2.dist-info/RECORD +132 -0
- cubevis-0.5.2.dist-info/WHEEL +4 -0
- cubevis-0.5.2.dist-info/licenses/LICENSE +504 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
########################################################################
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2025
|
|
4
|
+
# Associated Universities, Inc. Washington DC, USA.
|
|
5
|
+
#
|
|
6
|
+
# This script is free software; you can redistribute it and/or modify it
|
|
7
|
+
# under the terms of the GNU Library General Public License as published by
|
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or (at your
|
|
9
|
+
# option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# This library is distributed in the hope that it will be useful, but WITHOUT
|
|
12
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
13
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
|
|
14
|
+
# License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU Library General Public License
|
|
17
|
+
# along with this library; if not, write to the Free Software Foundation,
|
|
18
|
+
# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
|
|
19
|
+
#
|
|
20
|
+
# Correspondence concerning AIPS++ should be adressed as follows:
|
|
21
|
+
# Internet email: casa-feedback@nrao.edu.
|
|
22
|
+
# Postal address: AIPS++ Project Office
|
|
23
|
+
# National Radio Astronomy Observatory
|
|
24
|
+
# 520 Edgemont Road
|
|
25
|
+
# Charlottesville, VA 22903-2475 USA
|
|
26
|
+
#
|
|
27
|
+
########################################################################
|
|
28
|
+
import hashlib
|
|
29
|
+
from astropy.wcs import WCS
|
|
30
|
+
from regions import PixCoord, PolygonPixelRegion
|
|
31
|
+
|
|
32
|
+
class _Line:
|
|
33
|
+
|
|
34
|
+
def __init__( self, style ):
|
|
35
|
+
if not ( 'color' in style and 'width' in style and 'alpha' in style and 'dash' in style ):
|
|
36
|
+
raise RuntimeError( 'style must contain color, width, alpha and dash' )
|
|
37
|
+
self.__style = style
|
|
38
|
+
|
|
39
|
+
def color( self ):
|
|
40
|
+
return self.__style['color']
|
|
41
|
+
|
|
42
|
+
def width( self ):
|
|
43
|
+
return self.__style['width']
|
|
44
|
+
|
|
45
|
+
def alpha( self ):
|
|
46
|
+
return self.__style['alpha']
|
|
47
|
+
|
|
48
|
+
def dash( self ):
|
|
49
|
+
return self.__style['dash']
|
|
50
|
+
|
|
51
|
+
class _Fill:
|
|
52
|
+
|
|
53
|
+
def __init__( self, fill ):
|
|
54
|
+
if not ( 'color' in fill and 'alpha' in fill and 'hatch' in fill ):
|
|
55
|
+
raise RuntimeError( 'fill must contain color, alpha and hatch' )
|
|
56
|
+
self.__fill = fill
|
|
57
|
+
|
|
58
|
+
def color( self ):
|
|
59
|
+
return self.__fill['color']
|
|
60
|
+
|
|
61
|
+
def alpha( self ):
|
|
62
|
+
return self.__fill['alpha']
|
|
63
|
+
|
|
64
|
+
def hatch( self ):
|
|
65
|
+
return self.__fill['hatch']
|
|
66
|
+
|
|
67
|
+
class Region:
|
|
68
|
+
|
|
69
|
+
### reuse astropy WCS objects based upon fits header strings
|
|
70
|
+
### to avoid reparsing
|
|
71
|
+
wcs_cache = { }
|
|
72
|
+
|
|
73
|
+
def __init__( self, name, xs, ys, channels, line, fill, wcs=None ):
|
|
74
|
+
if type(name) != str:
|
|
75
|
+
raise RuntimeError( 'region names are strings' )
|
|
76
|
+
if len(xs) != len(ys):
|
|
77
|
+
raise RuntimeError( 'X and Y values must be of equal length' )
|
|
78
|
+
|
|
79
|
+
self.__ste = { }
|
|
80
|
+
self.__ste['name'] = name
|
|
81
|
+
self.__ste['xs'] = xs
|
|
82
|
+
self.__ste['ys'] = ys
|
|
83
|
+
self.__ste['chan'] = channels
|
|
84
|
+
self.__ste['line'] = line
|
|
85
|
+
self.__ste['fill'] = fill
|
|
86
|
+
self.__ste['wcs'] = None
|
|
87
|
+
if wcs is not None:
|
|
88
|
+
if isinstance(wcs,WCS):
|
|
89
|
+
self.__ste['wcs'] = wcs
|
|
90
|
+
elif type(wcs) == str:
|
|
91
|
+
## Assume wcs is a FITS header
|
|
92
|
+
hash = hashlib.md5(wcs.encode()).hexdigest()
|
|
93
|
+
if hash not in Region.wcs_cache:
|
|
94
|
+
Region.wcs_cache[hash] = WCS( header=wcs )
|
|
95
|
+
self.__ste['wcs'] = Region.wcs_cache[hash]
|
|
96
|
+
else:
|
|
97
|
+
raise RuntimeError( f'''Regions initialized with non-FITS header representations of the WCS of type {type(wcs)} are not supported''' )
|
|
98
|
+
|
|
99
|
+
def __str__(self):
|
|
100
|
+
return self.__repr__( )
|
|
101
|
+
|
|
102
|
+
def __repr__(self):
|
|
103
|
+
return f'''<{self.__class__.__name__}({self.__ste['name']}) @ {hex(id(self))}>'''
|
|
104
|
+
|
|
105
|
+
def name( self ):
|
|
106
|
+
return self.__ste['name']
|
|
107
|
+
|
|
108
|
+
def xs( self ):
|
|
109
|
+
return self.__ste['xs']
|
|
110
|
+
|
|
111
|
+
def ys( self ):
|
|
112
|
+
return self.__ste['ys']
|
|
113
|
+
|
|
114
|
+
def vertices( self ):
|
|
115
|
+
return zip( self.__ste['xs'], self.__ste['ys'] )
|
|
116
|
+
|
|
117
|
+
def channels( self ):
|
|
118
|
+
return self.__ste['chan']
|
|
119
|
+
|
|
120
|
+
def line( self ):
|
|
121
|
+
return _Line( self.__ste['line'] )
|
|
122
|
+
|
|
123
|
+
def fill( self ):
|
|
124
|
+
return _Fill( self.__ste['fill'] )
|
|
125
|
+
|
|
126
|
+
def to_astropy_pixel( self ):
|
|
127
|
+
### It seems as though there is no way to create 4 dimensional pixel coordinates (RA,DEC,STOKES,CHAN)
|
|
128
|
+
### using astropy.
|
|
129
|
+
return PolygonPixelRegion( vertices=PixCoord( self.__ste['xs'], self.__ste['ys'],
|
|
130
|
+
### meta and visual are not available on regions v0.10 which
|
|
131
|
+
### seems to be the most recent version available with pip
|
|
132
|
+
#meta={ 'name': self.__ste['name'] },
|
|
133
|
+
#visual={ 'dash': self.__ste['line']['dash'],
|
|
134
|
+
# 'linewidth': self.__ste['line']['width'] }
|
|
135
|
+
) )
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def to_astropy_sky( self, wcs=None ):
|
|
139
|
+
if wcs is None:
|
|
140
|
+
wcs = self.__ste['wcs']
|
|
141
|
+
if wcs is None:
|
|
142
|
+
raise RuntimeError( 'No world coordinate system is available' )
|
|
143
|
+
### Astropy coordinates seem to be limited to two dimensions so the generalized
|
|
144
|
+
### WCS object (potentially with stokes and frequency axes) must be converted to
|
|
145
|
+
### a two coordinate description.
|
|
146
|
+
return self.to_astropy_pixel( ).to_sky( wcs.sub([1,2]) )
|
|
147
|
+
|
|
148
|
+
class _Iter:
|
|
149
|
+
def __init__( self, ste ):
|
|
150
|
+
self.__ste = ste
|
|
151
|
+
|
|
152
|
+
def __iter__(self):
|
|
153
|
+
for name, rgn in self.__ste['raw'].items( ):
|
|
154
|
+
yield ( name, Region( name, rgn['geometry']['xs'], rgn['geometry']['ys'],
|
|
155
|
+
rgn['channels'], rgn['styling']['line'],
|
|
156
|
+
rgn['styling']['fill'], wcs=self.__ste['wcs'] ) )
|
|
157
|
+
|
|
158
|
+
class RegionList:
|
|
159
|
+
'''This class is for representing regions within cubevis applications. Currently it only wowrks with the
|
|
160
|
+
`createregion` app. It is not (currently) designed as an end user class but rather as an output class for
|
|
161
|
+
the user.
|
|
162
|
+
|
|
163
|
+
Currently it only accepts a dictionary like for `regiondict`:
|
|
164
|
+
```
|
|
165
|
+
{'rg0': {'channels': [[0, 0]], 'geometry': {'xs': [33.51816859449925, 31.611101128190022, 121.40193309213468], 'ys': [195.13666993774888, 232.39613665715922, 229.12483086356568]}, 'styling': {'line': {'color': '#ffffff', 'width': 1, 'alpha': 1, 'dash': 'solid'}, 'fill': {'color': '#ffffff', 'alpha': 0, 'hatch': 'blank'}}}, 'rg1': {'channels': [[0, 0]], 'geometry': {'xs': [289.43821781788444, 347.2289277115166, 351.7707259199244], 'ys': [236.68983013019135, 232.27134277978703, 186.01834737432947]}, 'styling': {'line': {'color': '#ffffff', 'width': 1, 'alpha': 1, 'dash': 'solid'}, 'fill': {'color': '#ffffff', 'alpha': 0, 'hatch': 'blank'}}}, 'rg2': {'channels': [[0, 0]], 'geometry': {'xs': [346.576046788646, 334.82618711394673, 270.71434943666696], 'ys': [61.16382772497764, 9.769953019597281, 16.587407740876944]}, 'styling': {'line': {'color': '#ffffff', 'width': 1, 'alpha': 1, 'dash': 'solid'}, 'fill': {'color': '#ffffff', 'alpha': 0, 'hatch': 'blank'}}}, 'rg3': {'channels': [[0, 0]], 'geometry': {'xs': [17.33383873194185, 20.60032838342714, 105.68674392600984], 'ys': [65.03236450671528, 26.158256778791444, 15.71542171880079]}, 'styling': {'line': {'color': '#ffffff', 'width': 1, 'alpha': 1, 'dash': 'solid'}, 'fill': {'color': '#ffffff', 'alpha': 0, 'hatch': 'blank'}}}, 'rg4': {'channels': [[0, 0]], 'geometry': {'xs': [194.1150775408404, 186.7420925740613, 247.32924839610087, 257.7778686999655, 220.3835500015239], 'ys': [100.28458653562863, 138.0450054510982, 136.91400495710235, 109.48993737427867, 75.12723329938433]}, 'styling': {'line': {'color': '#ffffff', 'width': 1, 'alpha': 1, 'dash': 'solid'}, 'fill': {'color': '#ffffff', 'alpha': 0, 'hatch': 'blank'}}}}
|
|
166
|
+
```
|
|
167
|
+
The `wcs` parameter could either be a string representation of a FITS wcs header or it could be an
|
|
168
|
+
astropy `WCS` object.
|
|
169
|
+
'''
|
|
170
|
+
|
|
171
|
+
def __init__( self, regiondict, wcs ):
|
|
172
|
+
|
|
173
|
+
self.__ste = { 'wcs': wcs }
|
|
174
|
+
self.__summary_size = 22
|
|
175
|
+
|
|
176
|
+
if type(regiondict) != dict:
|
|
177
|
+
raise RuntimeError( 'RegionList must be initialized with a dictionary representation of regions' )
|
|
178
|
+
self.__ste['raw'] = regiondict
|
|
179
|
+
|
|
180
|
+
def __iter__(self):
|
|
181
|
+
return _Iter(self.__ste).__iter__( )
|
|
182
|
+
|
|
183
|
+
def __str__(self):
|
|
184
|
+
return self.__repr__( )
|
|
185
|
+
|
|
186
|
+
def __repr__(self):
|
|
187
|
+
added = 0
|
|
188
|
+
names = ''
|
|
189
|
+
keys = self.__ste['raw'].keys( )
|
|
190
|
+
for name in keys:
|
|
191
|
+
if len(names) + len(names) > self.__summary_size: break
|
|
192
|
+
names = names + (',' if len(names) > 0 else '') + name
|
|
193
|
+
added = added + 1
|
|
194
|
+
if added < len(keys):
|
|
195
|
+
names = f'''{names}...'''
|
|
196
|
+
return f'''<{self.__class__.__name__}[{names}] @ {hex(id(self))}>'''
|
|
197
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import weakref
|
|
2
|
+
import atexit
|
|
3
|
+
import asyncio
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from ._logging import get_logger
|
|
7
|
+
logger = get_logger()
|
|
8
|
+
|
|
9
|
+
class _ResourceManager:
|
|
10
|
+
""" This class acts as a single place to manage the destruction of system
|
|
11
|
+
resources (event_loops, ports, etc). To start with, get the singleton
|
|
12
|
+
instance via
|
|
13
|
+
|
|
14
|
+
import utils.resource_manager
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self):
|
|
17
|
+
# A list of webservers to close when the event loop is closed.
|
|
18
|
+
self.webservers_to_close = []
|
|
19
|
+
self.reg_at_exit(self, 'stop_asyncio_loop')
|
|
20
|
+
|
|
21
|
+
def reg_webserver(self, websockets_server):
|
|
22
|
+
""" Register a webserver to be closed when the event loop is stopped. """
|
|
23
|
+
self.webservers_to_close.append(weakref.ref(websockets_server))
|
|
24
|
+
|
|
25
|
+
def reg_at_exit(self, obj_or_func=None, fname="", *vargs):
|
|
26
|
+
""" Register a function or method to be called when python exits.
|
|
27
|
+
@param obj_or_func An object containing a method fname, or a global/static function.
|
|
28
|
+
@param fname If obj_or_func is an object instance, then this can be the method name
|
|
29
|
+
of the object to call.
|
|
30
|
+
@param vargs Any extra parameters to pass to the obj_or_func or fname function.
|
|
31
|
+
"""
|
|
32
|
+
if (fname == "" and callable(obj_or_func)):
|
|
33
|
+
atexit.register(obj_or_func, *vargs)
|
|
34
|
+
else:
|
|
35
|
+
ref = weakref.ref(obj_or_func)
|
|
36
|
+
atexit.register(self._call_on_ref, ref, fname, *vargs)
|
|
37
|
+
|
|
38
|
+
def stop_asyncio_loop(self):
|
|
39
|
+
""" Calls "stop" on the event_loop and closes any webservers registered
|
|
40
|
+
with reg_webserver. """
|
|
41
|
+
logger.debug("stop_asyncio_loop")
|
|
42
|
+
try:
|
|
43
|
+
event_loop = asyncio.get_running_loop()
|
|
44
|
+
self._close_webservers(event_loop)
|
|
45
|
+
event_loop.stop()
|
|
46
|
+
return
|
|
47
|
+
except RuntimeError:
|
|
48
|
+
self._close_webservers(None)
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
def _close_webserver(self, server_weakref, event_loop=None):
|
|
52
|
+
""" Close the given webserver. If event_loop != None, then use the loop
|
|
53
|
+
to wait, blocking until the server has finished closing. """
|
|
54
|
+
instance = server_weakref()
|
|
55
|
+
if (instance == None):
|
|
56
|
+
logger.debug("close_webserver(None)")
|
|
57
|
+
return
|
|
58
|
+
logger.debug(f"close_webserver({instance})")
|
|
59
|
+
instance.close()
|
|
60
|
+
if event_loop != None:
|
|
61
|
+
event_loop.run_until_complete(instance.wait_closed())
|
|
62
|
+
|
|
63
|
+
def _close_webservers(self, event_loop):
|
|
64
|
+
""" Calls _close_webserver for each server registered with reg_webserver. """
|
|
65
|
+
for webserver in self.webservers_to_close:
|
|
66
|
+
self._close_webserver(webserver, event_loop)
|
|
67
|
+
self.webservers_to_close.clear()
|
|
68
|
+
|
|
69
|
+
def _call_on_ref(self, ref, fname, *vargs):
|
|
70
|
+
""" Call the function named fname on the given object instance ref, aka:
|
|
71
|
+
|
|
72
|
+
ref.fname(*vargs)
|
|
73
|
+
"""
|
|
74
|
+
instance = ref()
|
|
75
|
+
if instance == None:
|
|
76
|
+
logger.debug('call_on_ref(None)')
|
|
77
|
+
return
|
|
78
|
+
if not hasattr(instance, fname):
|
|
79
|
+
logger.debug(f"call_on_ref({instance.__class__.__name__}.{fname} == None)")
|
|
80
|
+
return
|
|
81
|
+
instance_method = getattr(instance, fname)
|
|
82
|
+
if not callable(instance_method):
|
|
83
|
+
logger.debug(f"call_on_ref(non-callable {instance.__class__.__name__}.{fname})")
|
|
84
|
+
return
|
|
85
|
+
logger.debug(f"{instance.__class__.__name__}.{fname}()")
|
|
86
|
+
instance_method(*vargs)
|