<commit_before># -*- test-case-name: yadis.test.test_etxrd -*-
"""
ElementTree interface to an XRD document.
"""

__all__ = [
    'nsTag',
    'mkXRDTag',
    'isXRDS',
    'parseXRDS',
    'getCanonicalID',
    'getYadisXRD',
    'getPriorityStrict',
    'getPriority',
    'prioSort',
    'iterServices',
    'expandService',
    'expandServices',
    ]

import sys
import random

from openid.oidutil import importElementTree
ElementTree = importElementTree()

# Use expat if it's present. Otherwise, use xmllib
try:
    XMLTreeBuilder = ElementTree.XMLTreeBuilder

    # This will raise an ImportError if an XML parser is not present.
    p = XMLTreeBuilder()
except ImportError:
    from elementtree.SimpleXMLTreeBuilder import TreeBuilder as XMLTreeBuilder

# the different elementtree modules don't have a common exception
# model. We just want to be able to catch the exceptions that signify
# malformed XML data and wrap them, so that the other library code
# doesn't have to know which XML library we're using.
try:
    # Make the parser raise an exception so we can sniff out the type
    # of exceptions
    p.feed('> purposely malformed XML <')
    p.close()
except (SystemExit, MemoryError, AssertionError, ImportError):
    raise
except:
    XMLError = sys.exc_info()[0]
    del p

from openid.yadis import xri

class XRDSError(Exception):
    """An error with the XRDS document."""

    # The exception that triggered this exception
    reason = None

class XRDSFraud(XRDSError):
    """Raised when there's an assertion in the XRDS that it does not have
    the authority to make.
    """

def parseXRDS(text):
    """Parse the given text as an XRDS document.

    @return: ElementTree containing an XRDS document

    @raises XRDSError: When there is a parse error or the document does
        not contain an XRDS.
    """
    try:
        parser = XMLTreeBuilder()
        parser.feed(text)
        element = parser.close()
    except XMLError, why:
        exc = XRDSError('Error parsing document as XML')
        exc.reason = why
        raise exc
    else:
        tree = ElementTree.ElementTree(element)
        if not isXRDS(tree):
            raise XRDSError('Not an XRDS document')

        return tree

XRD_NS_2_0 = 'xri://$xrd*($v*2.0)'
XRDS_NS = 'xri://$xrds'

def nsTag(ns, t):
    return '{%s}%s' % (ns, t)

def mkXRDTag(t):
    """basestring -> basestring

    Create a tag name in the XRD 2.0 XML namespace suitable for using
    with ElementTree
    """
    return nsTag(XRD_NS_2_0, t)

def mkXRDSTag(t):
    """basestring -> basestring

    Create a tag name in the XRDS XML namespace suitable for using
    with ElementTree
    """
    return nsTag(XRDS_NS, t)

# Tags that are used in Yadis documents
root_tag = mkXRDSTag('XRDS')
service_tag = mkXRDTag('Service')
xrd_tag = mkXRDTag('XRD')
type_tag = mkXRDTag('Type')
uri_tag = mkXRDTag('URI')

# Other XRD tags
canonicalID_tag = mkXRDTag('CanonicalID')

def isXRDS(xrd_tree):
    """Is this document an XRDS document?"""
    root = xrd_tree.getroot()
    return root.tag == root_tag

def getYadisXRD(xrd_tree):
    """Return the XRD element that should contain the Yadis services"""
    xrd = None

    # for the side-effect of assigning the last one in the list to the
    # xrd variable
    for xrd in xrd_tree.findall(xrd_tag):
        pass

    # There were no elements found, or else xrd would be set to the
    # last one
    if xrd is None:
        raise XRDSError('No XRD present in tree')

    return xrd

def getCanonicalID(iname, xrd_tree):
    """Return the CanonicalID from this XRDS document.

    @param iname: the XRI being resolved.
    @type iname: unicode

    @param xrd_tree: The XRDS output from the resolver.
    @type xrd_tree: ElementTree

    @returns: The XRI CanonicalID or None.
    @returntype: unicode or None
    """
    xrd_list = xrd_tree.findall(xrd_tag)
    xrd_list.reverse()

    try:
        canonicalID = xri.XRI(xrd_list[0].findall(canonicalID_tag)[-1].text)
    except IndexError:
        return None

    childID = canonicalID

    for xrd in xrd_list[1:]:
        # XXX: can't use rsplit until we require python >= 2.4.
        parent_sought = childID[:childID.rindex('!')]
        parent_list = [xri.XRI(c.text) for c in xrd.findall(canonicalID_tag)]
        if parent_sought not in parent_list:
            raise XRDSFraud("%r can not come from any of %s" % (parent_sought,
                                                                parent_list))

        childID = parent_sought

    root = xri.rootAuthority(iname)
    if not xri.providerIsAuthoritative(root, childID):
        raise XRDSFraud("%r can not come from root %r" % (childID, root))

    return canonicalID

class _Max(object):
    """Value that compares greater than any other value.

    Should only be used as a singleton. Implemented for use as a
    priority value for when a priority is not specified."""
    def __cmp__(self, other):
        if other is self:
            return 0

        return 1

Max = _Max()

def getPriorityStrict(element):
    """Get the priority of this element.

    Raises ValueError if the value of the priority is invalid. If no
    priority is specified, it returns a value that compares greater
    than any other value.
    """
    prio_str = element.get('priority')
    if prio_str is not None:
        prio_val = int(prio_str)
        if prio_val >= 0:
            return prio_val
        else:
            raise ValueError('Priority values must be non-negative integers')

    # Any errors in parsing the priority fall through to here
    return Max

def getPriority(element):
    """Get the priority of this element

    Returns Max if no priority is specified or the priority value is invalid.
    """
    try:
        return getPriorityStrict(element)
    except ValueError:
        return Max

def prioSort(elements):
    """Sort a list of elements that have priority attributes"""
    # Randomize the services before sorting so that equal priority
    # elements are load-balanced.
    random.shuffle(elements)

    prio_elems = [(getPriority(e), e) for e in elements]
    prio_elems.sort()
    sorted_elems = [s for (_, s) in prio_elems]
    return sorted_elems

def iterServices(xrd_tree):
    """Return an iterable over the Service elements in the Yadis XRD

    sorted by priority"""
    xrd = getYadisXRD(xrd_tree)
    return prioSort(xrd.findall(service_tag))

def sortedURIs(service_element):
    """Given a Service element, return a list of the contents of all
    URI tags in priority order."""
    return [uri_element.text for uri_element
            in prioSort(service_element.findall(uri_tag))]

def getTypeURIs(service_element):
    """Given a Service element, return a list of the contents of all
    Type tags"""
    return [type_element.text for type_element
            in service_element.findall(type_tag)]

def expandService(service_element):
    """Take a service element and expand it into an iterator of:
    ([type_uri], uri, service_element)
    """
    uris = sortedURIs(service_element)
    if not uris:
        uris = [None]

    expanded = []
    for uri in uris:
        type_uris = getTypeURIs(service_element)
        expanded.append((type_uris, uri, service_element))

    return expanded

def expandServices(service_elements):
    """Take a sorted iterator of service elements and expand it into a
    sorted iterator of:
    ([type_uri], uri, service_element)

    There may be more than one item in the resulting list for each
    service element if there is more than one URI or type for a
    service, but each triple will be unique.

    If there is no URI or Type for a Service element, it will not
    appear in the result.
    """
    expanded = []
    for service_element in service_elements:
        expanded.extend(expandService(service_element))

    return expanded
<commit_msg>[project @ Added function to openid.yadis.etxrd that parses the Expires date out of an XRD]<commit_after># -*- test-case-name: yadis.test.test_etxrd -*-
"""
ElementTree interface to an XRD document.
"""

__all__ = [
    'nsTag',
    'mkXRDTag',
    'isXRDS',
    'parseXRDS',
    'getCanonicalID',
    'getYadisXRD',
    'getPriorityStrict',
    'getPriority',
    'prioSort',
    'iterServices',
    'expandService',
    'expandServices',
    ]

import sys
import random

from datetime import datetime
from time import strptime

from openid.oidutil import importElementTree
ElementTree = importElementTree()

# Use expat if it's present. Otherwise, use xmllib
try:
    XMLTreeBuilder = ElementTree.XMLTreeBuilder

    # This will raise an ImportError if an XML parser is not present.
    p = XMLTreeBuilder()
except ImportError:
    from elementtree.SimpleXMLTreeBuilder import TreeBuilder as XMLTreeBuilder

# the different elementtree modules don't have a common exception
# model. We just want to be able to catch the exceptions that signify
# malformed XML data and wrap them, so that the other library code
# doesn't have to know which XML library we're using.
try:
    # Make the parser raise an exception so we can sniff out the type
    # of exceptions
    p.feed('> purposely malformed XML <')
    p.close()
except (SystemExit, MemoryError, AssertionError, ImportError):
    raise
except:
    XMLError = sys.exc_info()[0]
    del p

from openid.yadis import xri

class XRDSError(Exception):
    """An error with the XRDS document."""

    # The exception that triggered this exception
    reason = None

class XRDSFraud(XRDSError):
    """Raised when there's an assertion in the XRDS that it does not have
    the authority to make.
    """

def parseXRDS(text):
    """Parse the given text as an XRDS document.

    @return: ElementTree containing an XRDS document

    @raises XRDSError: When there is a parse error or the document does
        not contain an XRDS.
    """
    try:
        parser = XMLTreeBuilder()
        parser.feed(text)
        element = parser.close()
    except XMLError, why:
        exc = XRDSError('Error parsing document as XML')
        exc.reason = why
        raise exc
    else:
        tree = ElementTree.ElementTree(element)
        if not isXRDS(tree):
            raise XRDSError('Not an XRDS document')

        return tree

XRD_NS_2_0 = 'xri://$xrd*($v*2.0)'
XRDS_NS = 'xri://$xrds'

def nsTag(ns, t):
    return '{%s}%s' % (ns, t)

def mkXRDTag(t):
    """basestring -> basestring

    Create a tag name in the XRD 2.0 XML namespace suitable for using
    with ElementTree
    """
    return nsTag(XRD_NS_2_0, t)

def mkXRDSTag(t):
    """basestring -> basestring

    Create a tag name in the XRDS XML namespace suitable for using
    with ElementTree
    """
    return nsTag(XRDS_NS, t)

# Tags that are used in Yadis documents
root_tag = mkXRDSTag('XRDS')
service_tag = mkXRDTag('Service')
xrd_tag = mkXRDTag('XRD')
type_tag = mkXRDTag('Type')
uri_tag = mkXRDTag('URI')
expires_tag = mkXRDTag('Expires')

