Skip to content

Commit b9597cb

Browse files
author
David Turner
committed
Split off the utils code that writes new XGA configuration files to its own internal function in utils.py (_prep_xga_config_file), so that it can both be called by the _initialise_xga function, and be called in the top-level XGA init to ensure that a default-location configuration file is always created the first time somebody runs XGA.
1 parent 23173ce commit b9597cb

2 files changed

Lines changed: 62 additions & 34 deletions

File tree

xga/__init__.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
# This code is part of X-ray: Generate and Analyse (XGA), a module designed for the XMM Cluster Survey (XCS).
2-
# Last modified by David J Turner (djturner@umbc.edu) 4/27/26, 11:51 AM. Copyright (c) The Contributors.
1+
# This code is a part of X-ray: Generate and Analyse (XGA), a module designed for the XMM Cluster Survey (XCS).
2+
# Last modified by David J Turner (djturner@umbc.edu) 26/05/2026, 14:37. Copyright (c) The Contributors
33
from . import _version
44
__version__ = _version.get_versions()['version']
55

6+
# This function is what generates brand new XGA configuration files (and updates old-style existing
7+
# files, but that isn't relevant here) - we import and run it here so that the first time someone
8+
# runs XGA, the configuration file is set up in the default location.
9+
# This doesn't undermine the lazy-loading implemented in the __getattr__ function below, as this
10+
# function doesn't set any global constants, just makes sure the file exists.
11+
from .utils import _prep_xga_config_file
12+
_prep_xga_config_file()
13+
614
def __getattr__(name):
715
"""
816
A module level __getattr__ which allows us to lazily load the XGA configuration and census from the utils

xga/utils.py

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# This code is part of X-ray: Generate and Analyse (XGA), a module designed for the XMM Cluster Survey (XCS).
2-
# Last modified by David J Turner (djturner@umbc.edu) 5/21/26, 12:59 PM. Copyright (c) The Contributors.
1+
# This code is a part of X-ray: Generate and Analyse (XGA), a module designed for the XMM Cluster Survey (XCS).
2+
# Last modified by David J Turner (djturner@umbc.edu) 26/05/2026, 14:37. Copyright (c) The Contributors
33

44
import importlib.resources
55
import json
@@ -257,33 +257,31 @@ def _get_xspec_info() -> Tuple[Union[Version, None], List[str], List[str]]:
257257
return xspec_version, fit_methods, abund_tables
258258

259259

260-
def _initialise_xga():
260+
def _prep_xga_config_file() -> Tuple[dict, str, str]:
261261
"""
262-
The internal function that actually performs the XGA configuration and census loading, as well as
263-
checking for the availability of backend software.
264-
"""
265-
global _INITIALISED, CONFIG_PATH, CONFIG_FILE, xga_conf, VALID_CONFIG, USABLE, CENSUS, BLACKLIST, \
266-
COMBINED_INSTS, SAS_VERSION, SAS_AVAIL, ESASS_VERSION, ESASS_AVAIL, CIAO_VERSION, CIAO_AVAIL, \
267-
CALDB_VERSION, CALDB_AVAIL, XSPEC_VERSION, OUTPUT, NUM_CORES, CENSUS_FILES, BLACKLIST_FILES, \
268-
SASERROR_LIST, SASWARNING_LIST, XSPEC_FIT_METHOD, ABUND_TABLES
262+
This function prepares the XGA configuration file, either generating it for the first time (in the
263+
default location or a user-specified directory), updating it if it already exists and was set up for a
264+
previous version of XGA.
265+
266+
No global constant variables are set in this function, so the current state of the configuration
267+
dictionary, the configuration directory path, and the full path to the current configuration file
268+
are returned, so that _initialise_xga function can set them up as global constants.
269269
270+
:return: Local variables containing the current configuration dictionary, the current configuration
271+
path, and the current full path to the configuration file.
272+
:rtype: Tuple[dict, str, str]
273+
"""
274+
# ------------------ Preparing for config file reading/generation -------------------
270275
if 'XGA_CONFIG_DIR' in os.environ:
271-
CONFIG_PATH = os.path.abspath(os.environ['XGA_CONFIG_DIR'])
276+
cur_config_path = os.path.abspath(os.environ['XGA_CONFIG_DIR'])
272277
else:
273-
CONFIG_PATH = os.path.join(os.environ.get('XDG_CONFIG_HOME',
278+
cur_config_path = os.path.join(os.environ.get('XDG_CONFIG_HOME',
274279
os.path.join(os.path.expanduser('~'), '.config')), 'xga')
275280

276-
if not os.path.exists(CONFIG_PATH):
277-
os.makedirs(CONFIG_PATH)
278-
279-
CONFIG_FILE = os.path.join(CONFIG_PATH, 'xga.cfg')
281+
os.makedirs(cur_config_path, exist_ok=True)
280282

281-
# These are used for the observation census files
282-
CENSUS_FILES = {tel: os.path.join(CONFIG_PATH, tel, '{}_census.csv'.format(tel)) for tel in ALLOWED_INST}
283-
BLACKLIST_FILES = {tel: os.path.join(CONFIG_PATH, tel, '{}_blacklist.csv'.format(tel)) for tel in ALLOWED_INST}
283+
cur_config_file = os.path.join(cur_config_path, 'xga.cfg')
284284

285-
USABLE = {tele: False for tele in TELESCOPES}
286-
VALID_CONFIG = {tel: False for tel in TELESCOPES}
287285
# ------------- Creating/checking the entries in the configuration file -------------
288286
# This chunk of utils will be dedicated to making sure that the configuration file has been created (by default with
289287
# sections for every telescope that XGA supports), or that if it already exists it contains valid entries.
@@ -293,7 +291,7 @@ def _initialise_xga():
293291

294292
# In this case we find that the configuration file does not exist, and we set it up using the default sections and
295293
# configurations that were set up toward the top of this file
296-
if not os.path.exists(CONFIG_FILE):
294+
if not os.path.exists(cur_config_file):
297295
# Define a configuration object
298296
xga_default = ConfigParser()
299297
# This adds the overall XGA setup section - controls global things like where XGA generated files are stored, and
@@ -310,17 +308,17 @@ def _initialise_xga():
310308
xga_default[cur_sec_name] = tele_conf_sects[tel]
311309

312310
# The default configuration file is now written to the path that we expect to find the config file at
313-
with open(CONFIG_FILE, 'w') as new_cfg:
311+
with open(cur_config_file, 'w') as new_cfg:
314312
xga_default.write(new_cfg)
315313

316314
# First time run triggers this message - it used to be an error, and so XGA wouldn't advance beyond this point
317315
# with a new configuration file, but we want people to be able to use the product classes without configuring
318-
warn("This is the first time you've used XGA; to use most functionality you will need to configure {} to match "
319-
"your setup, though you can use product classes regardless.".format(CONFIG_FILE), stacklevel=2)
316+
warn(f"This is the first time you've used XGA; to use most functionality you will need to configure "
317+
f"{cur_config_file} to match your setup, though you can use product classes regardless.", stacklevel=2)
320318

321-
xga_conf = ConfigParser()
319+
cur_xga_conf = ConfigParser()
322320
# It would be nice to do configparser interpolation, but it wouldn't handle the lists of energy values
323-
xga_conf.read(CONFIG_FILE)
321+
cur_xga_conf.read(cur_config_file)
324322

325323
# If the current section name exists in xga_conf then all is well, there hasn't been an update to XGA which added
326324
# a new telescope installed - however if a section is missing then we need to add it. This is slightly inelegant
@@ -331,20 +329,42 @@ def _initialise_xga():
331329
altered = False
332330
for tel in TELESCOPES:
333331
cur_sec_name = "{}_FILES".format(tel.upper())
334-
if cur_sec_name not in xga_conf:
332+
if cur_sec_name not in cur_xga_conf:
335333
# If there isn't already a files section for one of the telescopes now supported by XGA, then we add it to
336334
# the existing configuration file
337-
xga_conf.add_section(cur_sec_name)
338-
xga_conf[cur_sec_name] = tele_conf_sects[tel]
335+
cur_xga_conf.add_section(cur_sec_name)
336+
cur_xga_conf[cur_sec_name] = tele_conf_sects[tel]
339337
altered = True
340338
# If we altered the existing configuration file, then we need to save the altered configuration to disk
341339
if altered:
342340
with open(CONFIG_FILE, 'w') as update_cfg:
343-
xga_conf.write(update_cfg)
341+
cur_xga_conf.write(update_cfg)
344342

345343
# As it turns out, the ConfigParser class is a pain to work with, so we're converting to a dict here
346344
# Addressing works just the same
347-
xga_conf = {str(sect): dict(xga_conf[str(sect)]) for sect in xga_conf}
345+
cur_xga_conf = {str(sect): dict(cur_xga_conf[str(sect)]) for sect in cur_xga_conf}
346+
347+
return cur_xga_conf, cur_config_path, cur_config_file
348+
349+
350+
def _initialise_xga():
351+
"""
352+
The internal function that actually performs the XGA configuration and census loading, as well as
353+
checking for the availability of backend software.
354+
"""
355+
global _INITIALISED, CONFIG_PATH, CONFIG_FILE, xga_conf, VALID_CONFIG, USABLE, CENSUS, BLACKLIST, \
356+
COMBINED_INSTS, SAS_VERSION, SAS_AVAIL, ESASS_VERSION, ESASS_AVAIL, CIAO_VERSION, CIAO_AVAIL, \
357+
CALDB_VERSION, CALDB_AVAIL, XSPEC_VERSION, OUTPUT, NUM_CORES, CENSUS_FILES, BLACKLIST_FILES, \
358+
SASERROR_LIST, SASWARNING_LIST, XSPEC_FIT_METHOD, ABUND_TABLES
359+
360+
xga_conf, CONFIG_PATH, CONFIG_FILE = _prep_xga_config_file()
361+
362+
# These are used for the observation census files
363+
CENSUS_FILES = {tel: os.path.join(CONFIG_PATH, tel, '{}_census.csv'.format(tel)) for tel in ALLOWED_INST}
364+
BLACKLIST_FILES = {tel: os.path.join(CONFIG_PATH, tel, '{}_blacklist.csv'.format(tel)) for tel in ALLOWED_INST}
365+
366+
USABLE = {tele: False for tele in TELESCOPES}
367+
VALID_CONFIG = {tel: False for tel in TELESCOPES}
348368

349369
# ------------- Final setup of important constants from the configuration file -------------
350370
# We make sure to create the absolute output path from what was specified in the configuration file

0 commit comments

Comments
 (0)