예제 #1
0
def get_db_class(module_name='file'):
    """Load the main DB class for the module of the given name.

    .. note::

        This is used by the `PanDB` constructor to determine the
        correct database type. Normal DB instantiation should be done
        via the `PanDB()` class with the desired `db_type` parameter
        set.  See example in `PanDB` below.

    Args:
        module_name (str): Name of module, one of: `file` (default), 'memory'.

    Returns:
        `panoptes.utils.database.PanDB`: An instance of the db class for the correct database type.

    Raises:
        Exception: If an unsupported database type string is passed.
    """
    class_map = {
        'file': 'PanFileDB',
        'memory': 'PanMemoryDB',
    }

    full_module_name = f'panoptes.utils.database.{module_name}'

    try:
        db_module = load_module(full_module_name)
        return getattr(db_module, class_map[module_name])
    except Exception as e:
        raise Exception(
            f'Unsupported database type: {full_module_name}: {e!r}')
예제 #2
0
    def _load_state(self, state, state_info=None):
        self.logger.debug(f"Loading state: {state}")
        try:
            state_location = self._states_location.replace("/", ".")
            state_module = load_module(f"{state_location}.{self._state_table_name}.{state}")

            # Get the `on_enter` method
            self.logger.debug(f"Checking {state_module}")

            on_enter_method = getattr(state_module, 'on_enter')
            setattr(self, f'on_enter_{state}', on_enter_method)
            self.logger.trace(f"Added `on_enter` method from {state_module} {on_enter_method}")

            if state_info is None:
                state_info = dict()

            # Add horizon if state requires.
            with suppress(KeyError):
                self._horizon_lookup[state] = state_info['horizon']
                del state_info['horizon']

            self.logger.debug(f"Creating state={state!r} with state_info={state_info!r}")
            state_machine = MachineState(name=state, **state_info)

            # Add default callbacks.
            state_machine.add_callback('enter', '_update_status')
            state_machine.add_callback('enter', f'on_enter_{state}')

        except Exception as e:
            raise error.InvalidConfig(f"Can't load state modules: {state}\t{e!r}")

        return state_machine
예제 #3
0
def _get_db_class(module_name='file'):
    """Load the main DB class for the module of the given name.

    .. doctest:

        >>> _get_db_class()
        <class 'panoptes.utils.database.file.PanFileDB'>
        >>> _get_db_class('memory')
        <class 'panoptes.utils.database.memory.PanMemoryDB'>

    Args:
        module_name (str): Name of module, one of: `file` (default), `mongo', 'memory'.

    Returns:
        `panoptes.utils.database.PanDB`: An instance of the db class for the correct database type.

    Raises:
        Exception: If an unsupported database type string is passed.
    """
    class_map = {
        'mongo': 'PanMongoDB',
        'file': 'PanFileDB',
        'memory': 'PanMemoryDB',
    }

    full_module_name = f'panoptes.utils.database.{module_name}'

    try:
        db_module = load_module(full_module_name)
        return getattr(db_module, class_map[module_name])
    except Exception:
        raise Exception(f'Unsupported database type: {full_module_name}')
예제 #4
0
파일: __init__.py 프로젝트: ASTROGBAE/POCS
def create_mount_simulator(mount_info=None,
                           earth_location=None,
                           *args, **kwargs):
    # Remove mount simulator
    current_simulators = get_config('simulator', default=[])
    logger.warning(f'Current simulators: {current_simulators}')
    with suppress(ValueError):
        current_simulators.remove('mount')

    mount_config = mount_info or {
        'model': 'Mount Simulator',
        'driver': 'simulator',
        'serial': {
            'port': '/dev/FAKE'
        }
    }

    # Set mount device info to simulator
    set_config('mount', mount_config)

    earth_location = earth_location or create_location_from_config()['earth_location']

    logger.debug(f"Loading mount driver: pocs.mount.{mount_config['driver']}")
    try:
        module = load_module(f"panoptes.pocs.mount.{mount_config['driver']}")
    except error.NotFound as e:
        raise error.MountNotFound(f'Error loading mount module: {e!r}')

    mount = module.Mount(earth_location, *args, **kwargs)

    logger.success(f"{mount_config['driver'].title()} mount created")

    return mount