# Other XRD tags
canonicalID_tag = mkXRDTag('CanonicalID')

def isXRDS(xrd_tree):
    """Is this document an XRDS document?"""
    root = xrd_tree.getroot()
    return root.tag == root_tag

def getYadisXRD(xrd_tree):
    """Return the XRD element that should contain the Yadis services"""
    xrd = None

    # for the side-effect of assigning the last one in the list to the
    # xrd variable
    for xrd in xrd_tree.findall(xrd_tag):
        pass

    # There were no elements found, or else xrd would be set to the
    # last one
    if xrd is None:
        raise XRDSError('No XRD present in tree')

    return xrd

def getXRDExpiration(xrd_element, default=None):
    """Return the expiration date of this XRD element, or None if no
    expiration was specified.

    @type xrd_element: ElementTree node

    @param default: The value to use as the expiration if no
        expiration was specified in the XRD.

    @rtype: datetime.datetime

    @raises ValueError: If the xrd:Expires element is present, but its
        contents are not formatted according to the specification.
    """
    expires_element = xrd_element.find(expires_tag)
    if expires_element is None:
        return default
    else:
        expires_string = expires_element.text

        # Will raise ValueError if the string is not the expected format
        expires_time = strptime(expires_string, "%Y-%m-%dT%H:%M:%SZ")
        return datetime(*expires_time[0:6])

def getCanonicalID(iname, xrd_tree):
    """Return the CanonicalID from this XRDS document.

    @param iname: the XRI being resolved.
    @type iname: unicode

    @param xrd_tree: The XRDS output from the resolver.
    @type xrd_tree: ElementTree

    @returns: The XRI CanonicalID or None.
    @returntype: unicode or None
    """
    xrd_list = xrd_tree.findall(xrd_tag)
    xrd_list.reverse()

    try:
        canonicalID = xri.XRI(xrd_list[0].findall(canonicalID_tag)[-1].text)
    except IndexError:
        return None

    childID = canonicalID

    for xrd in xrd_list[1:]:
        # XXX: can't use rsplit until we require python >= 2.4.
        parent_sought = childID[:childID.rindex('!')]
        parent_list = [xri.XRI(c.text) for c in xrd.findall(canonicalID_tag)]
        if parent_sought not in parent_list:
            raise XRDSFraud("%r can not come from any of %s" % (parent_sought,
                                                                parent_list))

        childID = parent_sought

    root = xri.rootAuthority(iname)
    if not xri.providerIsAuthoritative(root, childID):
        raise XRDSFraud("%r can not come from root %r" % (childID, root))

    return canonicalID

class _Max(object):
    """Value that compares greater than any other value.

    Should only be used as a singleton. Implemented for use as a
    priority value for when a priority is not specified."""
    def __cmp__(self, other):
        if other is self:
            return 0

        return 1

Max = _Max()

def getPriorityStrict(element):
    """Get the priority of this element.

    Raises ValueError if the value of the priority is invalid. If no
    priority is specified, it returns a value that compares greater
    than any other value.
    """
    prio_str = element.get('priority')
    if prio_str is not None:
        prio_val = int(prio_str)
        if prio_val >= 0:
            return prio_val
        else:
            raise ValueError('Priority values must be non-negative integers')

    # Any errors in parsing the priority fall through to here
    return Max

def getPriority(element):
    """Get the priority of this element

    Returns Max if no priority is specified or the priority value is invalid.
    """
    try:
        return getPriorityStrict(element)
    except ValueError:
        return Max

def prioSort(elements):
    """Sort a list of elements that have priority attributes"""
    # Randomize the services before sorting so that equal priority
    # elements are load-balanced.
    random.shuffle(elements)

    prio_elems = [(getPriority(e), e) for e in elements]
    prio_elems.sort()
    sorted_elems = [s for (_, s) in prio_elems]
    return sorted_elems

def iterServices(xrd_tree):
    """Return an iterable over the Service elements in the Yadis XRD

    sorted by priority"""
    xrd = getYadisXRD(xrd_tree)
    return prioSort(xrd.findall(service_tag))

def sortedURIs(service_element):
    """Given a Service element, return a list of the contents of all
    URI tags in priority order."""
    return [uri_element.text for uri_element
            in prioSort(service_element.findall(uri_tag))]

def getTypeURIs(service_element):
    """Given a Service element, return a list of the contents of all
    Type tags"""
    return [type_element.text for type_element
            in service_element.findall(type_tag)]

def expandService(service_element):
    """Take a service element and expand it into an iterator of:
    ([type_uri], uri, service_element)
    """
    uris = sortedURIs(service_element)
    if not uris:
        uris = [None]

    expanded = []
    for uri in uris:
        type_uris = getTypeURIs(service_element)
        expanded.append((type_uris, uri, service_element))

    return expanded

def expandServices(service_elements):
    """Take a sorted iterator of service elements and expand it into a
    sorted iterator of:
    ([type_uri], uri, service_element)

    There may be more than one item in the resulting list for each
    service element if there is more than one URI or type for a
    service, but each triple will be unique.

    If there is no URI or Type for a Service element, it will not
    appear in the result.
    """
    expanded = []
    for service_element in service_elements:
        expanded.extend(expandService(service_element))

    return expanded
<|endoftext|>


<commit_before>#!/usr/bin/env python

#This is free and unencumbered software released into the public domain.
#
#Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
#software, either in source code form or as a compiled binary, for any purpose,
#commercial or non-commercial, and by any means.
#
#In jurisdictions that recognise copyright laws, the author or authors of this
#software dedicate any and all copyright interest in the software to the public
#domain. We make this dedication for the benefit of the public at large and to
#the detriment of our heirs and successors. We intend this dedication to be an
#overt act of relinquishment in perpetuity of all present and future rights to
#this software under copyright law.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
#ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
#WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
#For more information, please refer to <https://unlicense.org/>.

"""
Handles all administration on plug-ins.
"""

import collections #For namedtuple.
import imp #Imports Python modules dynamically.
import os #To search through folders to find the plug-ins.

import luna.logger #Logging messages.
import luna.plugin #Checking if plug-ins provide the correct class.

__plugin_locations = []
"""
List of directories where to look for plug-ins.
"""

__plugins = {}
"""
Dictionary holding all plug-ins, indexed by their identity.
"""

__DependencyCandidate = collections.namedtuple("__DependencyCandidate", "identity type plugin_class dependencies")
"""
Represents a candidate dependency. We could run the __init__ of this candidate,
but we haven't resolved dependencies yet or instantiated the plug-in object.

This is a named tuple consisting of the following fields:
* identity: An unique identifier for the plug-in.
* type: The plug-in type.
* plugin_class: The class that implements the abstract base class of the
specified plug-in type.
* dependencies: A list of plug-in identities on which this plug-in depends.
"""

class MetadataValidationError(Exception):
	"""
	Marker exception to indicate that the metadata of a plug-in is invalid.
	"""

def add_plugin_location(location):
	"""
	.. function:: addPluginLocation(location)
	Adds a location to the list of locations where the application looks for
	plug-ins.

	It looks recursively through all subfolders of the specified folder.

	:param location: The location to add to the location list.
	"""
	if not location or not os.path.isdir(location): #Invalid plug-in location.
		raise NotADirectoryError("Plug-in location is not a path: {location}".format(location=location))
	__plugin_locations.append(location)

def discover():
	"""
	.. function:: discover()
	Discovers all plug-ins it can find.

	This goes through all folders of all plug-in types, and finds out what
	their metadata is. If the metadata is correct it will create a plug-in.
	When all plug-ins are loaded, the plug-ins whose dependencies can be met
	will be stored for later requesting. The plug-ins whose dependencies
	cannot be met will be discarded.

	This method can also be used to update the plug-in repository, but
	plug-ins are not deleted then. Only new plug-ins are added by this
	function.
	"""
	candidates = __find_candidates() #Makes a set of (id, path) tuples indicating names and folder paths of possible plug-ins.
	dependency_candidates = [] #Second stage of candidates. We could load these but haven't resolved dependencies yet. List of __DependencyCandidate instances.
	for identity, folder in candidates:
		luna.logger.debug("Loading plug-in {plugin} from {folder}.", plugin=identity, folder=folder)
		#Loading the plug-in.
		module = __load_candidate(identity, folder)
		if not module: #Failed to load module.
			continue
		if __get_plugin(identity):
			luna.logger.warning("Plug-in {plugin} is already loaded.", plugin=identity)
			continue

		#Parsing the metadata.
		try:
			metadata = module.metadata()
		except Exception as e:
			luna.logger.warning("Failed to load metadata of plug-in {plugin}: {error_message}", plugin=identity, error_message=str(e))
			continue
		try:
			__validate_metadata_global(metadata)
		except MetadataValidationError as e:
			luna.logger.warning("Metadata of plug-in {plugin} is invalid: {message}", plugin=identity, message=str(e))
			continue
		if "type" in metadata: #For plug-in type definitions, we have a built-in metadata checker.
			try:
				__validate_metadata_type(metadata)
			except MetadataValidationError as e:
				luna.logger.warning("Metadata of type plug-in {plugin} is invalid: {message}", plugin=identity, message=str(e))
				continue

		dependency_candidates.append(__DependencyCandidate(identity=identity, type=metadata["type"], plugin_class=metadata["class"], dependencies=dependencies))

	#Now go through the candidates to find plug-ins for which we can resolve the dependencies.
	for candidate in dependency_candidates:
		for dependency in candidate.dependencies:
			if dependency.count("/") != 1:
				luna.logger.warning("Plug-in {plugin} has an invalid dependency {dependency}.", plugin=candidate.identity, dependency=dependency)
				continue #With the next dependency.
			dependency_type, dependency_identity = dependency.split("/", 1) #Parse the dependency.
			for dependency_candidate in dependency_candidates:
				if dependency_identity == dependency_candidate.identity and dependency_type == dependency_candidate.type:
					break
			else: #Dependency was not found.
				luna.logger.warning("Plug-in {plugin} is missing dependency {dependency}!", plugin=candidate.identity, dependency=dependency_identity)
				break
		else: #All dependencies are resolved!
			try:
				plugin_instance = candidate.plugin_class() #Actually construct an instance of the plug-in.
			except Exception as e:
				luna.logger.warning("Initialising plug-in {plugin} failed: {error_message}", plugin=candidate.itentity, error_message=str(e))
				continue #With next plug-in.
			__plugins[candidate.identity] = plugin_instance

