<commit_before>"""
This module provides an interface to other systems (such as the robot itself)
for quering the position of wells and other items within labware equipment,
such as microplates, tipracks, and reservoirs.

The containers aren't stored or defined here, merely aggregated under the
namespace expected by the external API.

For example, `containers.load_container('microplate.96.deepwell')` will load
`microplates.Microplate_96_Deepwell`.  The name that gets passed to the
container is the same as the name that should be defined in the JSON
Protocol file.

There's also support for custom containers, either by placing them in the
config/containers directory of this library (see included examples), or by
using the load_custom_containers function of this module to specify an
alternate configuration directory.
"""

import os
import yaml
import inspect
import json

import labware

from labware import Microplate, Tiprack, Reservoir
from .grid import GridContainer, normalize_position

# These are the base types that containers can extend from.
_typemap = {
    'grid': GridContainer,
    'tiprack': Tiprack,
    'microplate': Microplate,
    'reservoir': Reservoir
}

# Valid properties to configure.
# TODO: Define this on each container type.
_valid_properties = [
    'rows', 'cols', 'a1_x', 'a1_y', 'spacing', 'height', 'length', 'width',
    'volume', 'min_vol', 'max_vol', 'well_depth', 'row_spacing', 
    'col_spacing', 'diameter', 'legacy_name'
]

# These are the types that can be defined within a custom container.
_valid_values = (int, float, str)

# The list of modules to automatically catalog as supported containers.
_container_modules = [
    labware.tipracks, labware.microplates, labware.reservoirs
]

_containers = {}

def load_custom_containers(folder=None):
    """
    Loads YAML files from the directory specified, or the default
    configuration directory for this module.

    Loaded containers will be named for their filename and their
    specified type.

    For example, a file named "foo.yml" which specifies that its
    container is a microplate will be officially called
    "microplate.foo" for reference purposes within JSON Protocols
    and other parts of the system.

    If a container name is reused, the old container will be 
    replaced.
    """
    # Default to local library configuration.
    if not folder:
        folder = os.path.join(os.getcwd(), 'config/containers')
    files = []
    # Get all YAML files from the specified directory, parse then,
    # and send the data to add_custom_container.
    for f in os.listdir(folder):
        full_path = os.path.join(folder, f)
        if os.path.isfile(full_path) and full_path.endswith('.yml'):
            data = yaml.load(open(full_path, 'r'))
            name = os.path.splitext(f)[0]
            add_custom_container(data, name=name)
        elif os.path.isdir(full_path):
            load_custom_containers(full_path)


def _load_default_containers():
    """
    Traverses the set list of default container types in order to provide
    access to them through standardized container reference names.

    We do this so that we don't have to manually list a container in this
    module everytime we add a new one.
    """
    _containers['grid'] = GridContainer
    for mod in _container_modules:
        props = dir(mod)
        for name in props:
            prop = getattr(mod, name)
            if inspect.isclass(prop) and issubclass(prop, GridContainer):
                name = normalize_container_name(name)<commit_msg>Containers: Module-specific config file path.<commit_after>"""
This module provides an interface to other systems (such as the robot itself)
for quering the position of wells and other items within labware equipment,
such as microplates, tipracks, and reservoirs.

The containers aren't stored or defined here, merely aggregated under the
namespace expected by the external API.

For example, `containers.load_container('microplate.96.deepwell')` will load
`microplates.Microplate_96_Deepwell`.  The name that gets passed to the
container is the same as the name that should be defined in the JSON
Protocol file.

There's also support for custom containers, either by placing them in the
config/containers directory of this library (see included examples), or by
using the load_custom_containers function of this module to specify an
alternate configuration directory.
"""

import os
import yaml
import inspect
import json

import labware

from labware import Microplate, Tiprack, Reservoir
from .grid import GridContainer, normalize_position

# These are the base types that containers can extend from.
_typemap = {
    'grid': GridContainer,
    'tiprack': Tiprack,
    'microplate': Microplate,
    'reservoir': Reservoir
}

# Valid properties to configure.
# TODO: Define this on each container type.
_valid_properties = [
    'rows', 'cols', 'a1_x', 'a1_y', 'spacing', 'height', 'length', 'width',
    'volume', 'min_vol', 'max_vol', 'well_depth', 'row_spacing', 
    'col_spacing', 'diameter', 'legacy_name'
]

# These are the types that can be defined within a custom container.
_valid_values = (int, float, str)

# The list of modules to automatically catalog as supported containers.
_container_modules = [
    labware.tipracks, labware.microplates, labware.reservoirs
]

_containers = {}

def load_custom_containers(folder=None):
    """
    Loads YAML files from the directory specified, or the default
    configuration directory for this module.

    Loaded containers will be named for their filename and their
    specified type.

    For example, a file named "foo.yml" which specifies that its
    container is a microplate will be officially called
    "microplate.foo" for reference purposes within JSON Protocols
    and other parts of the system.

    If a container name is reused, the old container will be 
    replaced.
    """
    # Default to local library configuration.
    if not folder:
        modpath = os.path.dirname(labware.__file__)
        folder = os.path.join(modpath, '..', 'config', 'containers')
    files = []
    # Get all YAML files from the specified directory, parse then,
    # and send the data to add_custom_container.
    for f in os.listdir(folder):
        full_path = os.path.join(folder, f)
        if os.path.isfile(full_path) and full_path.endswith('.yml'):
            data = yaml.load(open(full_path, 'r'))
            name = os.path.splitext(f)[0]
            add_custom_container(data, name=name)
        elif os.path.isdir(full_path):
            load_custom_containers(full_path)


def _load_default_containers():
    """
    Traverses the set list of default container types in order to provide
    access to them through standardized container reference names.

    We do this so that we don't have to manually list a container in this
    module everytime we add a new one.
    """
    _containers['grid'] = GridContainer
    for mod in _container_modules:
        props = dir(mod)
        for name in props:
            prop = getattr(mod, name)
            if inspect.isclass(prop) and issubclass(prop, GridContainer):
                name = normalize_container_name(name)
{'fraction_non_alphanumeric': 0.07660225218962881, 'fraction_numerical': 0.0019463367162519116, 'mean_word_length': 3.5909380982769625, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6161716', 'n_tokens_mistral': 2054, 'n_tokens_neox': 1952, 'n_words': 804}