예제 #5
0
def create_scheduler_from_config(observer=None, *args, **kwargs):
    """ Sets up the scheduler that will be used by the observatory """

    logger = get_logger()

    scheduler_config = get_config('scheduler', default=None)
    logger.info(f'scheduler_config: {scheduler_config!r}')

    if scheduler_config is None or len(scheduler_config) == 0:
        logger.info("No scheduler in config")
        return None

    if not observer:
        logger.debug(f'No Observer provided, creating from config.')
        site_details = create_location_from_config()
        observer = site_details['observer']

    scheduler_type = scheduler_config.get('type', 'dispatch')

    # Read the targets from the file
    fields_file = scheduler_config.get('fields_file', 'simple.yaml')
    fields_path = os.path.join(get_config('directories.targets'), fields_file)
    logger.debug(f'Creating scheduler: {fields_path}')

    if os.path.exists(fields_path):

        try:
            # Load the required module
            module = load_module(f'panoptes.pocs.scheduler.{scheduler_type}')

            obstruction_list = get_config('location.obstructions', default=[])
            default_horizon = get_config('location.horizon',
                                         default=30 * u.degree)

            horizon_line = horizon_utils.Horizon(
                obstructions=obstruction_list,
                default_horizon=default_horizon.value)

            # Simple constraint for now
            constraints = [
                Altitude(horizon=horizon_line),
                MoonAvoidance(),
                Duration(default_horizon, weight=5.)
            ]

            # Create the Scheduler instance
            scheduler = module.Scheduler(observer,
                                         fields_file=fields_path,
                                         constraints=constraints,
                                         *args,
                                         **kwargs)
            logger.debug("Scheduler created")
        except error.NotFound as e:
            raise error.NotFound(msg=e)
    else:
        raise error.NotFound(
            msg=f"Fields file does not exist: fields_file={fields_file!r}")

    return scheduler
예제 #6
0
파일: __init__.py 프로젝트: ASTROGBAE/POCS
def create_dome_simulator(*args, **kwargs):
    dome_config = get_config('dome')

    brand = dome_config['brand']
    driver = dome_config['driver']

    logger.debug(
        f'Creating dome simulator: brand={brand!r}, driver={driver!r}')

    module = load_module(f'panoptes.pocs.dome.{driver}')
    dome = module.Dome(*args, **kwargs)
    logger.info(f'Created dome driver: brand={brand!r}, driver={driver!r}')

    return dome
예제 #7
0
파일: __init__.py 프로젝트: ASTROGBAE/POCS
def create_dome_from_config(*args, **kwargs):
    """If there is a dome specified in the config, create a driver for it.

    A dome needs a config. We assume that there is at most one dome in the config, i.e. we don't
    support two different dome devices, such as might be the case if there are multiple
    independent actuators, for example slit, rotation and vents. Those would need to be handled
    by a single dome driver class.
    """

    dome_config = get_config('dome')

    if dome_config is None:
        logger.info('No dome in config.')
        return None

    brand = dome_config['brand']
    driver = dome_config['driver']

    logger.debug(f'Creating dome: brand={brand!r}, driver={driver!r}')
    module = load_module(f'panoptes.pocs.dome.{driver}')
    dome = module.Dome(*args, **kwargs)
    logger.info(f'Created dome driver: brand={brand}, driver={driver}')

    return dome
def test_bad_load_module():
    with pytest.raises(error.NotFound):
        load_module('FOOBAR')
예제 #9
0
파일: camera.py 프로젝트: ASTROGBAE/POCS
    def _create_subcomponent(self, class_path, subcomponent):
        """
        Creates a subcomponent as an attribute of the camera. Can do this from either an instance
        of the appropriate subcomponent class, or from a dictionary of keyword arguments for the
        subcomponent class' constructor.

        Args:
            subcomponent (instance of sub_name | dict): the subcomponent object, or the keyword
                arguments required to create it.
            class_path (str): Full namespace of the subcomponent, e.g.
                'panoptes.pocs.focuser.focuser.AbstractFocuser'.

        Returns:
            object: an instance of the subcomponent object.

        Raises:
            panoptes.utils.error.NotFound: Not found error.
        """
        # Load the module for the subcomponent.
        sub_parts = class_path.split('.')
        base_class_name = sub_parts.pop()
        base_module_name = '.'.join(sub_parts)
        self.logger.debug(f'Loading base_module_name={base_module_name!r}')
        try:
            base_module = load_module(base_module_name)
        except error.NotFound as err:
            self.logger.critical(
                f"Couldn't import base_module_name={base_module_name!r}")
            raise err

        # Get the base class from the loaded module.
        self.logger.debug(
            f'Trying to get base_class_name={base_class_name!r} from {base_module}'
        )
        base_class = getattr(base_module, base_class_name)

        # If we get an instance, just use it.
        if isinstance(subcomponent, base_class):
            self.logger.debug(
                f"subcomponent={subcomponent!r} is already a base_class={base_class!r} instance"
            )
        # If we get a dict, use them as params to create instance.
        elif isinstance(subcomponent, dict):
            try:
                model = subcomponent['model']
                self.logger.debug(
                    f"subcomponent={subcomponent!r} is a dict but has model={model!r} keyword, "
                    f"trying to create a base_class={base_class!r} instance")
                base_class = load_module(model)
            except (KeyError, error.NotFound) as err:
                raise error.NotFound(
                    f"Can't create a class_path={class_path!r} from subcomponent={subcomponent!r}"
                )

            self.logger.debug(
                f'Creating the base_class_name={base_class_name!r} object from dict'
            )

            # Copy dict creation items and add the camera.
            subcomponent_kwargs = copy.deepcopy(subcomponent)
            subcomponent_kwargs.update({'camera': self})

            try:
                subcomponent = base_class(**subcomponent_kwargs)
            except TypeError:
                raise error.NotFound(
                    f'base_class={base_class!r} is not a callable class. '
                    f'Please specify full path to class (not module).')
            self.logger.success(
                f'{subcomponent} created for {base_class_name}')
        else:
            # Should have been passed either an instance of base_class or dict with subcomponent
            # configuration. Got something else...
            raise error.NotFound(
                f"Expected either a {base_class_name} instance or dict, got {subcomponent!r}"
            )

        # Give the subcomponent a reference back to the camera.
        setattr(subcomponent, 'camera', self)
        return subcomponent