def get_interface(name):
	"""
	.. function:: getInterface(name)
	Gets an interface plug-in with the specified name, if it exists.

	:param name: The name of the interface plug-in to get.
	:returns: The specified interface, or ``None`` if it doesn't exist.
	"""
	return __get_plugin(name)

def get_interfaces():
	"""
	.. function:: getInterfaces()
	Gets all interface plug-ins.

	:returns: A list of all interface plug-ins.
	"""
	return __get_all_plugins_of_type("interface")

def get_logger(name):
	"""
	.. function:: getLogger(name)
	Gets a logger plug-in with the specified name, if it exists.

	:param name: The name of the logger plug-in to get.
	:returns: The specified logger, or ``None`` if it doesn't exist.
	"""
	return __get_plugin(name)

def get_loggers():
	"""
	.. function:: getLoggers()
	Gets all logger plug-ins.

	:returns: A list of all logger plug-ins.
	"""
	return __get_all_plugins_of_type("logger")

def __find_candidates():
	"""
	.. function:: __findCandidates()
	Finds candidates for what looks like might be plug-ins.

	A candidate is a folder inside a plug-in location, which has a file
	``__init__.py``. The file is not yet executed at this point.

	:returns: A set of tuples of the form (<identity>, <path>), containing
	respectively the identity of the plug-in and the path to the plug-in's
	folder.
	"""
	candidates = set()
	for location in __plugin_locations:
		for root, directories, files in os.walk(location, followlinks=True):
			if "__init__.py" not in files: #The directory must have an __init__.py file.
				continue
			directories[:] = [] #Remove all subdirectories. We aren't going to look in submodules.
			identity = os.path.basename(root) #The name of the folder becomes the plug-in's identity.
			candidates.add((identity, location))
	return candidates

def __load_candidate(identity, folder):
	"""
	.. function:: __loadCandidate(identity, folder)
	Loads a plug-in candidate as a Python package.

	This is intended to be used on Python packages, containing an init
	script.

	:param identity: The identity of the plug-in to load. Hierarchical
		identities are not supported.
	:param folder: The path to the folder where the plug-in can be found.
	:returns: A Python package representing the plug-in. If anything went
		wrong, ``None`` is returned.
	"""
	if "." in identity:
		luna.logger.warning("Can't load plug-in {plugin}: Invalid plug-in identity; periods are forbidden.", plugin=identity)
		return None
	try:
		file, path, description = imp.find_module(identity, [folder])
	except Exception as e:
		luna.logger.warning("Failed to find module of plug-in in {plugin}: {error_message}", plugin=folder, error_message=str(e))
		return None
	try:
		module = imp.load_module(identity, file, path, description)
	except Exception as e:
		luna.logger.warning("Failed to load plug-in {plugin}: {error_message}", plugin=identity, error_message=str(e))
		raise
	finally:
		if file: #Plug-in loading should not open any files, but if it does, close it immediately.
			luna.logger.warning("Plug-in {plugin} is a file: {filename}", plugin=identity, filename=str(file))
			file.close()
	return module

def __get_all_plugins_of_type(plugin_type):
	"""
	.. function:: __getAllPluginsOfType(type)
	Gets all plug-ins with the specified type.

	:returns: A list of all plug-ins of the specified plug-in type.
	"""
	result = []
	for (candidate_type, candidate_identity) in __plugins:
		if plugin_type == candidate_type:
			result.append(__plugins[(candidate_type, candidate_identity)])
	return result

def __get_plugin(identity):
	"""
	.. function:: __getPlugin(identity)
	Gets the plug-in with the specified identity, if it exists.

	:param identity: The identity of the plug-in to get.
	:returns: The plug-in, or ``None`` if it doesn't exist.
	"""
	if identity in __plugins:
		return __plugins[identity]
	return None #Plug-in couldn't be found.

def __validate_metadata_global(metadata):
	"""
	.. function:: __validate_metadata_global(metadata)
	Checks if the global part of the metadata of a plug-in is correct.

	If it is incorrect, an exception is raised.

	The global part of the metadata includes the part of metadata that is common
	among all plug-ins.

	:param metadata: A dictionary containing the metadata of the plug-in.
	:raises MetadataValidationError: The metadata is invalid.
	"""
	required_fields = {"name", "description", "type", "version", "class"}
	try:
		if not required_fields <= metadata.keys(): #Set boolean comparison: Not all required_fields in metadata.
			raise MetadataValidationError("Required fields missing: " + str(required_fields - metadata.keys()))
		if not issubclass(metadata["class"], luna.plugin.Plugin):
			raise MetadataValidationError("The plug-in class is not a subclass of luna.plugin.Plugin.")
	except (AttributeError, TypeError):
		raise MetadataValidationError("Metadata is not a dictionary.")

def __validate_metadata_type(metadata):
	"""
	.. function:: __validate_metadata_type(metadata)
	Checks if the metadata of a plug-in type plug-in is correct.

	If it is incorrect, an exception is raised. At this point, the metadata must
	already be validated as plug-in metadata.

	:param metadata: A dictionary containing the metadata of the plug-in.
	:raises MetadataValidationError: The metadata is invalid.
	"""
	if "type" not in metadata:
		raise MetadataValidationError("This is not a plug-in type plug-in.")
	try:
		required_fields = {"api", "interface", "register"}
		if not required_fields <= metadata["type"].keys(): #Set boolean comparison: Not all required_fields in metadata["type"].
			raise MetadataValidationError("Required fields in type missing: " + str(required_fields - metadata["type"].keys()))
		if not "_abc_registry" in dir(metadata["type"]["interface"]):
			raise MetadataValidationError("The interface must be an abstract base class.")
		if not "__call__" in dir(metadata["type"]["register"]):
			raise MetadataValidationError("The register must be callable.")
	except (AttributeError, TypeError):
		raise MetadataValidationError("The type section is not a dictionary.")<commit_msg>Type plug-ins cannot have dependencies<commit_after>#!/usr/bin/env python

#This is free and unencumbered software released into the public domain.
#
#Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
#software, either in source code form or as a compiled binary, for any purpose,
#commercial or non-commercial, and by any means.
#
#In jurisdictions that recognise copyright laws, the author or authors of this
#software dedicate any and all copyright interest in the software to the public
#domain. We make this dedication for the benefit of the public at large and to
#the detriment of our heirs and successors. We intend this dedication to be an
#overt act of relinquishment in perpetuity of all present and future rights to
#this software under copyright law.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
#ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
#WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
#For more information, please refer to <https://unlicense.org/>.

"""
Handles all administration on plug-ins.
"""

import collections #For namedtuple.
import imp #Imports Python modules dynamically.
import os #To search through folders to find the plug-ins.

import luna.logger #Logging messages.
import luna.plugin #Checking if plug-ins provide the correct class.

__plugin_locations = []
"""
List of directories where to look for plug-ins.
"""

__plugins = {}
"""
Dictionary holding all plug-ins, indexed by their identity.
"""

__DependencyCandidate = collections.namedtuple("__DependencyCandidate", "identity type plugin_class dependencies")
"""
Represents a candidate dependency. We could run the __init__ of this candidate,
but we haven't resolved dependencies yet or instantiated the plug-in object.

This is a named tuple consisting of the following fields:
* identity: An unique identifier for the plug-in.
* type: The plug-in type.
* plugin_class: The class that implements the abstract base class of the
specified plug-in type.
* dependencies: A list of plug-in identities on which this plug-in depends.
"""

class MetadataValidationError(Exception):
	"""
	Marker exception to indicate that the metadata of a plug-in is invalid.
	"""

def add_plugin_location(location):
	"""
	.. function:: addPluginLocation(location)
	Adds a location to the list of locations where the application looks for
	plug-ins.

	It looks recursively through all subfolders of the specified folder.

	:param location: The location to add to the location list.
	"""
	if not location or not os.path.isdir(location): #Invalid plug-in location.
		raise NotADirectoryError("Plug-in location is not a path: {location}".format(location=location))
	__plugin_locations.append(location)

def discover():
	"""
	.. function:: discover()
	Discovers all plug-ins it can find.

	This goes through all folders of all plug-in types, and finds out what
	their metadata is. If the metadata is correct it will create a plug-in.
	When all plug-ins are loaded, the plug-ins whose dependencies can be met
	will be stored for later requesting. The plug-ins whose dependencies
	cannot be met will be discarded.

	This method can also be used to update the plug-in repository, but
	plug-ins are not deleted then. Only new plug-ins are added by this
	function.
	"""
	candidates = __find_candidates() #Makes a set of (id, path) tuples indicating names and folder paths of possible plug-ins.
	dependency_candidates = [] #Second stage of candidates. We could load these but haven't resolved dependencies yet. List of __DependencyCandidate instances.
	for identity, folder in candidates:
		luna.logger.debug("Loading plug-in {plugin} from {folder}.", plugin=identity, folder=folder)
		#Loading the plug-in.
		module = __load_candidate(identity, folder)
		if not module: #Failed to load module.
			continue
		if __get_plugin(identity):
			luna.logger.warning("Plug-in {plugin} is already loaded.", plugin=identity)
			continue

		#Parsing the metadata.
		try:
			metadata = module.metadata()
		except Exception as e:
			luna.logger.warning("Failed to load metadata of plug-in {plugin}: {error_message}", plugin=identity, error_message=str(e))
			continue
		try:
			__validate_metadata_global(metadata)
		except MetadataValidationError as e:
			luna.logger.warning("Metadata of plug-in {plugin} is invalid: {message}", plugin=identity, message=str(e))
			continue
		if "type" in metadata: #For plug-in type definitions, we have a built-in metadata checker.
			try:
				__validate_metadata_type(metadata)
			except MetadataValidationError as e:
				luna.logger.warning("Metadata of type plug-in {plugin} is invalid: {message}", plugin=identity, message=str(e))
				continue

		dependency_candidates.append(__DependencyCandidate(identity=identity, type=metadata["type"], plugin_class=metadata["class"], dependencies=dependencies))

	#Now go through the candidates to find plug-ins for which we can resolve the dependencies.
	for candidate in dependency_candidates:
		for dependency in candidate.dependencies:
			if dependency.count("/") != 1:
				luna.logger.warning("Plug-in {plugin} has an invalid dependency {dependency}.", plugin=candidate.identity, dependency=dependency)
				continue #With the next dependency.
			dependency_type, dependency_identity = dependency.split("/", 1) #Parse the dependency.
			for dependency_candidate in dependency_candidates:
				if dependency_identity == dependency_candidate.identity and dependency_type == dependency_candidate.type:
					break
			else: #Dependency was not found.
				luna.logger.warning("Plug-in {plugin} is missing dependency {dependency}!", plugin=candidate.identity, dependency=dependency_identity)
				break
		else: #All dependencies are resolved!
			try:
				plugin_instance = candidate.plugin_class() #Actually construct an instance of the plug-in.
			except Exception as e:
				luna.logger.warning("Initialising plug-in {plugin} failed: {error_message}", plugin=candidate.itentity, error_message=str(e))
				continue #With next plug-in.
			__plugins[candidate.identity] = plugin_instance

