fab.magic

Automagic configuration and beamtime loading.

This module provides a convenient way to quickly set up the analyis environment. It makes some assumptions on how you use fab. If you prefer a more structured setup or finer control over the configuration process, please refer to the fab.settings, fab.instruments and fab.beamtime modules.

There are three main actions that you can access from this module:

  • Setting the configuration file
  • Defining the beamtime and loading beamtime metadata
  • Loading an instrument

If you are using multiple actions, the order of the actions is important. The configuration file should be set first, then the beamtime and finally the instrument imoprt.

Setting the configuration file

With the statement from fab.magic import config, fab will try to load a configuration file from the current directory or any of its parent directories. The file name should match fab_config*.toml. E.g. fab_config.toml and fab_config_musix.toml are valid names.

Defining the beamtime and loading beamtime metadata

With the statement from fab.magic import beamtime, fab will try to load a beamtime object giving access to the beamtime metadata created with the fablive library. If no beamtime number is provided in the configuration file, fab will try to autodetect it from the current directory.

Loading an instrument

With the statement from fab.magic import <instrument>, fab will import and instantiate the corresponding instrument as defined in the loaded configuration file.

Qiuckstart

In most cases, you will be running your analyis from within the beamtime folder (e.g. the /shared/ directory on maxwell). In this case, you can simply create a fab_config.toml file in your working directory (or it's parent directories) and then simply use:

from fab.magic import config, beamtime, <instrument>

That's it! You can now access the configuration, the beamtime metadata and the instrument. To load data, you can use the load method of the instrument. For an instrument named musix, this would look like:

from fab.magic import config, beamtime, musix

run = musix.load()
  1"""
  2Automagic configuration and beamtime loading.
  3
  4This module provides a convenient way to quickly set up the analyis
  5environment. It makes some assumptions on how you use `fab`. If you prefer a more structured
  6setup or finer control over the configuration process, please refer to the `fab.settings`,
  7`fab.instruments` and `fab.beamtime` modules.
  8
  9There are three main actions that you can access from this module:
 10- Setting the configuration file
 11- Defining the beamtime and loading beamtime metadata
 12- Loading an instrument
 13
 14If you are using multiple actions, the order of the actions is important. 
 15The configuration file should be set first, then the beamtime and finally the instrument imoprt.
 16
 17### Setting the configuration file
 18With the statement `from fab.magic import config`, `fab` will try to load a configuration file
 19from the current directory or any of its parent directories. The file name should match
 20`fab_config*.toml`. E.g. `fab_config.toml` and `fab_config_musix.toml` are valid names.
 21
 22### Defining the beamtime and loading beamtime metadata
 23With the statement `from fab.magic import beamtime`, `fab` will try to load a beamtime object
 24giving access to the beamtime metadata created with the `fablive` library. If no beamtime number
 25is provided in the configuration file, `fab` will try to autodetect it from the current directory.
 26
 27### Loading an instrument
 28With the statement `from fab.magic import <instrument>`, `fab` will import and instantiate the
 29corresponding instrument as defined in the loaded configuration file.
 30
 31## Qiuckstart
 32In most cases, you will be running your analyis from within the beamtime folder (e.g. the 
 33/shared/ directory on maxwell). In this case, you can simply create a `fab_config.toml` file
 34in your working directory (or it's parent directories) and then simply use:
 35
 36```python
 37from fab.magic import config, beamtime, <instrument>
 38```
 39
 40That's it! You can now access the configuration, the beamtime metadata and the instrument. 
 41To load data, you can use the `load` method of the instrument. For an instrument named musix,
 42this would look like:
 43
 44```python
 45from fab.magic import config, beamtime, musix
 46
 47run = musix.load()
 48```
 49"""
 50import importlib
 51import sys
 52from pathlib import Path
 53
 54from .instruments import Instrument
 55from .settings import cfg, update_context, update_config, maxwell_start_if_not_already
 56from .beamtime import Beamtime, autodetect_beamtime, beamtime_basepath
 57
 58import logging
 59logger = logging.getLogger(__name__)
 60tele_log = logging.getLogger('fab.telemetry')
 61
 62
 63
 64
 65def _find_upwards(cwd: Path, glob_pattern: str) -> Path | None:
 66    """ Recursively search a file pattern in all parent directories. """
 67    if cwd == Path(cwd.root) or cwd == cwd.parent:
 68        return None
 69    
 70    try:
 71        return list(cwd.glob(glob_pattern))[0]
 72    except IndexError:
 73        return _find_upwards(cwd.parent, glob_pattern)
 74
 75def _magic_config():
 76    """ Attemps to load the config from one of the parent directories. 
 77        The file name should match 'fab_config*.toml'.
 78
 79        E.g. fab_config.toml and fab_config_musix.toml are valid names.
 80        Starting from the current directioy, the function will search
 81        for the config file in the current directory and all parent directories.
 82    """
 83    tele_log.info("Magic config")
 84
 85    config_file = _find_upwards(Path.cwd(), "fab_config*.toml")
 86    if not config_file:
 87        raise FileNotFoundError("Could not autodetect any matching config file")
 88
 89    update_config(config_file)
 90    return cfg
 91
 92def _magic_beamtime():
 93    """ Return a beamtime object corresponding to the current configured beamtime.
 94        If no beamtime is configured, try to autodetect it from the current directory.
 95
 96    """
 97    tele_log.info("Magic beamtime")
 98    
 99    if not cfg.beamtime:
100        logger.debug("No beamtime configured. Attempting to autodetect it...")
101        beamtime = autodetect_beamtime()
102        cfg['beamtime'] = beamtime
103
104    return Beamtime(cfg.beamtime)
105
106def __getattr__(name):
107    logger.debug(f"Magic config: {name}")
108
109    match name:
110        case "__path__":
111            raise AttributeError
112
113        case 'config':
114            return _magic_config()
115            
116        case 'beamtime':
117            return _magic_beamtime()
118
119        case _:
120            pass
121        
122    # If loading an instrument, make sure that the dask cluster is running
123    # and that a beamtime is set
124    maxwell_start_if_not_already()
125
126    try:
127        return Instrument.from_name(name)
128    except Exception as e:
129        logger.exception(e)
130        raise AttributeError from e
131    
132    return None
logger = <Logger fab.magic (INFO)>
tele_log = <Logger fab.telemetry (INFO)>