예제 #10
0
파일: __init__.py 프로젝트: ASTROGBAE/POCS
def create_cameras_from_config(config=None,
                               cameras=None,
                               auto_primary=True,
                               recreate_existing=False,
                               *args, **kwargs):
    """Create camera object(s) based on the config.

    Creates a camera for each camera item listed in the config. Ensures the
    appropriate camera module is loaded.

    Args:
        config (dict or None): A config object for a camera or None to lookup in
            config-server.
        cameras (list of panoptes.pocs.camera.Camera or None): A list of camera
            objects or None.
        auto_primary (bool): If True, when no camera is marked as the primary camera,
            the first camera in the list will be used as primary. Default True.
        recreate_existing (bool): If True, a camera object will be recreated if an
            existing camera with the same `uid` is already assigned. Should currently
            only affect cameras that use the `sdk` (i.g. not DSLRs). Default False
            raises an exception if camera is already assigned.
        *args (list): Passed to `get_config`.
        **kwargs (dict): Can pass a `cameras` object that overrides the info in
            the configuration file. Can also pass `auto_detect`(bool) to try and
            automatically discover the ports. Any other items as passed to `get_config`.

    Returns:
        OrderedDict: An ordered dictionary of created camera objects, with the
            camera name as key and camera instance as value. Returns an empty
            OrderedDict if there is no camera configuration items.

    Raises:
        error.CameraNotFound: Raised if camera cannot be found at specified port or if
            auto_detect=True and no cameras are found.
        error.PanError: Description
    """
    camera_config = config or get_config('cameras', *args, **kwargs)

    if not camera_config:
        # cameras section either missing or empty
        logger.info('No camera information in config.')
        return None

    logger.debug(f"camera_config={camera_config!r}")
    camera_defaults = camera_config.get('defaults', dict())

    cameras = cameras or OrderedDict()
    ports = list()

    auto_detect = camera_defaults.get('auto_detect', False)

    # Lookup the connected ports
    if auto_detect:
        logger.debug("Auto-detecting ports for cameras")
        try:
            ports = list_connected_cameras()
        except error.PanError as e:
            logger.warning(e)

        if len(ports) == 0:
            raise error.CameraNotFound(msg="No cameras detected. For testing, use camera simulator.")
        else:
            logger.debug(f"Detected ports={ports!r}")

    primary_camera = None

    device_info = camera_config['devices']
    for cam_num, cfg in enumerate(device_info):
        # Get a copy of the camera defaults and update with device config.
        device_config = camera_defaults.copy()
        device_config.update(cfg)

        cam_name = device_config.setdefault('name', f'Cam{cam_num:02d}')

        # Check for proper connection method.
        model = device_config['model']

        # Assign an auto-detected port. If none are left, skip
        if auto_detect:
            try:
                device_config['port'] = ports.pop()
            except IndexError:
                logger.warning(f"No ports left for {cam_name}, skipping.")
                continue
        elif model == 'simulator':
            device_config['port'] = f'usb:999,{random.randint(0, 1000):03d}'

        logger.debug(f'Creating camera: {model}')

        try:
            module = load_module(model)
            logger.debug(f'Camera module: module={module!r}')

            if recreate_existing:
                with suppress(AttributeError):
                    module._assigned_cameras = set()

            # We either got a class or a module.
            if callable(module):
                camera = module(**device_config)
            else:
                if hasattr(module, 'Camera'):
                    camera = module.Camera(**device_config)
                else:
                    raise error.NotFound(f'module={module!r} does not have a Camera object')
        except error.NotFound:
            logger.error(f"Cannot find camera module with config: {device_config}")
        except Exception as e:
            logger.error(f"Cannot create camera type: {model} {e}")
        else:
            # Check if the config specified a primary camera and if it matches.
            if camera.uid == camera_config.get('primary'):
                camera.is_primary = True
                primary_camera = camera

            logger.debug(f"Camera created: camera={camera!r}")

            cameras[cam_name] = camera

    if len(cameras) == 0:
        raise error.CameraNotFound(msg="No cameras available")

    # If no camera was specified as primary use the first
    if primary_camera is None and auto_primary:
        logger.info(f'No primary camera given, assigning the first camera (auto_primary={auto_primary!r})')
        primary_camera = list(cameras.values())[0]  # First camera
        primary_camera.is_primary = True

    logger.info(f"Primary camera: {primary_camera}")
    logger.success(f"{len(cameras)} cameras created")

    return cameras