def get_interface(name):
	"""
	.. function:: getInterface(name)
	Gets an interface plug-in with the specified name, if it exists.

	:param name: The name of the interface plug-in to get.
	:returns: The specified interface, or ``None`` if it doesn't exist.
	"""
	return __get_plugin(name)

def get_interfaces():
	"""
	.. function:: getInterfaces()
	Gets all interface plug-ins.

	:returns: A list of all interface plug-ins.
	"""
	return __get_all_plugins_of_type("interface")

def get_logger(name):
	"""
	.. function:: getLogger(name)
	Gets a logger plug-in with the specified name, if it exists.

	:param name: The name of the logger plug-in to get.
	:returns: The specified logger, or ``None`` if it doesn't exist.
	"""
	return __get_plugin(name)

def get_loggers():
	"""
	.. function:: getLoggers()
	Gets all logger plug-ins.

	:returns: A list of all logger plug-ins.
	"""
	return __get_all_plugins_of_type("logger")

def __find_candidates():
	"""
	.. function:: __findCandidates()
	Finds candidates for what looks like might be plug-ins.

	A candidate is a folder inside a plug-in location, which has a file
	``__init__.py``. The file is not yet executed at this point.

	:returns: A set of tuples of the form (<identity>, <path>), containing
	respectively the identity of the plug-in and the path to the plug-in's
	folder.
	"""
	candidates = set()
	for location in __plugin_locations:
		for root, directories, files in os.walk(location, followlinks=True):
			if "__init__.py" not in files: #The directory must have an __init__.py file.
				continue
			directories[:] = [] #Remove all subdirectories. We aren't going to look in submodules.
			identity = os.path.basename(root) #The name of the folder becomes the plug-in's identity.
			candidates.add((identity, location))
	return candidates

def __load_candidate(identity, folder):
	"""
	.. function:: __loadCandidate(identity, folder)
	Loads a plug-in candidate as a Python package.

	This is intended to be used on Python packages, containing an init
	script.

	:param identity: The identity of the plug-in to load. Hierarchical
		identities are not supported.
	:param folder: The path to the folder where the plug-in can be found.
	:returns: A Python package representing the plug-in. If anything went
		wrong, ``None`` is returned.
	"""
	if "." in identity:
		luna.logger.warning("Can't load plug-in {plugin}: Invalid plug-in identity; periods are forbidden.", plugin=identity)
		return None
	try:
		file, path, description = imp.find_module(identity, [folder])
	except Exception as e:
		luna.logger.warning("Failed to find module of plug-in in {plugin}: {error_message}", plugin=folder, error_message=str(e))
		return None
	try:
		module = imp.load_module(identity, file, path, description)
	except Exception as e:
		luna.logger.warning("Failed to load plug-in {plugin}: {error_message}", plugin=identity, error_message=str(e))
		raise
	finally:
		if file: #Plug-in loading should not open any files, but if it does, close it immediately.
			luna.logger.warning("Plug-in {plugin} is a file: {filename}", plugin=identity, filename=str(file))
			file.close()
	return module

def __get_all_plugins_of_type(plugin_type):
	"""
	.. function:: __getAllPluginsOfType(type)
	Gets all plug-ins with the specified type.

	:returns: A list of all plug-ins of the specified plug-in type.
	"""
	result = []
	for (candidate_type, candidate_identity) in __plugins:
		if plugin_type == candidate_type:
			result.append(__plugins[(candidate_type, candidate_identity)])
	return result

def __get_plugin(identity):
	"""
	.. function:: __getPlugin(identity)
	Gets the plug-in with the specified identity, if it exists.

	:param identity: The identity of the plug-in to get.
	:returns: The plug-in, or ``None`` if it doesn't exist.
	"""
	if identity in __plugins:
		return __plugins[identity]
	return None #Plug-in couldn't be found.

def __validate_metadata_global(metadata):
	"""
	.. function:: __validate_metadata_global(metadata)
	Checks if the global part of the metadata of a plug-in is correct.

	If it is incorrect, an exception is raised.

	The global part of the metadata includes the part of metadata that is common
	among all plug-ins.

	:param metadata: A dictionary containing the metadata of the plug-in.
	:raises MetadataValidationError: The metadata is invalid.
	"""
	required_fields = {"name", "description", "type", "version", "class"}
	try:
		if not required_fields <= metadata.keys(): #Set boolean comparison: Not all required_fields in metadata.
			raise MetadataValidationError("Required fields missing: " + str(required_fields - metadata.keys()))
		if not issubclass(metadata["class"], luna.plugin.Plugin):
			raise MetadataValidationError("The plug-in class is not a subclass of luna.plugin.Plugin.")
	except (AttributeError, TypeError):
		raise MetadataValidationError("Metadata is not a dictionary.")

def __validate_metadata_type(metadata):
	"""
	.. function:: __validate_metadata_type(metadata)
	Checks if the metadata of a plug-in type plug-in is correct.

	If it is incorrect, an exception is raised. At this point, the metadata must
	already be validated as plug-in metadata.

	:param metadata: A dictionary containing the metadata of the plug-in.
	:raises MetadataValidationError: The metadata is invalid.
	"""
	if "type" not in metadata:
		raise MetadataValidationError("This is not a plug-in type plug-in.")
	try:
		required_fields = {"api", "interface", "register"}
		if not required_fields <= metadata["type"].keys(): #Set boolean comparison: Not all required_fields in metadata["type"].
			raise MetadataValidationError("Required fields in type missing: " + str(required_fields - metadata["type"].keys()))
		if not "_abc_registry" in dir(metadata["type"]["interface"]):
			raise MetadataValidationError("The interface must be an abstract base class.")
		if not "__call__" in dir(metadata["type"]["register"]):
			raise MetadataValidationError("The register must be callable.")
		if metadata["dependencies"]:
			raise MetadataValidationError("Type plug-ins may not have dependencies.")
	except (AttributeError, TypeError):
		raise MetadataValidationError("The type section is not a dictionary.")<|endoftext|>


<commit_before>"""function module.

This module contains the Operator class, which is then passed to an integrator
to run a full depletion simulation.
"""

from collections import namedtuple
import os
from pathlib import Path
from abc import ABC, abstractmethod
from xml.etree import ElementTree as ET
from warnings import warn

from numpy import nonzero, empty

from openmc.data import DataLibrary, JOULE_PER_EV
from openmc.checkvalue import check_type
from .chain import Chain

OperatorResult = namedtuple('OperatorResult', ['k', 'rates'])
OperatorResult.__doc__ = """\
Result of applying transport operator

Parameters
----------
k : float
    Resulting eigenvalue
rates : openmc.deplete.ReactionRates
    Resulting reaction rates

"""
try:
    OperatorResult.k.__doc__ = None
    OperatorResult.rates.__doc__ = None
except AttributeError:
    # Can't set __doc__ on properties on Python 3.4
    pass

class TransportOperator(ABC):
    """Abstract class defining a transport operator

    Each depletion integrator is written to work with a generic transport
    operator that takes a vector of material compositions and returns an
    eigenvalue and reaction rates. This abstract class sets the requirements for
    such a transport operator. Users should instantiate
    :class:`openmc.deplete.Operator` rather than this class.

    Parameters
    ----------
    chain_file : str, optional
        Path to the depletion chain XML file.  Defaults to the file
        listed under ``depletion_chain`` in
        :envvar:`OPENMC_CROSS_SECTIONS` environment variable.
    fission_q : dict, optional
        Dictionary of nuclides and their fission Q values [eV]. If not given,
        values will be pulled from the ``chain_file``.

    Attributes
    ----------
    dilute_initial : float
        Initial atom density to add for nuclides that are zero in initial
        condition to ensure they exist in the decay chain.  Only done for
        nuclides with reaction rates. Defaults to 1.0e3.

    """
    def __init__(self, chain_file=None, fission_q=None):
        self.dilute_initial = 1.0e3
        self.output_dir = '.'

        # Read depletion chain
        if chain_file is None:
            chain_file = os.environ.get("OPENMC_DEPLETE_CHAIN", None)
            if chain_file is None:
                data = DataLibrary.from_xml()
                # search for depletion_chain path from end of list
                for lib in reversed(data.libraries):
                    if lib['type'] == 'depletion_chain':
                        break
                else:
                    raise IOError(
                        "No chain specified, either manually or "
                        "under depletion_chain in environment variable "
                        "OPENMC_CROSS_SECTIONS.")
                chain_file = lib['path']
            else:
                warn("Use of OPENMC_DEPLETE_CHAIN is deprecated in favor "
                     "of adding depletion_chain to OPENMC_CROSS_SECTIONS",
                     FutureWarning)
        self.chain = Chain.from_xml(chain_file, fission_q)

    @abstractmethod
    def __call__(self, vec, print_out=True):
        """Runs a simulation.

        Parameters
        ----------
        vec : list of numpy.ndarray
            Total atoms to be used in function.
        print_out : bool, optional
            Whether or not to print out time.

        Returns
        -------
        openmc.deplete.OperatorResult
            Eigenvalue and reaction rates resulting from transport operator

        """
        pass

    def __enter__(self):
        # Save current directory and move to specific output directory
        self._orig_dir = os.getcwd()
        if not self.output_dir.exists():
            self.output_dir.mkdir()  # exist_ok parameter is 3.5+

        # In Python 3.6+, chdir accepts a Path directly
        os.chdir(str(self.output_dir))

        return self.initial_condition()

    def __exit__(self, exc_type, exc_value, traceback):
        self.finalize()
        os.chdir(self._orig_dir)

    @property
    def output_dir(self):
        return self._output_dir

    @output_dir.setter
    def output_dir(self, output_dir):
        self._output_dir = Path(output_dir)

    @abstractmethod
    def initial_condition(self):
        """Performs final setup and returns initial condition.

        Returns
        -------
        list of numpy.ndarray
            Total density for initial conditions.
        """

        pass

    @abstractmethod
    def get_results_info(self):
        """Returns volume list, cell lists, and nuc lists.

        Returns
        -------
        volume : list of float
            Volumes corresponding to materials in burn_list
        nuc_list : list of str
            A list of all nuclide names. Used for sorting the simulation.
        burn_list : list of int
            A list of all cell IDs to be burned.  Used for sorting the simulation.
        full_burn_list : list of int
            All burnable materials in the geometry.
        """

        pass

    def finalize(self):
        pass

