casaconfig 1.0.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.
casaconfig/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ """
2
+ Interface specification for all user facing external functions in the casaconfig package.
3
+ """
4
+ # __init__.py
5
+ from .private.pull_data import pull_data
6
+ from .private.data_available import data_available
7
+ from .private.data_update import data_update
8
+ from .private.do_auto_updates import do_auto_updates
9
+ from .private.measures_available import measures_available
10
+ from .private.measures_update import measures_update
11
+ from .private.update_all import update_all
12
+ from .private.set_casacore_path import set_casacore_path
13
+ from .private.get_config import get_config
14
+ from .private.get_data_info import get_data_info
15
+ from .private.CasaconfigErrors import *
casaconfig/__main__.py ADDED
@@ -0,0 +1,143 @@
1
+ import sys
2
+ import os
3
+ from casaconfig.private.get_argparser import get_argparser
4
+
5
+ parser = get_argparser(add_help=True)
6
+
7
+ # get_argparser supplies configfile, noconfig, nositeconfig
8
+ # add measurespath, pull-data, data-update, measures-update, update-all, reference-testing, current-data
9
+
10
+ parser.prog = "casaconfig"
11
+
12
+ # measurespath will default to the value in config if not set here
13
+ parser.add_argument( "--measurespath", dest='measurespath', default=None,
14
+ help="location of casarundata")
15
+
16
+ parser.add_argument( "--pull-data", dest='pulldata', action='store_const', const=True, default=False,
17
+ help="invoke pull_data() to populate measurespath with latest casarundata")
18
+ parser.add_argument( "--data-update", dest='dataupdate', action='store_const', const=True, default=False,
19
+ help="invoke data_update() to update measurespath to the latest casarundata")
20
+ parser.add_argument( "--measures-update", dest='measuresupdate', action='store_const', const=True, default=False,
21
+ help="invoke measures_update() to update measurespath to the latest measures data")
22
+ parser.add_argument( "--update-all", dest='updateall', action='store_const', const=True, default=False,
23
+ help="invoke update_all() to populate (update) measurespath with the latest casarundata and measures data")
24
+ parser.add_argument( "--reference-testing", action='store_const', const=True, dest='referencetesting', default=False,
25
+ help="set measurespath to the casarundata when this version was produced, used for testing purposes")
26
+ parser.add_argument( "--current-data", dest='currentdata', action='store_const', const=True, default=False,
27
+ help="print out a summary of the current casarundata and measures data installed in measurespath and then exit")
28
+ parser.add_argument( "--summary", dest='summary', action='store_const', const=True, default=False,
29
+ help="print out a summary of casaconfig data handling and the exit")
30
+ parser.add_argument("--force", dest='force', action='store_const', const=True, default=False,
31
+ help="force an update using the force=True option to update_all, data_update, and measures_update")
32
+
33
+ # initialize the configuration to be used
34
+ flags,args = parser.parse_known_args(sys.argv)
35
+ from casaconfig import config
36
+
37
+ # import the casaconfig module
38
+ import casaconfig
39
+
40
+ # make sure measurespath reflects any command line value
41
+ if flags.measurespath is None:
42
+ flags.measurespath = config.measurespath
43
+ else:
44
+ config.measurespath = flags.measurespath
45
+
46
+ # watch for measurespath of None, that likely means that casasiteconfig.py is in use and this has not been set. It can't be used then.
47
+ try:
48
+ if flags.measurespath is None:
49
+ print("measurespath has been set to None in the user or site config file.")
50
+ print("Either provide a measurespath on the casaconfig command line or edit the user or site config file to set measurespath to a location.")
51
+ sys.exit(1)
52
+
53
+ # do any expanduser and abspath - this is what should be used
54
+ measurespath = os.path.abspath(os.path.expanduser(flags.measurespath))
55
+
56
+ if flags.currentdata:
57
+ if not os.path.exists(measurespath) or not os.path.isdir(measurespath):
58
+ print("No data installed at %s. The measurespath does not exist or is not a directory." % measurespath)
59
+ else:
60
+ print("current data installed at %s" % measurespath)
61
+ dataInfo = casaconfig.get_data_info(measurespath)
62
+
63
+ # casarundata
64
+ casarunInfo = dataInfo['casarundata']
65
+ if casarunInfo is None or casarunInfo['version'] == "invalid":
66
+ print("No casarundata found (missing readme.txt and not obviously legacy casa data).")
67
+ if casarunInfo['version'] == "error":
68
+ print("Unexpected casarundata readme.txt content; casarundata should be reinstalled.")
69
+ elif casarunInfo['version'] == "unknown":
70
+ print("casarundata version is unknown (probably legacy casa data not maintained by casaconfig).")
71
+ else:
72
+ currentVersion = casarunInfo['version']
73
+ currentDate = casarunInfo['date']
74
+ print('casarundata version %s installed on %s' % (currentVersion, currentDate))
75
+
76
+ # measures
77
+ measuresInfo = dataInfo['measures']
78
+ if measuresInfo is None or measuresInfo['version'] == "invalid":
79
+ print("No measures data found (missing readme.txt and not obviously legacy measures data).")
80
+ if measuresInfo['version'] == "error":
81
+ print("Unexpected measures readme.txt content; measures should be reinstalled.")
82
+ elif measuresInfo['version'] == "unknown":
83
+ print("measures version is unknown (probably legacy measures data not maintained by casaconfig).")
84
+ else:
85
+ currentVersion = measuresInfo['version']
86
+ currentDate = measuresInfo['date']
87
+ print('measures version %s installed on %s' % (currentVersion, currentDate))
88
+
89
+ # ignore any other arguments
90
+ elif flags.summary:
91
+ from casaconfig.private.summary import summary
92
+ summary(config)
93
+ # ignore any other arguments
94
+ else:
95
+ if flags.referencetesting:
96
+ print("reference testing using pull_data and 'release' version into %s" % measurespath)
97
+ casaconfig.pull_data(measurespath,'release')
98
+ # ignore all other options
99
+ else:
100
+ # watch for nothing actually set
101
+ if not flags.updateall and not flags.pulldata and not flags.dataupdate and not flags.measuresupdate:
102
+ parser.print_help()
103
+ sys.exit(1)
104
+ # the update options, update all does everything, no need to invoke anything else
105
+ print("Checking for updates into %s" % measurespath)
106
+ if flags.updateall:
107
+ casaconfig.update_all(measurespath,force=flags.force)
108
+ else:
109
+ # do any pull_update first
110
+ if flags.pulldata:
111
+ casaconfig.pull_data(measurespath)
112
+ # then data_update, not necessary if pull_data just happened
113
+ if flags.dataupdate and not flags.pulldata:
114
+ casaconfig.data_update(measurespath, force=flags.force)
115
+ # then measures_update
116
+ if flags.measuresupdate:
117
+ casaconfig.measures_update(measurespath, force=flags.force)
118
+
119
+ except casaconfig.UnsetMeasurespath as exc:
120
+ # UnsetMeasurespath should not happen because measurespath is checked to not be None above, but just in case
121
+ print(str(exc))
122
+ print("This exception should not happen, check the previous messages for additional information and try a different path")
123
+ sys.exit(1)
124
+
125
+ except casaconfig.BadReadme as exc:
126
+ print(str(exc))
127
+ sys.exit(1)
128
+
129
+ except casaconfig.RemoteError as exc:
130
+ print(str(exc))
131
+ print("This is likely due to no network connection or bad connectivity to a remote site, wait and try again")
132
+ sys.exit(1)
133
+
134
+ except casaconfig.BadLock as exc:
135
+ # the output produced by the update functions is sufficient, just re-echo the exception text itself
136
+ print(str(exc))
137
+ sys.exit(1)
138
+
139
+ except casaconfig.NotWritable as exc:
140
+ print(str(exc))
141
+ sys.exit(1)
142
+
143
+ sys.exit(0)
casaconfig/config.py ADDED
@@ -0,0 +1,155 @@
1
+ ########################################################################
2
+ #
3
+ # Copyright (C) 2022
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: aips2-request@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
+ '''
29
+ Configuration state for all CASA python packages.
30
+ DO NOT ADD new configuration variables here. Instead, add them in
31
+ private/config_defaults_static.py.
32
+ '''
33
+ from .private import config_defaults as _config_defaults
34
+ import traceback as __traceback
35
+ import sys as __sys
36
+ import os as __os
37
+ import pkgutil as __pkgutil
38
+ from .private import io_redirect as _io
39
+ from .private.get_argparser import get_argparser as __get_argparser
40
+
41
+ ## dictionary to keep track of errors encountered
42
+ __errors_encountered = { }
43
+
44
+ def _standard_config_path( ):
45
+ standard_siteconfig_paths = [ '/opt/casa/casasiteconfig.py',
46
+ '/home/casa/casasiteconfig.py' ]
47
+
48
+ if 'CASASITECONFIG' in __os.environ:
49
+ f = __os.environ.get('CASASITECONFIG')
50
+ # if set, it must be a fully qualified file (leading '/') that exists
51
+ if f.find('/')==0 and __os.path.isfile(f):
52
+ return [ f ]
53
+ else:
54
+ global __errors_encountered
55
+ __errors_encountered[f] = f'CASASITECONFIG environment variable set to a path ({f}) which does not exist or is not fully qualified.'
56
+ print( f'Warning: {__errors_encountered[f]}', file=__sys.stderr )
57
+ return [ ]
58
+
59
+ for f in standard_siteconfig_paths:
60
+ if __os.path.isfile(f):
61
+ return [ f ]
62
+ return [ 'casasiteconfig' ]
63
+
64
+ ## list of config variables
65
+ __defaults = [ x for x in dir(_config_defaults) if not x.startswith('_') ]
66
+
67
+ ## get the ArgumentParser with the arguments needed by casaconfig, help is turned off
68
+ __parser = __get_argparser()
69
+ __flags,__args = __parser.parse_known_args(__sys.argv)
70
+ __user_config = [ ] if __flags.noconfig else [ __os.path.abspath( __os.path.expanduser( __flags.configfile )) ]
71
+ __site_config = [ ] if __flags.nositeconfig else _standard_config_path( )
72
+
73
+ ## files to be evaluated/loaded
74
+ __config_files = [ * __site_config , *__user_config ]
75
+ __loaded_config_files = [ __file__ ]
76
+
77
+ ## evaluate config files
78
+ ## ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
79
+ ## If the the execution mode of python is module execution (e.g. "python3 -m casatools") then
80
+ ## the output from the configuration files is thrown away. Otherwise, all output from the
81
+ ## configuration files is passed through. This allows for output from module execution to
82
+ ## be used by scripts.
83
+ ##
84
+ _module_execution = len(__sys.argv) > 0 and __sys.argv[0] == '-m'
85
+ with _io.all_redirected(to=__os.devnull) if _module_execution else _io.no_redirect( ):
86
+ for __f in __config_files :
87
+ if __f.find('/') >= 0:
88
+ if __os.path.exists(__f):
89
+ ## config file is a fully qualified path
90
+ try:
91
+ __orig = { k: _config_defaults._globals( )[k] for k in __defaults }
92
+ exec( open(__f).read( ), __orig )
93
+ except Exception as e:
94
+ __errors_encountered[__f] = __traceback.format_exc( )
95
+ else:
96
+ for __v in __defaults:
97
+ _config_defaults._globals( )[__v] = __orig[__v]
98
+ __loaded_config_files.append( __f )
99
+ else:
100
+ ## config file is a package name
101
+ __pkg = __pkgutil.get_loader(__f)
102
+ if __pkg is not None:
103
+ try:
104
+ __orig = { k: _config_defaults._globals( )[k] for k in __defaults }
105
+ exec(open(__pkg.get_filename( )).read( ),__orig)
106
+ except Exception as e:
107
+ __errors_encountered[__pkg.get_filename( )] = __traceback.format_exc( )
108
+ else:
109
+ for __v in __defaults:
110
+ _config_defaults._globals( )[__v] = __orig[__v]
111
+ __loaded_config_files.append( __pkg.get_filename( ) )
112
+
113
+ # if datapath is empty here, set it to [measurespath]
114
+ if len(_config_defaults.datapath) == 0:
115
+ # watch for measurespath = None, which likely means the casasiteconfig.py is in use and has not been set
116
+ # it's up to downstream acts to catch that and give some feedback, but don't use it here if it's None
117
+ if _config_defaults.measurespath is not None:
118
+ _config_defaults.datapath = [ _config_defaults.measurespath ]
119
+
120
+ # the names of config values that are path that need to be expanded here
121
+ __path_names = ["cachedir","datapath","measurespath","logfile","iplogfile","startupfile"]
122
+
123
+ for __v in __defaults:
124
+ globals()[__v] = getattr(_config_defaults,__v,None)
125
+ if (__v in __path_names) :
126
+ # expand ~ or ~user constructs and make sure they are absolute paths
127
+ if (type(globals()[__v]) is list) :
128
+ # None values cause problems with expanduser, do these individually
129
+ # None values aren't useful in a list, don't carry them forward
130
+ __vlist = []
131
+ for __vval in globals()[__v]:
132
+ if __vval is not None:
133
+ __vval = __os.path.abspath(__os.path.expanduser(__vval))
134
+ __vlist.append(__vval)
135
+ else:
136
+ # debugging for now
137
+ print("None value seen in config parameter list %s, skipped" % __v)
138
+ print("__loaded_config_files : ")
139
+ for __f in __loaded_config_files:
140
+ print(" %s" % __f)
141
+
142
+ globals()[__v] = __vlist
143
+ else:
144
+ # watch for None values here (possibly also in the list, but just here for now)
145
+ if globals()[__v] is not None:
146
+ globals()[__v] = __os.path.abspath(__os.path.expanduser(globals()[__v]))
147
+ else:
148
+ pass
149
+ # debugging
150
+ # print("None value seen while expanding path-like fields for config parameter %s" % __v)
151
+
152
+ def load_success( ):
153
+ return __loaded_config_files
154
+ def load_failure( ):
155
+ return __errors_encountered
@@ -0,0 +1,42 @@
1
+ # Copyright 2024 AUI, Inc. Washington DC, USA
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """
15
+ this module will be included in the api
16
+ """
17
+
18
+ class AutoUpdatesNotAllowed(Exception):
19
+ """Raised when a path does not exist or is not owned by the user"""
20
+ pass
21
+
22
+ class BadLock(Exception):
23
+ """Raised when the lock file is not empty and a lock attempt was made"""
24
+ pass
25
+
26
+ class BadReadme(Exception):
27
+ """Raised when a readme.txt file does not contain the expected contents"""
28
+ pass
29
+
30
+ class NoReadme(Exception):
31
+ """Raised when the readme.txt file is not found at path (path also may not exist)"""
32
+
33
+ class RemoteError(Exception):
34
+ """Raised when there is an error fetching some remote content"""
35
+ pass
36
+
37
+ class NotWritable(Exception):
38
+ """Raised when the path is not writable by the user"""
39
+
40
+ class UnsetMeasurespath(Exception):
41
+ """Raised when a path argument is None"""
42
+ pass
File without changes
@@ -0,0 +1,19 @@
1
+ # An example site config file.
2
+ # Place this in a location checked by casaconfig :
3
+ # /opt/casa/casasiteconfig.py
4
+ # /home/casa/casasiteconfig.py
5
+ # the environment value CASASITECONFIG - use the fully qualified path
6
+ # anywhere in the python path, e.g. the site-packages directory in the CASA being used.
7
+ #
8
+
9
+ # This file should be edited to set measurespath as appropriate
10
+
11
+ # Set this to point to the location where the site maintained casarundata can be found
12
+ # by default datapath will include measurespath
13
+
14
+ measurespath = "/path/to/installed/casarundata"
15
+
16
+ # turn off all auto updates of data
17
+
18
+ measures_auto_update = False
19
+ data_auto_update = False
@@ -0,0 +1,50 @@
1
+ ########################################################################
2
+ #
3
+ # Copyright (C) 2022
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: aips2-request@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
+ '''
29
+ Default values for the configuration variables for all CASA python packages.
30
+ DO NOT ADD new configuration variables here. Instead, add them in
31
+ config_defaults_static.py (found in the same directory as this file).
32
+ '''
33
+ import os as _os
34
+ import sys as _sys
35
+ import time as _time
36
+ import pkgutil as _pkgutil
37
+
38
+ from .get_argparser import get_argparser as __get_argparser
39
+
40
+ ## get the ArgumentParser with the arguments needed by casaconfig, help is turned off
41
+ ## this is used to supply command line configuration variales to the static defaults
42
+ ## specification.
43
+ __parser = __get_argparser()
44
+ __flags,__args = __parser.parse_known_args(_sys.argv)
45
+
46
+ def _globals( ):
47
+ return globals()
48
+
49
+ exec( open(_os.path.join(_os.path.dirname(__file__),'config_defaults_static.py')).read( ), globals( ) )
50
+
@@ -0,0 +1,56 @@
1
+ # lsit of paths where CASA should search for data subdirectories. Default [measurespath].
2
+ datapath = [ ]
3
+
4
+ # location of required measures data, takes precedence over any measures data also present in datapath.
5
+ measurespath = "~/.casa/data"
6
+
7
+ # automatically update measures data if not current (measurespath must be owned by the user)
8
+ # when data_auto_update is True then measures_auto_update MUST also be True
9
+ measures_auto_update = True
10
+
11
+ # automatically update casarundata and measures data if not current (measurespath must be owned by the user)
12
+ data_auto_update = True
13
+
14
+ # location of the optional user's startup.py
15
+ startupfile = '~/.casa/startup.py'
16
+
17
+ # location of the cachedir
18
+ cachedir = '~/.casa'
19
+
20
+ # log file path/name
21
+ logfile='casa-%s.log' % _time.strftime("%Y%m%d-%H%M%S", _time.gmtime())
22
+
23
+ # do not create a log file when True, If True, then any logfile value is ignored and there is no log file
24
+ nologfile = False
25
+
26
+ # print log output to terminal when True (in addition to any logfile and CASA logger)
27
+ log2term = False
28
+
29
+ # do not start the CASA logger GUI when True
30
+ nologger = False
31
+
32
+ # avoid starting GUI tools when True. If True then the CASA logger is not started even if nologger is False
33
+ nogui = False
34
+
35
+ # the IPython prompt color scheme. Must be one of "Neutral", "NoColor", "Linux" or "LightBG", default "Neutral"
36
+ # if an invalid color is given a warning message is printed and logged but CASA continues using the default color
37
+ colors = "Neutral"
38
+
39
+ # startup without a graphical backend if True
40
+ agg = False
41
+
42
+ # attempt to load the pipeline modules and set other options appropriate for pipeline use if True
43
+ # when pipeline is True then agg will be assumed to be true even if agg is set to False here or on the command line
44
+ pipeline = False
45
+
46
+ # create and use an IPython log using the iplogfile path
47
+ iplog = False
48
+
49
+ # the IPython log file path name to be used when iplog is True
50
+ iplogfile='ipython-%s.log' % _time.strftime("%Y%m%d-%H%M%S", _time.gmtime())
51
+
52
+ # include the user's local site-packages in the python path if True. May conflict with CASA modules
53
+ user_site = False
54
+
55
+ # verbosity level for casaconfig
56
+ casaconfig_verbose = 1
@@ -0,0 +1,88 @@
1
+ # Copyright 2023 AUI, Inc. Washington DC, USA
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """
15
+ this module will be included in the api
16
+ """
17
+
18
+ def data_available():
19
+ """
20
+ List available casarundata versions on CASA server at https://go.nrao.edu/casarundata
21
+
22
+ This returns a list of the casarundata versions available on the CASA
23
+ server. The version parameter of data_update must be one
24
+ of the values in that list if set (otherwise the most recent version
25
+ in this list is used).
26
+
27
+ A casarundata version is the filename of the tarball and look
28
+ like "casarundata.x.y.z.tar.*" (different compressions may be used by CASA without
29
+ changing casaconfig functions that use those tarballs). The full filename is
30
+ the casarundata version expected in casaconfig functions.
31
+
32
+ Parameters
33
+ None
34
+
35
+ Returns
36
+ list - version names returned as list of strings
37
+
38
+ Raises
39
+ - casaconfig.RemoteError - Raised when there is an error fetching some remote content
40
+ - Exception - Unexpected exception while getting list of available casarundata versions
41
+
42
+ """
43
+
44
+ import html.parser
45
+ import urllib.request
46
+ import urllib.error
47
+ import ssl
48
+ import certifi
49
+
50
+ from casaconfig import RemoteError
51
+
52
+ class LinkParser(html.parser.HTMLParser):
53
+ def reset(self):
54
+ super().reset()
55
+ self.rundataList = []
56
+
57
+ def handle_starttag(self, tag, attrs):
58
+ if tag == 'a':
59
+ for (name, value) in attrs:
60
+ # only care if this is an href and the value starts with
61
+ # casarundata and has '.tar.' somewhere later and does not end in .md5
62
+ if name == 'href' and (value.startswith('casarundata') and (value.rfind('.tar')>11) and (value[-4:] != '.md5')):
63
+ # only add it to the list if it's not already there
64
+ if (value not in self.rundataList):
65
+ self.rundataList.append(value)
66
+
67
+ try:
68
+ context = ssl.create_default_context(cafile=certifi.where())
69
+ with urllib.request.urlopen('https://go.nrao.edu/casarundata/', context=context, timeout=400) as urlstream:
70
+ parser = LinkParser()
71
+ encoding = urlstream.headers.get_content_charset() or 'UTF-8'
72
+ for line in urlstream:
73
+ parser.feed(line.decode(encoding))
74
+
75
+ # return the sorted list, earliest versions are first, newest is last
76
+ return sorted(parser.rundataList)
77
+
78
+ except urllib.error.URLError as urlerr:
79
+ raise RemoteError("Unable to retrieve list of available casarundata versions : " + str(urlerr)) from None
80
+
81
+ except Exception as exc:
82
+ msg = "Unexpected exception while getting list of available casarundata versions : " + str(exc)
83
+ raise Exception(msg)
84
+
85
+ # nothing to return if it got here, must have been an exception
86
+ return []
87
+
88
+