예제 #11
0
파일: __init__.py 프로젝트: ASTROGBAE/POCS
def create_mount_from_config(mount_info=None,
                             earth_location=None,
                             *args, **kwargs):
    """Create a mount instance based on the provided config.

    Creates an instance of the AbstractMount sub-class in the module specified in the config.
    Specifically, the class must be in a file called pocs/mount/<DRIVER_NAME>.py,
    and the class must be called Mount.

    Args:
        mount_info: Optional param which overrides the 'mount' entry in config if provided.
            Useful for testing.
        earth_location: `astropy.coordinates.EarthLocation` instance, representing the
            location of the mount on the Earth. If not specified, the config must include the
            observatory's location (Latitude, Longitude and Altitude above mean sea level).
            Useful for testing.
        *args: Other positional args will be passed to the concrete class specified in the config.
        **kwargs: Other keyword args will be passed to the concrete class specified in the config.

    Returns:
        An instance of the Mount class if the config (or mount_info) is complete. `None` if neither
        mount_info nor config['mount'] is provided.

    Raises:
        error.MountNotFound: Exception raised when mount cannot be created
            because of incorrect configuration.
    """

    # If mount_info was not passed as a parameter, check config.
    if mount_info is None:
        logger.debug('No mount info provided, using values from config.')
        mount_info = get_config('mount', default=None)

        # If nothing in config, raise exception.
        if mount_info is None:
            raise error.MountNotFound('No mount information in config, cannot create.')

    # If earth_location was not passed as a parameter, check config.
    if earth_location is None:
        logger.debug('No location provided, using values from config.')

        # Get details from config.
        site_details = create_location_from_config()
        earth_location = site_details['earth_location']

    driver = mount_info.get('driver')
    if not driver or not isinstance(driver, str):
        raise error.MountNotFound('Mount info in config is missing a driver name.')

    model = mount_info.get('model', driver)
    logger.debug(f'Mount: driver={driver} model={model}')

    # Check if we should be using a simulator
    use_simulator = 'mount' in get_config('simulator', default=[])
    logger.debug(f'Mount is simulator: {use_simulator}')

    # Create simulator if requested
    if use_simulator or (driver == 'simulator'):
        logger.debug(f'Creating mount simulator')
        return create_mount_simulator(mount_info=mount_info, earth_location=earth_location)

    # See if we have a serial connection
    try:
        port = mount_info['serial']['port']
        logger.info(f'Looking for {driver} on {port}.')
        if port is None or len(glob(port)) == 0:
            msg = f'Mount port ({port}) not available. Use simulator = mount for simulator.'
            raise error.MountNotFound(msg=msg)
    except KeyError:
        # See Issue 866
        if model == 'bisque':
            logger.debug('Driver specifies a bisque type mount, no serial port needed.')
        else:
            msg = 'Mount port not specified in config file. Use simulator=mount for simulator.'
            raise error.MountNotFound(msg=msg)

    logger.debug(f'Loading mount driver: pocs.mount.{driver}')
    try:
        module = load_module(f'panoptes.pocs.mount.{driver}')
    except error.NotFound as e:
        raise error.MountNotFound(e)

    # Make the mount include site information
    mount = module.Mount(location=earth_location, *args, **kwargs)

    logger.success(f'{driver} mount created')

    return mount