class ReactionRateHelper(ABC):
    """Abstract class for generating reaction rates for operators

    Responsible for generating reaction rate tallies for burnable
    materials, given nuclides and scores from the operator.

    Reaction rates are passed back to the operator for be used in
    an :class:`openmc.deplete.OperatorResult` instance

    Attributes
    ----------
    nuclides : list of str
        All nuclides with desired reaction rates. Ordered to be
        consistent with :class:`openmc.deplete.Operator`
    """

    def __init__(self):
        self._nuclides = None
        self._rate_tally = None
        self._results_cache = None

    @abstractmethod
    def generate_tallies(self, materials, scores):
        """Use the C API to build tallies needed for reaction rates"""

    @property
    def nuclides(self):
        """List of nuclides with requested reaction rates"""
        return self._nuclides

    @nuclides.setter
    def nuclides(self, nuclides):
        check_type("nuclides", nuclides, list, str)
        self._nuclides = nuclides
        self._rate_tally.nuclides = nuclides

    def _get_results_cache(self, nnucs, nreact):
        """Cache for results for a given material

        Creates an empty array of shape ``(nnucs, nreact)``
        if the shape does not match the current cache.
        """
        if (self._results_cache is None
                or self._results_cache.shape != (nnucs, nreact)):
            self._results_cache = empty((nnucs, nreact))
        return self._results_cache

    @abstractmethod
    def get_material_rates(self, mat_id, nuc_index, react_index):
        """Return 2D array of [nuclide, reaction] reaction rates

        Parameters
        ----------
        mat_id : int
            Unique ID for the requested material
        nuc_index : list of str
            Ordering of desired nuclides
        react_index : list of str
            Ordering of reactions
        """

    def divide_by_adens(self, number):
        """Normalize reaction rates by number of nuclides

        Acts on the current material examined by
        :meth:`get_material_rates`

        Parameters
        ----------
            Ordered identically to :attr:`nuclides`

        Returns
        -------
        results : numpy.ndarray
            Array of reactions rates of shape ``(n_nuclides, n_rxns)``
            normalized by the number of nuclides
        """

        mask = nonzero(number)
        results = self._results_cache
        for col in range(results.shape[1]):
            results[mask, col] /= number[mask]
        return results

class EnergyHelper(ABC):
    """Abstract class for obtaining energy produced

    The ultimate goal of this helper is to provide instances of
    :class:`openmc.deplete.Operator` with the total energy produced
    in a transport simulation. This information, provided with the
    power requested by the user and reaction rates from a
    :class:`ReactionRateHelper` will scale reaction rates to the
    correct values.

    Attributes
    ----------
    nuclides : list of str
        All nuclides with desired reaction rates. Ordered to be
        consistent with :class:`openmc.deplete.Operator`
    energy : float
        Total energy [J/s/source neutron] produced in a transport simulation.
        Updated in the material iteration with :meth:`update`.
    """

    def __init__(self):
        self._nuclides = None
        self._energy = 0.0

    @property
    def energy(self):
        return self._energy * JOULE_PER_EV

    def reset(self):
        """Reset energy produced prior to unpacking tallies"""
        self._energy = 0.0

    @abstractmethod
    def prepare(self, chain_nucs, rate_index, materials):
        """Perform work needed to obtain energy produced

        This method is called prior to the transport simulations
        in :meth:`openmc.deplete.Operator.initial_condition`.

        Parameters
        ----------
        chain_nucs : list of str
            All nuclides to be tracked in this problem
        rate_index : dict of str to int
            Mapping from nuclide name to index in the
            `fission_rates` for :meth:`update`.
        materials : list of str
            All materials tracked on the operator helped by this
            object. Should correspond to
            :attr:`openmc.deplete.Operator.burnable_materials`
        """

    def update(self, fission_rates, mat_index):
        """Update the energy produced

        Parameters
        ----------
        fission_rates : numpy.ndarray
            fission reaction rate for each isotope in the specified
            material. Should be ordered corresponding to initial
            ``rate_index`` used in :meth:`prepare`
        mat_index : int
            Index for the specific material in the list of all burnable
            materials.
        """

    @property
    def nuclides(self):
        """List of nuclides with requested reaction rates"""
        return self._nuclides

    @nuclides.setter
    def nuclides(self, nuclides):
        check_type("nuclides", nuclides, list, str)
        self._nuclides = nuclides
<commit_msg>Re-document number parameter in ReactionRateHelper.divide_by_adens<commit_after>"""function module.

This module contains the Operator class, which is then passed to an integrator
to run a full depletion simulation.
"""

from collections import namedtuple
import os
from pathlib import Path
from abc import ABC, abstractmethod
from xml.etree import ElementTree as ET
from warnings import warn

from numpy import nonzero, empty

from openmc.data import DataLibrary, JOULE_PER_EV
from openmc.checkvalue import check_type
from .chain import Chain

OperatorResult = namedtuple('OperatorResult', ['k', 'rates'])
OperatorResult.__doc__ = """\
Result of applying transport operator

Parameters
----------
k : float
    Resulting eigenvalue
rates : openmc.deplete.ReactionRates
    Resulting reaction rates

"""
try:
    OperatorResult.k.__doc__ = None
    OperatorResult.rates.__doc__ = None
except AttributeError:
    # Can't set __doc__ on properties on Python 3.4
    pass

class TransportOperator(ABC):
    """Abstract class defining a transport operator

    Each depletion integrator is written to work with a generic transport
    operator that takes a vector of material compositions and returns an
    eigenvalue and reaction rates. This abstract class sets the requirements for
    such a transport operator. Users should instantiate
    :class:`openmc.deplete.Operator` rather than this class.

    Parameters
    ----------
    chain_file : str, optional
        Path to the depletion chain XML file.  Defaults to the file
        listed under ``depletion_chain`` in
        :envvar:`OPENMC_CROSS_SECTIONS` environment variable.
    fission_q : dict, optional
        Dictionary of nuclides and their fission Q values [eV]. If not given,
        values will be pulled from the ``chain_file``.

    Attributes
    ----------
    dilute_initial : float
        Initial atom density to add for nuclides that are zero in initial
        condition to ensure they exist in the decay chain.  Only done for
        nuclides with reaction rates. Defaults to 1.0e3.

    """
    def __init__(self, chain_file=None, fission_q=None):
        self.dilute_initial = 1.0e3
        self.output_dir = '.'

        # Read depletion chain
        if chain_file is None:
            chain_file = os.environ.get("OPENMC_DEPLETE_CHAIN", None)
            if chain_file is None:
                data = DataLibrary.from_xml()
                # search for depletion_chain path from end of list
                for lib in reversed(data.libraries):
                    if lib['type'] == 'depletion_chain':
                        break
                else:
                    raise IOError(
                        "No chain specified, either manually or "
                        "under depletion_chain in environment variable "
                        "OPENMC_CROSS_SECTIONS.")
                chain_file = lib['path']
            else:
                warn("Use of OPENMC_DEPLETE_CHAIN is deprecated in favor "
                     "of adding depletion_chain to OPENMC_CROSS_SECTIONS",
                     FutureWarning)
        self.chain = Chain.from_xml(chain_file, fission_q)

    @abstractmethod
    def __call__(self, vec, print_out=True):
        """Runs a simulation.

        Parameters
        ----------
        vec : list of numpy.ndarray
            Total atoms to be used in function.
        print_out : bool, optional
            Whether or not to print out time.

        Returns
        -------
        openmc.deplete.OperatorResult
            Eigenvalue and reaction rates resulting from transport operator

        """
        pass

    def __enter__(self):
        # Save current directory and move to specific output directory
        self._orig_dir = os.getcwd()
        if not self.output_dir.exists():
            self.output_dir.mkdir()  # exist_ok parameter is 3.5+

        # In Python 3.6+, chdir accepts a Path directly
        os.chdir(str(self.output_dir))

        return self.initial_condition()

    def __exit__(self, exc_type, exc_value, traceback):
        self.finalize()
        os.chdir(self._orig_dir)

    @property
    def output_dir(self):
        return self._output_dir

    @output_dir.setter
    def output_dir(self, output_dir):
        self._output_dir = Path(output_dir)

    @abstractmethod
    def initial_condition(self):
        """Performs final setup and returns initial condition.

        Returns
        -------
        list of numpy.ndarray
            Total density for initial conditions.
        """

        pass

    @abstractmethod
    def get_results_info(self):
        """Returns volume list, cell lists, and nuc lists.

        Returns
        -------
        volume : list of float
            Volumes corresponding to materials in burn_list
        nuc_list : list of str
            A list of all nuclide names. Used for sorting the simulation.
        burn_list : list of int
            A list of all cell IDs to be burned.  Used for sorting the simulation.
        full_burn_list : list of int
            All burnable materials in the geometry.
        """

        pass

    def finalize(self):
        pass

class ReactionRateHelper(ABC):
    """Abstract class for generating reaction rates for operators

    Responsible for generating reaction rate tallies for burnable
    materials, given nuclides and scores from the operator.

    Reaction rates are passed back to the operator for be used in
    an :class:`openmc.deplete.OperatorResult` instance

    Attributes
    ----------
    nuclides : list of str
        All nuclides with desired reaction rates. Ordered to be
        consistent with :class:`openmc.deplete.Operator`
    """

    def __init__(self):
        self._nuclides = None
        self._rate_tally = None
        self._results_cache = None

    @abstractmethod
    def generate_tallies(self, materials, scores):
        """Use the C API to build tallies needed for reaction rates"""

    @property
    def nuclides(self):
        """List of nuclides with requested reaction rates"""
        return self._nuclides

    @nuclides.setter
    def nuclides(self, nuclides):
        check_type("nuclides", nuclides, list, str)
        self._nuclides = nuclides
        self._rate_tally.nuclides = nuclides

    def _get_results_cache(self, nnucs, nreact):
        """Cache for results for a given material

        Creates an empty array of shape ``(nnucs, nreact)``
        if the shape does not match the current cache.
        """
        if (self._results_cache is None
                or self._results_cache.shape != (nnucs, nreact)):
            self._results_cache = empty((nnucs, nreact))
        return self._results_cache

    @abstractmethod
    def get_material_rates(self, mat_id, nuc_index, react_index):
        """Return 2D array of [nuclide, reaction] reaction rates

        Parameters
        ----------
        mat_id : int
            Unique ID for the requested material
        nuc_index : list of str
            Ordering of desired nuclides
        react_index : list of str
            Ordering of reactions
        """

    def divide_by_adens(self, number):
        """Normalize reaction rates by number of nuclides

        Acts on the current material examined by
        :meth:`get_material_rates`

        Parameters
        ----------
        number : iterable of float
            Number density [atoms/b-cm] of each nuclide tracked in the calculation.
            Ordered identically to :attr:`nuclides`

        Returns
        -------
        results : numpy.ndarray
            Array of reactions rates of shape ``(n_nuclides, n_rxns)``
            normalized by the number of nuclides
        """

        mask = nonzero(number)
        results = self._results_cache
        for col in range(results.shape[1]):
            results[mask, col] /= number[mask]
        return results

class EnergyHelper(ABC):
    """Abstract class for obtaining energy produced

    The ultimate goal of this helper is to provide instances of
    :class:`openmc.deplete.Operator` with the total energy produced
    in a transport simulation. This information, provided with the
    power requested by the user and reaction rates from a
    :class:`ReactionRateHelper` will scale reaction rates to the
    correct values.

    Attributes
    ----------
    nuclides : list of str
        All nuclides with desired reaction rates. Ordered to be
        consistent with :class:`openmc.deplete.Operator`
    energy : float
        Total energy [J/s/source neutron] produced in a transport simulation.
        Updated in the material iteration with :meth:`update`.
    """

    def __init__(self):
        self._nuclides = None
        self._energy = 0.0

    @property
    def energy(self):
        return self._energy * JOULE_PER_EV

    def reset(self):
        """Reset energy produced prior to unpacking tallies"""
        self._energy = 0.0

    @abstractmethod
    def prepare(self, chain_nucs, rate_index, materials):
        """Perform work needed to obtain energy produced

        This method is called prior to the transport simulations
        in :meth:`openmc.deplete.Operator.initial_condition`.

        Parameters
        ----------
        chain_nucs : list of str
            All nuclides to be tracked in this problem
        rate_index : dict of str to int
            Mapping from nuclide name to index in the
            `fission_rates` for :meth:`update`.
        materials : list of str
            All materials tracked on the operator helped by this
            object. Should correspond to
            :attr:`openmc.deplete.Operator.burnable_materials`
        """

    def update(self, fission_rates, mat_index):
        """Update the energy produced

        Parameters
        ----------
        fission_rates : numpy.ndarray
            fission reaction rate for each isotope in the specified
            material. Should be ordered corresponding to initial
            ``rate_index`` used in :meth:`prepare`
        mat_index : int
            Index for the specific material in the list of all burnable
            materials.
        """

    @property
    def nuclides(self):
        """List of nuclides with requested reaction rates"""
        return self._nuclides

    @nuclides.setter
    def nuclides(self, nuclides):
        check_type("nuclides", nuclides, list, str)
        self._nuclides = nuclides
<|endoftext|>


<commit_before>#!/usr/bin/env python
# encoding: utf-8
"""
Access to HST data sets.

The environment variables $PHATDATA and $BROWNDATA should point to root
directories for the PHAT and Brown HLSP datasets.

2013-12-02 - Created by Jonathan Sick
"""

import os
import glob

def _instr_name(band):
    """Map instrument names to bands."""
    if band in ["f475w", "f814w"]:
        instr = "acs-wfc"
    elif band in ["f110w", "f160w"]:
        instr = "wfc3-ir"
    elif band in ["f275w", "f336w"]:
        instr = "wf3-uvis"
    return instr

def _phat_basedir(brick):
    """Base directory for PHAT brick data"""
    return os.path.join(os.getenv('PHATDATA', None), "brick%02i" % brick)

def phat_phot_path(brick, field, filterset, kind='gst'):
    """Get path to a photometry product for a specific phat field.

    Parameters
    ----------

    brick : int
        Number of the brick (1-23).
    field : int
        Number of the field within the brick.
    filterset : str
        Name of the filter set: ``f275w-f336w``.
    """
    paths = glob.glob(os.path.join(_phat_basedir,
        "*-b%02i_%s_v1_%s.fits" % (brick, field, filterset, kind)))
    assert len(paths) == 1
    assert os.path.exists(paths[0])
    return paths[0]

def phat_brick_path(brick, band):
    """Get path to the drizzled FITS image for a PHAT brick.

    Parameters
    ----------

    brick : int
        Number of the brick (1-23).
    band : str
        Name of the filter: e.g. ``f275w``.
    """
    prefix = "hlsp_phat_hst"
    b = "*-M31-b%02i" % brick
    postfix = "v1_drz.fits"
    instr = _instr_name(band)
    filename = "_".join((prefix, instr, b, band, postfix))
    paths = glob.glob(os.path.join(_phat_basedir(brick), filename))
    assert len(paths) == 1
    assert os.path.exists(paths[0])
    return paths[0]

def phat_field_path(brick, field, band):
    """Get path to the drizzled FITS image for a specific PHAT field.

    Parameters
    ----------

    brick : int
        Number of the brick (1-23).
    field : int
        Number of the field within the brick.
    band : str
        Name of the filter: e.g. ``f275w``.
    """
    prefix = "hlsp_phat_hst"
    b = "*-M31-b%02i-f%02i" % (brick, field)
    postfix = "v1_drz.fits"
    instr = _instr_name(band)
    filename = "_".join((prefix, instr, b, band, postfix))
    paths = glob.glob(os.path.join(_phat_basedir(brick), filename))
    assert len(paths) == 1
    assert os.path.exists(paths[0])
    return paths[0]

def brown_phot_path(field, kind='cat'):
    """Get path for a Brown GO-10265 HLSP photometry product.

    Parameters
    ----------
    field : str
        Field name: halo11, stream, disk, halo21, halo35a, halo35b
    kind : str
        Data type. `cat` for photometry catalog, `art` for artificial stars
        and `msk` for a mask image.
    """
    if kind == 'cat':
        ext = '_v2_cat.txt'
    elif kind == 'art':
        ext = '_v2_art.fits'
    elif kind == 'msk':
        ext = '_v2_msk.fits'
    path = os.path.join(os.getenv('BROWNDATA', None),
            'hlsp_andromeda_hst_acs-wfc_%s_f606w-f814w%s' % (field, ext))
    assert os.path.exists(path)
    return path

def brown_image_path(field, band):
    """Get path for a Brown GO-10265 HLSP drizzled image product.

    Parameters
    ----------
    field : str
        Field name: halo11, stream, disk, halo21, halo35a, halo35b
    band : str
        Filter name. Either f814w or f606w.
    """
    path = os.path.join(os.getenv('BROWNDATA', None),
            'hlsp_andromeda_hst_acs-wfc_%s_%s_v2_img.fits' % (field, band))
    assert os.path.exists(path)
    return path

def main():
    pass

if __name__ == '__main__':
    main()
<commit_msg>Small fixes to paths<commit_after>#!/usr/bin/env python
# encoding: utf-8
"""
Access to HST data sets.

The environment variables $PHATDATA and $BROWNDATA should point to root
directories for the PHAT and Brown HLSP datasets.

2013-12-02 - Created by Jonathan Sick
"""

import os
import glob

def _instr_name(band):
    """Map instrument names to bands."""
    if band in ["f475w", "f814w"]:
        instr = "acs-wfc"
    elif band in ["f110w", "f160w"]:
        instr = "wfc3-ir"
    elif band in ["f275w", "f336w"]:
        instr = "wf3-uvis"
    return instr

def _phat_basedir(brick):
    """Base directory for PHAT brick data"""
    return os.path.join(os.getenv('PHATDATA', None), "brick%02i" % brick)

def phat_phot_path(brick, field, filterset, kind='gst'):
    """Get path to a photometry product for a specific phat field.

    Parameters
    ----------

    brick : int
        Number of the brick (1-23).
    field : int
        Number of the field within the brick.
    filterset : str
        Name of the filter set: ``f275w-f336w``.
    """
    paths = glob.glob(os.path.join(_phat_basedir(brick),
        "*-b%02i-f%02i_%s_v1_%s.fits" % (brick, field, filterset, kind)))
    assert len(paths) == 1
    assert os.path.exists(paths[0])
    return paths[0]

def phat_brick_path(brick, band):
    """Get path to the drizzled FITS image for a PHAT brick.

    Parameters
    ----------

    brick : int
        Number of the brick (1-23).
    band : str
        Name of the filter: e.g. ``f275w``.
    """
    prefix = "hlsp_phat_hst"
    b = "*-m31-b%02i" % brick
    postfix = "v1_drz.fits"
    instr = _instr_name(band)
    filename = "_".join((prefix, instr, b, band, postfix))
    temp = os.path.join(_phat_basedir(brick), filename)
    paths = glob.glob(temp)
    assert len(paths) == 1
    assert os.path.exists(paths[0])
    return paths[0]

def phat_field_path(brick, field, band):
    """Get path to the drizzled FITS image for a specific PHAT field.

    Parameters
    ----------

    brick : int
        Number of the brick (1-23).
    field : int
        Number of the field within the brick.
    band : str
        Name of the filter: e.g. ``f275w``.
    """
    prefix = "hlsp_phat_hst"
    b = "*-m31-b%02i-f%02i" % (brick, field)
    postfix = "v1_drz.fits"
    instr = _instr_name(band)
    filename = "_".join((prefix, instr, b, band, postfix))
    paths = glob.glob(os.path.join(_phat_basedir(brick), filename))
    assert len(paths) == 1
    assert os.path.exists(paths[0])
    return paths[0]

def brown_phot_path(field, kind='cat'):
    """Get path for a Brown GO-10265 HLSP photometry product.

    Parameters
    ----------
    field : str
        Field name: halo11, stream, disk, halo21, halo35a, halo35b
    kind : str
        Data type. `cat` for photometry catalog, `art` for artificial stars
        and `msk` for a mask image.
    """
    if kind == 'cat':
        ext = '_v2_cat.txt'
    elif kind == 'art':
        ext = '_v2_art.fits'
    elif kind == 'msk':
        ext = '_v2_msk.fits'
    path = os.path.join(os.getenv('BROWNDATA', None),
            'hlsp_andromeda_hst_acs-wfc_%s_f606w-f814w%s' % (field, ext))
    assert os.path.exists(path)
    return path

def brown_image_path(field, band):
    """Get path for a Brown GO-10265 HLSP drizzled image product.

    Parameters
    ----------
    field : str
        Field name: halo11, stream, disk, halo21, halo35a, halo35b
    band : str
        Filter name. Either f814w or f606w.
    """
    path = os.path.join(os.getenv('BROWNDATA', None),
            'hlsp_andromeda_hst_acs-wfc_%s_%s_v2_img.fits' % (field, band))
    assert os.path.exists(path)
    return path

def main():
    pass

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before>'''
    ModernGL framebuffers
'''

from .common import InvalidObject

class Framebuffer:
    '''
        Create a :py:class:`Framebuffer` using :py:meth:`Context.framebuffer`.
    '''

    def __init__(self):
        self.mglo = None
        raise NotImplementedError()

    def release(self):
        '''
            Release the ModernGL object.
        '''

        self.mglo.release()
        self.__class__ = InvalidObject

    @staticmethod
    def new(obj):
        '''
            For internal use only.
        '''

        res = Framebuffer.__new__(Framebuffer)
        res.mglo = obj
        return res

    def clear(self, r=0.0, g=0.0, b=0.0, a=0.0, *, viewport=None) -> None:
        '''
            Clear the framebuffer.

            Values must be in ``(0, 255)`` range.
            If the `viewport` is not ``None`` then scrissor test
            will be used to clear the given viewport.

            If the `viewport` is a 2-tuple it will clear the
            ``(0, 0, width, height)`` where ``(width, height)`` is the 2-tuple.

            If the `viewport` is a 4-tuple it will clear the given viewport.

            Args:
                red (int): color component.
                green (int): color component.
                blue (int): color component.
                alpha (int): alpha component.

            Keyword Args:
                viewport (tuple): The viewport.
        '''

        if viewport is not None:
            viewport = tuple(viewport)

        self.mglo.clear(r, g, b, a, viewport)

    def use(self) -> None:
        '''
            Bind the framebuffer. Set the target for the
            `VertexArray.render` or `VertexArray.transform` methods.
        '''

        self.mglo.use()

    def read(self, viewport=None, components=3, *, floats=False) -> bytes:
        '''
            Read the framebuffer content.
        '''

        return self.mglo.read(viewport, components, floats)
<commit_msg>rgba -> reg green blue alpha<commit_after>'''
    ModernGL framebuffers
'''

from .common import InvalidObject

class Framebuffer:
    '''
        Create a :py:class:`Framebuffer` using :py:meth:`Context.framebuffer`.
    '''

    def __init__(self):
        self.mglo = None
        raise NotImplementedError()

    def release(self):
        '''
            Release the ModernGL object.
        '''

        self.mglo.release()
        self.__class__ = InvalidObject

    @staticmethod
    def new(obj):
        '''
            For internal use only.
        '''

        res = Framebuffer.__new__(Framebuffer)
        res.mglo = obj
        return res

    def clear(self, red=0, green=0, blue=0, alpha=0, *, viewport=None) -> None:
        '''
            Clear the framebuffer.

            Values must be in ``(0, 255)`` range.
            If the `viewport` is not ``None`` then scrissor test
            will be used to clear the given viewport.

            If the `viewport` is a 2-tuple it will clear the
            ``(0, 0, width, height)`` where ``(width, height)`` is the 2-tuple.

            If the `viewport` is a 4-tuple it will clear the given viewport.

            Args:
                red (int): color component.
                green (int): color component.
                blue (int): color component.
                alpha (int): alpha component.

            Keyword Args:
                viewport (tuple): The viewport.
        '''

        if viewport is not None:
            viewport = tuple(viewport)

        self.mglo.clear(red, green, blue, alpha, viewport)

    def use(self) -> None:
        '''
            Bind the framebuffer. Set the target for the
            `VertexArray.render` or `VertexArray.transform` methods.
        '''

        self.mglo.use()

    def read(self, viewport=None, components=3, *, floats=False) -> bytes:
        '''
            Read the framebuffer content.
        '''

        return self.mglo.read(viewport, components, floats)
<|endoftext|>


<commit_before>import collections
import subprocess

try:
    from subprocess import run as subprocess_run
    from subprocess import CalledProcessError
except ImportError:
    _CompletedProcess = collections.namedtuple('_CompletedProcess', ['args', 'retcode', 'stdout', 'stderr'])

    class CalledProcessError(subprocess.CalledProcessError):
        def __init__(self, returncode, cmd, output=None, stderr=None):
            super().__init__(returncode, cmd, output)
            self.stderr = stderr

        @property
        def stdout(self):
            return self.output

        @stdout.setter
        def stdout(self, value):
            self.output = value

    def subprocess_run(command, stdin=None, input=None, check=False, stdout=None, stderr=None):
        if input is not None:
            if stdin is not None:
                raise ValueError('stdin and input arguments can not be used at the same time.')
            stdin = subprocess.PIPE

        with subprocess.Popen(command, stdin=stdin,
                              stdout=stdout, stderr=stderr) as process:
            try:
                stdout, stderr = process.communicate(input=input)
            except:
                process.kill()
                process.wait()
                raise
            retcode = process.poll()
            if check and retcode:
                raise CalledProcessError(returncode=retcode, cmd=process.args,
                                                    output=stdout, stderr=stderr)
        return _CompletedProcess(args=process.args, retcode=retcode,
                                 stdout=stdout, stderr=stderr)
<commit_msg>Fixed code formatting.<commit_after>import collections
import subprocess

try:
    from subprocess import run as subprocess_run
    from subprocess import CalledProcessError
except ImportError:
    _CompletedProcess = collections.namedtuple('_CompletedProcess', ['args', 'retcode', 'stdout', 'stderr'])

    class CalledProcessError(subprocess.CalledProcessError):
        def __init__(self, returncode, cmd, output=None, stderr=None):
            super().__init__(returncode, cmd, output)
            self.stderr = stderr

        @property
        def stdout(self):
            return self.output

        @stdout.setter
        def stdout(self, value):
            self.output = value

    def subprocess_run(command, stdin=None, input=None, check=False, stdout=None, stderr=None):
        if input is not None:
            if stdin is not None:
                raise ValueError('stdin and input arguments can not be used at the same time.')
            stdin = subprocess.PIPE

        with subprocess.Popen(command, stdin=stdin,
                              stdout=stdout, stderr=stderr) as process:
            try:
                stdout, stderr = process.communicate(input=input)
            except:
                process.kill()
                process.wait()
                raise
            retcode = process.poll()
            if check and retcode:
                raise CalledProcessError(returncode=retcode, cmd=process.args,
                                         output=stdout, stderr=stderr)
        return _CompletedProcess(args=process.args, retcode=retcode,
                                 stdout=stdout, stderr=stderr)
<|endoftext|>


<commit_before># Parser for the OpenNSA command-line tool.
#
# Commands: reserve, provision, release, terminate, query
#
# Options:

# -f defaults file
# -w wsdl directory

# -h host (for callback)
# -o port (for callback)

# -u service url

# -t topology file
# -n network

# -p provider nsa
# -r requester nsa

# -c connection id
# -g global reservation id

# -s source stp
# -d dest stp
# -a start time
# -e end time
# -b bandwidth (megabits)

# -l certificate (signed public key)
# -k key (private key)
# -i certificate directory

# Flags
# -v verbose
# -q dump soap payloads
# -x Use TLS for callback port
# -z (skip) verify certificate (default is to verify)

# free switches
# ijmy

# Not all commands will accept all flags and some flags are mutally exclusive

from twisted.python import usage

from opennsa.cli import options

# parameters used for all commands

class DefaultsFileOption(usage.Options):
    optParameters = [ [ options.DEFAULTS_FILE, 'f', None, 'Defaults file'] ]

class WSDLDirectoryOption(usage.Options):
    optParameters = [ [ options.WSDL_DIRECTORY, 'w', None, 'Service URL'] ]

class HostOption(usage.Options):
    optParameters = [ [ options.HOST, 'h', None, 'Host (for callback)'] ]

class PortOption(usage.Options):
    optParameters = [ [ options.PORT, 'o', None, 'Port (for callback)'] ]

# parameters which are only used for some commands

class ServiceURLOption(usage.Options):
    optParameters = [ [ options.SERVICE_URL, 'u', None, 'Service URL'] ]

class TopologyFileOption(usage.Options):
    optParameters = [ [ options.TOPOLOGY_FILE, 't', None, 'Topology File'] ]

class NetworkOption(usage.Options):
    optParameters = [ [ options.NETWORK, 'n', None, 'Provider Network'] ]

class ProviderNSAOption(usage.Options):
    optParameters = [ [ options.PROVIDER, 'p', None, 'Provider NSA Identity'] ]

class RequesterNSAOption(usage.Options):
    optParameters = [ [ options.REQUESTER, 'r', None, 'Requester NSA Identity'] ]

class SourceSTPOption(usage.Options):
    optParameters = [ [ options.SOURCE_STP, 's', None, 'Source STP'] ]

class DestSTPOption(usage.Options):
    optParameters = [ [ options.DEST_STP, 'd', None, 'Dest STP'] ]

class ConnectionIDOption(usage.Options):
    optParameters = [ [ options.CONNECTION_ID, 'c', None, 'Connection id'] ]

class GlobalIDOption(usage.Options):
    optParameters = [ [ options.GLOBAL_ID, 'g', None, 'Global id'] ]

class StartTimeOption(usage.Options):
    optParameters = [ [ options.START_TIME, 'a', None, 'Start time'] ]
    def postOptions(self):
        if self[options.START_TIME] is not None:
            self[options.START_TIME] = options.parseTimestamp( self[options.START_TIME] )

class EndTimeOption(usage.Options):
    optParameters = [ [ options.END_TIME, 'e', None, 'End time'] ]
    def postOptions(self):
        if self[options.END_TIME] is not None:
            self[options.END_TIME] = options.parseTimestamp( self[options.END_TIME] )

class BandwidthOption(usage.Options):
    optParameters = [ [ options.BANDWIDTH, 'b', None, 'Bandwidth (Megabits)'] ]

class PublicKeyOption(usage.Options):
    optParameters = [ [ options.CERTIFICATE, 'l', None, 'Certificate path' ] ]

class PrivateKeyOption(usage.Options):
    optParameters = [ [ options.KEY, 'k', None, 'Private key path' ] ]

class CertificateDirectoryOption(usage.Options):
    optParameters = [ [ options.CERTIFICATE_DIR, 'i', None, 'Certificate directory' ] ]

# flags

class TLSFlag(usage.Options):
    optFlags = [ [ options.TLS, 'x', 'Use TLS for listener port' ] ]

class SkipCertificateVerificationFlag(usage.Options):
    optFlags = [ [ options.VERIFY_CERT, 'z', 'Skip certificate verification' ] ]

class FullGraphFlag(usage.Options):
    optFlags = [ [ options.FULL_GRAPH, 'l', 'Render full graph with all links.' ] ]

# command options

class BaseOptions(DefaultsFileOption):

    optFlags = [
        [ options.VERBOSE, 'v', 'Print out more information'],
        [ options.DUMP_PAYLOAD, 'q', 'Dump message payloads'],
    ]

class NetworkBaseOptions(BaseOptions, WSDLDirectoryOption, HostOption, PortOption,
                         ServiceURLOption, TopologyFileOption, NetworkOption,
                         TLSFlag, PublicKeyOption, PrivateKeyOption, CertificateDirectoryOption, SkipCertificateVerificationFlag):

    def postOptions(self):
        # technically we should do this for all superclasses, but this is the only one that has anything to do
        if self[options.SERVICE_URL] and (self[options.TOPOLOGY_FILE] or self[options.NETWORK]):
            raise usage.UsageError('Cannot set service url while having topology file or network.')

class NetworkCommandOptions(NetworkBaseOptions, ProviderNSAOption, RequesterNSAOption, ConnectionIDOption, GlobalIDOption):
    pass

class DiscoveryOptions(NetworkBaseOptions):
    pass

class ReserveOptions(NetworkCommandOptions, SourceSTPOption, DestSTPOption, StartTimeOption, EndTimeOption, BandwidthOption):

    def postOptions(self):
        NetworkCommandOptions.postOptions(self)
        StartTimeOption.postOptions(self)
        EndTimeOption.postOptions(self)

class PathOptions(BaseOptions, SourceSTPOption, DestSTPOption, TopologyFileOption):
    pass

class TopologyOptions(BaseOptions, TopologyFileOption):
    pass

class TopologyGraphOptions(TopologyOptions, FullGraphFlag):
    pass

class ProvisionReleaseTerminateOptions(NetworkCommandOptions):
    pass

class Options(usage.Options):
    subCommands = [
        ['discover',        None,   DiscoveryOptions,       'Discover services at an NSA.'],
        ['reserve',         None,   ReserveOptions,         'Create a reservation.'],
        ['reserveprovision',None,   ReserveOptions,         'Create a reservation and provision the connection.'],
        ['provision',       None,   NetworkCommandOptions,  'Provision a connection.'],
        ['release',         None,   NetworkCommandOptions,  'Release a connection.'],
        ['terminate',       None,   NetworkCommandOptions,  'Terminate a connection.'],
        ['querysummary',    None,   NetworkCommandOptions,  'Query a connection (summary).'],
        ['querydetails',    None,   NetworkCommandOptions,  'Query a connection (recursive).'],
        ['path',            None,   PathOptions,            'Print possible paths from source STP to destination STP.'],
        ['topology',        None,   TopologyOptions,        'Print (known) topology information.'],
        ['topology-graph',  None,   TopologyGraphOptions,   'Print a machine parsable network topology (Graphviz).']
    ]

    def postOptions(self):
        if self.subCommand is None:
            return usage.UsageError('No option specified')

    def opt_version(self):
        from opennsa import __version__
        from twisted import copyright
        print "OpenNSA version %s. " % __version__ + \
              "Running on Twisted version", copyright.version
        raise SystemExit

<commit_msg>add comment about number being free for switches in cli parser<commit_after># Parser for the OpenNSA command-line tool.
#
# Commands: reserve, provision, release, terminate, query
#
# Options:

# -f defaults file
# -w wsdl directory

# -h host (for callback)
# -o port (for callback)

# -u service url

# -t topology file
# -n network

# -p provider nsa
# -r requester nsa

# -c connection id
# -g global reservation id

# -s source stp
# -d dest stp
# -a start time
# -e end time
# -b bandwidth (megabits)

# -l certificate (signed public key)
# -k key (private key)
# -i certificate directory

# Flags
# -v verbose
# -q dump soap payloads
# -x Use TLS for callback port
# -z (skip) verify certificate (default is to verify)

# free switches
# ijmy + 0-9

# Not all commands will accept all flags and some flags are mutally exclusive

from twisted.python import usage

from opennsa.cli import options

# parameters used for all commands

class DefaultsFileOption(usage.Options):
    optParameters = [ [ options.DEFAULTS_FILE, 'f', None, 'Defaults file'] ]

class WSDLDirectoryOption(usage.Options):
    optParameters = [ [ options.WSDL_DIRECTORY, 'w', None, 'Service URL'] ]

class HostOption(usage.Options):
    optParameters = [ [ options.HOST, 'h', None, 'Host (for callback)'] ]

class PortOption(usage.Options):
    optParameters = [ [ options.PORT, 'o', None, 'Port (for callback)'] ]

# parameters which are only used for some commands

class ServiceURLOption(usage.Options):
    optParameters = [ [ options.SERVICE_URL, 'u', None, 'Service URL'] ]

class TopologyFileOption(usage.Options):
    optParameters = [ [ options.TOPOLOGY_FILE, 't', None, 'Topology File'] ]

class NetworkOption(usage.Options):
    optParameters = [ [ options.NETWORK, 'n', None, 'Provider Network'] ]

class ProviderNSAOption(usage.Options):
    optParameters = [ [ options.PROVIDER, 'p', None, 'Provider NSA Identity'] ]

class RequesterNSAOption(usage.Options):
    optParameters = [ [ options.REQUESTER, 'r', None, 'Requester NSA Identity'] ]

class SourceSTPOption(usage.Options):
    optParameters = [ [ options.SOURCE_STP, 's', None, 'Source STP'] ]

class DestSTPOption(usage.Options):
    optParameters = [ [ options.DEST_STP, 'd', None, 'Dest STP'] ]

class ConnectionIDOption(usage.Options):
    optParameters = [ [ options.CONNECTION_ID, 'c', None, 'Connection id'] ]

class GlobalIDOption(usage.Options):
    optParameters = [ [ options.GLOBAL_ID, 'g', None, 'Global id'] ]

class StartTimeOption(usage.Options):
    optParameters = [ [ options.START_TIME, 'a', None, 'Start time'] ]
    def postOptions(self):
        if self[options.START_TIME] is not None:
            self[options.START_TIME] = options.parseTimestamp( self[options.START_TIME] )

class EndTimeOption(usage.Options):
    optParameters = [ [ options.END_TIME, 'e', None, 'End time'] ]
    def postOptions(self):
        if self[options.END_TIME] is not None:
            self[options.END_TIME] = options.parseTimestamp( self[options.END_TIME] )

class BandwidthOption(usage.Options):
    optParameters = [ [ options.BANDWIDTH, 'b', None, 'Bandwidth (Megabits)'] ]

class PublicKeyOption(usage.Options):
    optParameters = [ [ options.CERTIFICATE, 'l', None, 'Certificate path' ] ]

class PrivateKeyOption(usage.Options):
    optParameters = [ [ options.KEY, 'k', None, 'Private key path' ] ]

class CertificateDirectoryOption(usage.Options):
    optParameters = [ [ options.CERTIFICATE_DIR, 'i', None, 'Certificate directory' ] ]

# flags

class TLSFlag(usage.Options):
    optFlags = [ [ options.TLS, 'x', 'Use TLS for listener port' ] ]

class SkipCertificateVerificationFlag(usage.Options):
    optFlags = [ [ options.VERIFY_CERT, 'z', 'Skip certificate verification' ] ]

class FullGraphFlag(usage.Options):
    optFlags = [ [ options.FULL_GRAPH, 'l', 'Render full graph with all links.' ] ]

# command options

class BaseOptions(DefaultsFileOption):

    optFlags = [
        [ options.VERBOSE, 'v', 'Print out more information'],
        [ options.DUMP_PAYLOAD, 'q', 'Dump message payloads'],
    ]

class NetworkBaseOptions(BaseOptions, WSDLDirectoryOption, HostOption, PortOption,
                         ServiceURLOption, TopologyFileOption, NetworkOption,
                         TLSFlag, PublicKeyOption, PrivateKeyOption, CertificateDirectoryOption, SkipCertificateVerificationFlag):

    def postOptions(self):
        # technically we should do this for all superclasses, but this is the only one that has anything to do
        if self[options.SERVICE_URL] and (self[options.TOPOLOGY_FILE] or self[options.NETWORK]):
            raise usage.UsageError('Cannot set service url while having topology file or network.')

class NetworkCommandOptions(NetworkBaseOptions, ProviderNSAOption, RequesterNSAOption, ConnectionIDOption, GlobalIDOption):
    pass

class DiscoveryOptions(NetworkBaseOptions):
    pass

class ReserveOptions(NetworkCommandOptions, SourceSTPOption, DestSTPOption, StartTimeOption, EndTimeOption, BandwidthOption):

    def postOptions(self):
        NetworkCommandOptions.postOptions(self)
        StartTimeOption.postOptions(self)
        EndTimeOption.postOptions(self)

class PathOptions(BaseOptions, SourceSTPOption, DestSTPOption, TopologyFileOption):
    pass

class TopologyOptions(BaseOptions, TopologyFileOption):
    pass

class TopologyGraphOptions(TopologyOptions, FullGraphFlag):
    pass

class ProvisionReleaseTerminateOptions(NetworkCommandOptions):
    pass

class Options(usage.Options):
    subCommands = [
        ['discover',        None,   DiscoveryOptions,       'Discover services at an NSA.'],
        ['reserve',         None,   ReserveOptions,         'Create a reservation.'],
        ['reserveprovision',None,   ReserveOptions,         'Create a reservation and provision the connection.'],
        ['provision',       None,   NetworkCommandOptions,  'Provision a connection.'],
        ['release',         None,   NetworkCommandOptions,  'Release a connection.'],
        ['terminate',       None,   NetworkCommandOptions,  'Terminate a connection.'],
        ['querysummary',    None,   NetworkCommandOptions,  'Query a connection (summary).'],
        ['querydetails',    None,   NetworkCommandOptions,  'Query a connection (recursive).'],
        ['path',            None,   PathOptions,            'Print possible paths from source STP to destination STP.'],
        ['topology',        None,   TopologyOptions,        'Print (known) topology information.'],
        ['topology-graph',  None,   TopologyGraphOptions,   'Print a machine parsable network topology (Graphviz).']
    ]

    def postOptions(self):
        if self.subCommand is None:
            return usage.UsageError('No option specified')

    def opt_version(self):
        from opennsa import __version__
        from twisted import copyright
        print "OpenNSA version %s. " % __version__ + \
              "Running on Twisted version", copyright.version
        raise SystemExit

<|endoftext|>