<commit_before>import copy
import numpy as np

def nelder_mead(fun, x0,
                initial_step=0.1,
                no_improve_thr=10e-6, no_improv_break=10,
                maxiter=0,
                alpha=1., gamma=2., rho=-0.5, sigma=0.5,
                verbose=False):
    '''
    parameters:
        fun (function): function to optimize, must return a scalar score
            and operate over a numpy array of the same dimensions as x0
        x0 (numpy array): initial position
        initial_step (float/np array): determines the stepsize to construct
            the initial simplex. If a float is specified it uses the same
            value for all parameters, if an array is specified it uses
            the specified step for each parameter.

        no_improv_thr,  no_improv_break (float, int): break after
            no_improv_break iterations with an improvement lower than
            no_improv_thr
        maxiter (int): always break after this number of iterations.
            Set it to 0 to loop indefinitely.
        alpha (float): reflection coefficient
        gamma (float): expansion coefficient
        rho (float): contraction coefficient
        sigma (float): shrink coefficient
            For details on these parameters see Wikipedia page

    return: tuple (best parameter array, best score)

    Pure Python/Numpy implementation of the Nelder-Mead algorithm.
    Implementation from https://github.com/fchollet/nelder-mead, edited by
    Adriaan Rol for use in PycQED.
    Reference: https://en.wikipedia.org/wiki/Nelder%E2%80%93Mead_method
    '''
    # init
    x0 = np.array(x0)  # ensures algorithm also accepts lists
    dim = len(x0)
    prev_best = fun(x0)
    no_improv = 0
    res = [[x0, prev_best]]
    if type(initial_step) is float:
        initial_step_matrix = np.eye(dim)*initial_step
    elif (type(initial_step) is list) or (type(initial_step) is np.ndarray):
        if len(initial_step) != dim:
            raise ValueError('initial_step array must be same lenght as x0')
        initial_step_matrix = np.diag(initial_step)
    else:
        raise TypeError('initial_step ({})must be list or np.array'.format(
                        type(initial_step)))

    for i in range(dim):
        x = copy.copy(x0)
        x = x + initial_step_matrix[i]
        score = fun(x)
        res.append([x, score])

    # simplex iter
    iters = 0
    while 1:
        # order
        res.sort(key=lambda x: x[1])
        best = res[0][1]

        # break after maxiter
        if maxiter and iters >= maxiter:
            # Conclude failure break the loop
            if verbose:
                print('max iterations exceeded, optimization failed')
            break
        iters += 1

        if best < prev_best - no_improve_thr:
            no_improv = 0
            prev_best = best
        else:
            no_improv += 1

        if no_improv >= no_improv_break:
            # Conclude success, break the loop
            if verbose:
                print('No improvement registered for {} rounds,'.format(
                      no_improv_break) + 'concluding succesful convergence')
            break

        # centroid
        x0 = [0.] * dim
        for tup in res[:-1]:
            for i, c in enumerate(tup[0]):
                x0[i] += c / (len(res)-1)

        # reflection
        xr = x0 + alpha*(x0 - res[-1][0])
        rscore = fun(xr)
        if res[0][1] <= rscore < res[-2][1]:
            del res[-1]
            res.append([xr, rscore])
            continue

        # expansion
        if rscore < res[0][1]:
            xe = x0 + gamma*(x0 - res[-1][0])
            escore = fun(xe)
            if escore < rscore:
                del res[-1]
                res.append([xe, escore])
                continue
            else:
                del res[-1]
                res.append([xr, rscore])
                continue

        # contraction
        xc = x0 + rho*(x0 - res[-1][0])
        cscore = fun(xc)
        if cscore < res[-1][1]:
            del res[-1]
            res.append([xc, cscore])
            continue

        # reduction
        x1 = res[0][0]
        nres = []
        for tup in res:
            redx = x1 + sigma*(tup[0] - x1)
            score = fun(redx)
            nres.append([redx, score])
        res = nres

    # once the loop is broken evaluate the final value one more time as
    # verification
    fun(res[0][0])
    return res[0]

def SPSA(fun, x0,
         initial_step=0.1,
         no_improve_thr=10e-6, no_improv_break=10,
         maxiter=0,
         gamma=0.101, alpha=0.602, a=0.2, c=0.3, A=300,
         p=0.5, ctrl_min=0.,ctrl_max=np.pi,
         verbose=False):
    '''
    parameters:
        fun (function): function to optimize, must return a scalar score
            and operate over a numpy array of the same dimensions as x0
        x0 (numpy array): initial position

        no_improv_thr,  no_improv_break (float, int): break after
            no_improv_break iterations with an improvement lower than
            no_improv_thr
        maxiter (int): always break after this number of iterations.
            Set it to 0 to loop indefinitely.
        alpha, gamma, a, c, A, (float): parameters for the SPSA gains
            (see refs for definitions)
        p (float): probability to get 1 in Bernoulli +/- 1 distribution
            (see refs for context)
    return: tuple (best parameter array, best score)

    alpha, gamma, a, c, A and p, are parameters for the algorithm.
    Their function is described in the references below,
    and even optimal values have been discussed in the literature.

    Pure Python/Numpy implementation of the SPSA algorithm designed by Spall.
    Implementation from http://www.jhuapl.edu/SPSA/PDF-SPSA/Spall_An_Overview.PDF,
    edited by Ramiro Sagastizabal for use in PycQED.
    Reference: http://www.jhuapl.edu/SPSA/Pages/References-Intro.htm
    '''
    # init
    x0 = np.array(x0)  # ensures algorithm also accepts lists
    dim = len(x0)
    prev_best = fun(x0)
    no_improv = 0
    res = [[x0, prev_best]]

    x = copy.copy(x0)

    # SPSA iter
    iters = 0
    while 1:
        # order
        res.sort(key=lambda x: x[1])
        best = res[0][1]

        # break after maxiter
        if maxiter and iters >= maxiter:
            # Conclude failure break the loop
            if verbose:
                print('max iterations exceeded, optimization failed')
            break
        iters += 1

        if best < prev_best - no_improve_thr:
            no_improv = 0
            prev_best = best
        else:
            no_improv += 1

        if no_improv >= no_improv_break:
            # Conclude success, break the loop
            if verbose:
                print('No improvement registered for {} rounds,'.format(
                      no_improv_break) + 'concluding succesful convergence')
            break
        # step 1
        a_k = a/(iters+A)**alpha
        c_k = c/iters**gamma
        # step 2
        delta = np.where(np.random.rand(dim) > p, 1, -1)
        # step 3
        x_plus = x+c_k*delta
        x_minus = x-c_k*delta
        y_plus = fun(x_plus)
        y_minus = fun(x_minus)
        # res.append([x_plus, y_plus])
        # res.append([x_minus, y_minus])
        # step 4
        gradient = (y_plus-y_minus)/(2.*c_k*delta)
        # step 5
        x = x-a_k*gradient
        x = np.where(x < ctrl_min, ctrl_min, x)
        x = np.where(x > ctrl_max, ctrl_max, x)
        score = fun(x)
        res.append([x, score])

    # once the loop is broken evaluate the final value one more time as
    # verification
    fun(res[0][0])
    return res[0]
<commit_msg>Boundaries for the optimization docstring<commit_after>import copy
import numpy as np

def nelder_mead(fun, x0,
                initial_step=0.1,
                no_improve_thr=10e-6, no_improv_break=10,
                maxiter=0,
                alpha=1., gamma=2., rho=-0.5, sigma=0.5,
                verbose=False):
    '''
    parameters:
        fun (function): function to optimize, must return a scalar score
            and operate over a numpy array of the same dimensions as x0
        x0 (numpy array): initial position
        initial_step (float/np array): determines the stepsize to construct
            the initial simplex. If a float is specified it uses the same
            value for all parameters, if an array is specified it uses
            the specified step for each parameter.

        no_improv_thr,  no_improv_break (float, int): break after
            no_improv_break iterations with an improvement lower than
            no_improv_thr
        maxiter (int): always break after this number of iterations.
            Set it to 0 to loop indefinitely.
        alpha (float): reflection coefficient
        gamma (float): expansion coefficient
        rho (float): contraction coefficient
        sigma (float): shrink coefficient
            For details on these parameters see Wikipedia page

    return: tuple (best parameter array, best score)

    Pure Python/Numpy implementation of the Nelder-Mead algorithm.
    Implementation from https://github.com/fchollet/nelder-mead, edited by
    Adriaan Rol for use in PycQED.
    Reference: https://en.wikipedia.org/wiki/Nelder%E2%80%93Mead_method
    '''
    # init
    x0 = np.array(x0)  # ensures algorithm also accepts lists
    dim = len(x0)
    prev_best = fun(x0)
    no_improv = 0
    res = [[x0, prev_best]]
    if type(initial_step) is float:
        initial_step_matrix = np.eye(dim)*initial_step
    elif (type(initial_step) is list) or (type(initial_step) is np.ndarray):
        if len(initial_step) != dim:
            raise ValueError('initial_step array must be same lenght as x0')
        initial_step_matrix = np.diag(initial_step)
    else:
        raise TypeError('initial_step ({})must be list or np.array'.format(
                        type(initial_step)))

    for i in range(dim):
        x = copy.copy(x0)
        x = x + initial_step_matrix[i]
        score = fun(x)
        res.append([x, score])

    # simplex iter
    iters = 0
    while 1:
        # order
        res.sort(key=lambda x: x[1])
        best = res[0][1]

        # break after maxiter
        if maxiter and iters >= maxiter:
            # Conclude failure break the loop
            if verbose:
                print('max iterations exceeded, optimization failed')
            break
        iters += 1

        if best < prev_best - no_improve_thr:
            no_improv = 0
            prev_best = best
        else:
            no_improv += 1

        if no_improv >= no_improv_break:
            # Conclude success, break the loop
            if verbose:
                print('No improvement registered for {} rounds,'.format(
                      no_improv_break) + 'concluding succesful convergence')
            break

        # centroid
        x0 = [0.] * dim
        for tup in res[:-1]:
            for i, c in enumerate(tup[0]):
                x0[i] += c / (len(res)-1)

        # reflection
        xr = x0 + alpha*(x0 - res[-1][0])
        rscore = fun(xr)
        if res[0][1] <= rscore < res[-2][1]:
            del res[-1]
            res.append([xr, rscore])
            continue

        # expansion
        if rscore < res[0][1]:
            xe = x0 + gamma*(x0 - res[-1][0])
            escore = fun(xe)
            if escore < rscore:
                del res[-1]
                res.append([xe, escore])
                continue
            else:
                del res[-1]
                res.append([xr, rscore])
                continue

        # contraction
        xc = x0 + rho*(x0 - res[-1][0])
        cscore = fun(xc)
        if cscore < res[-1][1]:
            del res[-1]
            res.append([xc, cscore])
            continue

        # reduction
        x1 = res[0][0]
        nres = []
        for tup in res:
            redx = x1 + sigma*(tup[0] - x1)
            score = fun(redx)
            nres.append([redx, score])
        res = nres

    # once the loop is broken evaluate the final value one more time as
    # verification
    fun(res[0][0])
    return res[0]

def SPSA(fun, x0,
         initial_step=0.1,
         no_improve_thr=10e-6, no_improv_break=10,
         maxiter=0,
         gamma=0.101, alpha=0.602, a=0.2, c=0.3, A=300,
         p=0.5, ctrl_min=0.,ctrl_max=np.pi,
         verbose=False):
    '''
    parameters:
        fun (function): function to optimize, must return a scalar score
            and operate over a numpy array of the same dimensions as x0
        x0 (numpy array): initial position

        no_improv_thr,  no_improv_break (float, int): break after
            no_improv_break iterations with an improvement lower than
            no_improv_thr
        maxiter (int): always break after this number of iterations.
            Set it to 0 to loop indefinitely.
        alpha, gamma, a, c, A, (float): parameters for the SPSA gains
            (see refs for definitions)
        p (float): probability to get 1 in Bernoulli +/- 1 distribution
            (see refs for context)
        ctrl_min, ctrl_max (float/array): boundaries for the parameters.
            can be either a global boundary for all dimensions, or a
            numpy array containing the boundary for each dimension.
    return: tuple (best parameter array, best score)

    alpha, gamma, a, c, A and p, are parameters for the algorithm.
    Their function is described in the references below,
    and even optimal values have been discussed in the literature.

    Pure Python/Numpy implementation of the SPSA algorithm designed by Spall.
    Implementation from http://www.jhuapl.edu/SPSA/PDF-SPSA/Spall_An_Overview.PDF,
    edited by Ramiro Sagastizabal for use in PycQED.
    Reference: http://www.jhuapl.edu/SPSA/Pages/References-Intro.htm
    '''
    # init
    x0 = np.array(x0)  # ensures algorithm also accepts lists
    dim = len(x0)
    prev_best = fun(x0)
    no_improv = 0
    res = [[x0, prev_best]]

    x = copy.copy(x0)

    # SPSA iter
    iters = 0
    while 1:
        # order
        res.sort(key=lambda x: x[1])
        best = res[0][1]

        # break after maxiter
        if maxiter and iters >= maxiter:
            # Conclude failure break the loop
            if verbose:
                print('max iterations exceeded, optimization failed')
            break
        iters += 1

        if best < prev_best - no_improve_thr:
            no_improv = 0
            prev_best = best
        else:
            no_improv += 1

        if no_improv >= no_improv_break:
            # Conclude success, break the loop
            if verbose:
                print('No improvement registered for {} rounds,'.format(
                      no_improv_break) + 'concluding succesful convergence')
            break
        # step 1
        a_k = a/(iters+A)**alpha
        c_k = c/iters**gamma
        # step 2
        delta = np.where(np.random.rand(dim) > p, 1, -1)
        # step 3
        x_plus = x+c_k*delta
        x_minus = x-c_k*delta
        y_plus = fun(x_plus)
        y_minus = fun(x_minus)
        # res.append([x_plus, y_plus])
        # res.append([x_minus, y_minus])
        # step 4
        gradient = (y_plus-y_minus)/(2.*c_k*delta)
        # step 5
        x = x-a_k*gradient
        x = np.where(x < ctrl_min, ctrl_min, x)
        x = np.where(x > ctrl_max, ctrl_max, x)
        score = fun(x)
        res.append([x, score])

    # once the loop is broken evaluate the final value one more time as
    # verification
    fun(res[0][0])
    return res[0]
<|endoftext|>


<commit_before># No shebang line, this module is meant to be imported
#
# Copyright 2013 Oliver Palmer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Resource
--------

Base resources which can be used to build top leve
documents, pages, or other types of data for the web.
"""

from json import loads, dumps

try:
    from httplib import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED, INTERNAL_SERVER_ERROR)
except ImportError:  # pragma: no cover
    from http.client import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED, INTERNAL_SERVER_ERROR)

from twisted.internet.defer import Deferred, inlineCallbacks
from twisted.web.server import NOT_DONE_YET
from twisted.web.resource import Resource as _Resource
from voluptuous import Invalid, Schema

from pyfarm.core.enums import STRING_TYPES
from pyfarm.agent.http.core import template
from pyfarm.agent.logger import getLogger

logger = getLogger("agent.http.resource")

class Resource(_Resource):
    """
    Basic subclass of :class:`._Resource` for passing requests to
    specific methods.  Unlike :class:`._Resource` however this will
    will also handle:

        * Templates
        * Content type discovery and validation
        * Handling of deferred responses
        * Validation of POST/PUT data against a schema
    """
    TEMPLATE = NotImplemented
    SUPPORTED_CONTENT_TYPES = set(["text/html", "application/json"])

    # Used by API endpoints for data validation
    # format of this attri
    SCHEMAS = {}

    def __init__(self):
        _Resource.__init__(self)
        assert isinstance(self.SUPPORTED_CONTENT_TYPES, set)

    @property
    def template(self):
        """
        Loads the template provided but the partial path in ``TEMPLATE`` on
        the class.
        """
        if self.TEMPLATE is NotImplemented:
            raise NotImplementedError("You must set `TEMPLATE` first")
        return template.load(self.TEMPLATE)

    def response_types(self, request, default=None):
        """
        Returns an instance of :class:`frozenset` which contains the type
        or types of responses we can send.  We look at the ``Accept`` and
        ``Content-Type`` headers to determine this information.  If values
        are present in the ``Accept`` header these will be returned instead
        of values in ``Content-Type``.  If neither of these headers are present
        we'll return what was provided to ``default`` instead.

        .. note::

            This method's purpose is only to return what kinds of responses
            are acceptable based on the request.  Ultimately, it's up to the
            calling method to make the final determination about what kind of
            response to send.
        """
        assert default is None or isinstance(default, (list, tuple, set))
        accepts = request.requestHeaders.getRawHeaders("Accept")
        content_types = request.requestHeaders.getRawHeaders("Content-Type")

        if accepts is not None:
            return frozenset(accepts)
        elif content_types is not None:
            return frozenset(content_types)
        elif default is None:
            return frozenset()
        else:
            return frozenset(default)

    def putChild(self, path, child):
        """
        Overrides the builtin putChild() so we can return the results for
        each call and use them externally.
        """
        assert isinstance(path, STRING_TYPES)
        assert isinstance(child, Resource)
        _Resource.putChild(self, path, child)
        return child

    def error(self, request, code, message):
        """
        Writes the proper out an error response message depending on the
        content type in the request
        """
        content_types = self.response_types(request, default=["text/html"])
        logger.error(message)

        if "text/html" in content_types:
            request.setResponseCode(code)
            html_error = template.load("error.html")
            result = html_error.render(
                code=code, code_msg=responses[code], message=message)
            request.write(result)

        elif "application/json" in content_types:
            request.setResponseCode(code)
            error = dumps({"error": message})
            request.write(error.encode("ascii", "replace"))

        else:
            request.setResponseCode(UNSUPPORTED_MEDIA_TYPE)
            error = dumps(
                {"error": "Can only handle text/html or application/json here"})
            request.write(error.encode("ascii", "replace"))

        request.finish()

    def render_tuple(self, request, response):
        """
        Takes a response tuple of ``(body, code, headers)`` or
        ``(body, code)`` and renders the resulting data onto
        the request.
        """
        if len(response) == 3:
            body, code, headers = response

            if isinstance(headers, dict):
                # Set the response headers
                for header, value in headers.items():
                    # Response header values in Twisted are supposed
                    # to be strings, unlike request headers, according
                    # to the documentation.  Internally it seems to set
                    # it as a list however that's not something the
                    # setHeader() api exposes.
                    if not isinstance(value, STRING_TYPES):
                        self.error(
                            request, INTERNAL_SERVER_ERROR,
                            "Expected string for header values"
                        )
                        return

                    request.setHeader(header, value)

            request.setResponseCode(code)
            request.write(body)
            request.finish()

        elif len(response) == 2:
            body, code = response
            request.setResponseCode(code)
            request.write(body)
            request.finish()

        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Expected two or three length tuple for response"
            )

    @inlineCallbacks
    def render_deferred(self, request, deferred):
        """
        An inline callback used to unpack a deferred
        response object.
        """
        response = yield deferred
        if isinstance(response, tuple):
            self.render_tuple(request, response)
        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Expected a tuple to be returned from the deferred response.")

    def render(self, request):
        # Check to ensure that the content type being requested
        content_types = self.response_types(
            request, default=["text/html", "application/json"])

        if not self.SUPPORTED_CONTENT_TYPES & content_types:
            self.error(
                request, UNSUPPORTED_MEDIA_TYPE,
                "%s is not a support content type for this url" % content_types)
            return NOT_DONE_YET

        kwargs = {"request": request}

        try:
            handler_method = getattr(self, request.method.lower())
        except AttributeError:
            self.error(
                request, METHOD_NOT_ALLOWED,
                "Method %s is not supported" % request.method)
            return NOT_DONE_YET

        # Attempt to load the data for the incoming request if appropriate
        if ("application/json" in content_types
                and request.method in ("POST", "PUT")):
            data = request.content.read().strip()
            if data:
                try:
                    data = loads(data)
                except ValueError as e:
                    self.error(
                        request, BAD_REQUEST,
                        "Failed to decode json data: %r" % e)
                    return NOT_DONE_YET

                # We have data, check to see if we have a schema
                # and if we do does it validate.
                schema = self.SCHEMAS.get(request)
                if isinstance(schema, Schema):
                    try:
                        schema(data)
                    except Invalid as e:
                        self.error(
                            request, BAD_REQUEST,
                            "Failed to validate the request data "
                            "against the schema: %s" % e)
                        return NOT_DONE_YET

            kwargs.update(data=data)

        try:
            response = handler_method(**kwargs)
        except Exception as error:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Unhandled error while rendering response: %s" % error
            )
            return NOT_DONE_YET

        # The handler_method is going to handle everything
        if response == NOT_DONE_YET:
            return NOT_DONE_YET

        # Flask style response
        elif isinstance(response, tuple):
            self.render_tuple(request, response)
            return NOT_DONE_YET

        # handler_method() is returns a Deferred which means
        # we have to handle writing the response ourselves
        elif isinstance(response, Deferred):
            self.render_deferred(request, response)
            return NOT_DONE_YET

        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Unhandled type %s in response" % response
            )
            return NOT_DONE_YET
<commit_msg>fix to support multiple headers and check input type<commit_after># No shebang line, this module is meant to be imported
#
# Copyright 2013 Oliver Palmer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Resource
--------

Base resources which can be used to build top leve
documents, pages, or other types of data for the web.
"""

from json import loads, dumps

try:
    from httplib import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED, INTERNAL_SERVER_ERROR)
except ImportError:  # pragma: no cover
    from http.client import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED, INTERNAL_SERVER_ERROR)

from twisted.internet.defer import Deferred, inlineCallbacks
from twisted.web.server import NOT_DONE_YET
from twisted.web.resource import Resource as _Resource
from voluptuous import Invalid, Schema

from pyfarm.core.enums import STRING_TYPES
from pyfarm.agent.http.core import template
from pyfarm.agent.logger import getLogger

logger = getLogger("agent.http.resource")

class Resource(_Resource):
    """
    Basic subclass of :class:`._Resource` for passing requests to
    specific methods.  Unlike :class:`._Resource` however this will
    will also handle:

        * Templates
        * Content type discovery and validation
        * Handling of deferred responses
        * Validation of POST/PUT data against a schema
    """
    TEMPLATE = NotImplemented
    SUPPORTED_CONTENT_TYPES = set(["text/html", "application/json"])

    # Used by API endpoints for data validation
    # format of this attri
    SCHEMAS = {}

    def __init__(self):
        _Resource.__init__(self)
        assert isinstance(self.SUPPORTED_CONTENT_TYPES, set)

    @property
    def template(self):
        """
        Loads the template provided but the partial path in ``TEMPLATE`` on
        the class.
        """
        if self.TEMPLATE is NotImplemented:
            raise NotImplementedError("You must set `TEMPLATE` first")
        return template.load(self.TEMPLATE)

    def response_types(self, request, default=None):
        """
        Returns an instance of :class:`frozenset` which contains the type
        or types of responses we can send.  We look at the ``Accept`` and
        ``Content-Type`` headers to determine this information.  If values
        are present in the ``Accept`` header these will be returned instead
        of values in ``Content-Type``.  If neither of these headers are present
        we'll return what was provided to ``default`` instead.

        .. note::

            This method's purpose is only to return what kinds of responses
            are acceptable based on the request.  Ultimately, it's up to the
            calling method to make the final determination about what kind of
            response to send.
        """
        assert default is None or isinstance(default, (list, tuple, set))
        accepts = request.requestHeaders.getRawHeaders("Accept")
        content_types = request.requestHeaders.getRawHeaders("Content-Type")

        if accepts is not None:
            return frozenset(accepts)
        elif content_types is not None:
            return frozenset(content_types)
        elif default is None:
            return frozenset()
        else:
            return frozenset(default)

    def putChild(self, path, child):
        """
        Overrides the builtin putChild() so we can return the results for
        each call and use them externally.
        """
        assert isinstance(path, STRING_TYPES)
        assert isinstance(child, Resource)
        _Resource.putChild(self, path, child)
        return child

    def error(self, request, code, message):
        """
        Writes the proper out an error response message depending on the
        content type in the request
        """
        content_types = self.response_types(request, default=["text/html"])
        logger.error(message)

        if "text/html" in content_types:
            request.setResponseCode(code)
            html_error = template.load("error.html")
            result = html_error.render(
                code=code, code_msg=responses[code], message=message)
            request.write(result)

        elif "application/json" in content_types:
            request.setResponseCode(code)
            error = dumps({"error": message})
            request.write(error.encode("ascii", "replace"))

        else:
            request.setResponseCode(UNSUPPORTED_MEDIA_TYPE)
            error = dumps(
                {"error": "Can only handle text/html or application/json here"})
            request.write(error.encode("ascii", "replace"))

        request.finish()

    def render_tuple(self, request, response):
        """
        Takes a response tuple of ``(body, code, headers)`` or
        ``(body, code)`` and renders the resulting data onto
        the request.
        """
        assert isinstance(response, (list, tuple))
        if len(response) == 3:
            body, code, headers = response

            if isinstance(headers, dict):
                for header, value in headers.items():
                    if isinstance(value, STRING_TYPES):
                        value = [value]
                    request.responseHeaders.setRawHeaders(header, value)

            request.setResponseCode(code)
            request.write(body)
            request.finish()

        elif len(response) == 2:
            body, code = response
            request.setResponseCode(code)
            request.write(body)
            request.finish()

        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Expected two or three length tuple for response"
            )

    @inlineCallbacks
    def render_deferred(self, request, deferred):
        """
        An inline callback used to unpack a deferred
        response object.
        """
        response = yield deferred
        if isinstance(response, tuple):
            self.render_tuple(request, response)
        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Expected a tuple to be returned from the deferred response.")

    def render(self, request):
        # Check to ensure that the content type being requested
        content_types = self.response_types(
            request, default=["text/html", "application/json"])

        if not self.SUPPORTED_CONTENT_TYPES & content_types:
            self.error(
                request, UNSUPPORTED_MEDIA_TYPE,
                "%s is not a support content type for this url" % content_types)
            return NOT_DONE_YET

        kwargs = {"request": request}

        try:
            handler_method = getattr(self, request.method.lower())
        except AttributeError:
            self.error(
                request, METHOD_NOT_ALLOWED,
                "Method %s is not supported" % request.method)
            return NOT_DONE_YET

        # Attempt to load the data for the incoming request if appropriate
        if ("application/json" in content_types
                and request.method in ("POST", "PUT")):
            data = request.content.read().strip()
            if data:
                try:
                    data = loads(data)
                except ValueError as e:
                    self.error(
                        request, BAD_REQUEST,
                        "Failed to decode json data: %r" % e)
                    return NOT_DONE_YET

                # We have data, check to see if we have a schema
                # and if we do does it validate.
                schema = self.SCHEMAS.get(request)
                if isinstance(schema, Schema):
                    try:
                        schema(data)
                    except Invalid as e:
                        self.error(
                            request, BAD_REQUEST,
                            "Failed to validate the request data "
                            "against the schema: %s" % e)
                        return NOT_DONE_YET

            kwargs.update(data=data)

        try:
            response = handler_method(**kwargs)
        except Exception as error:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Unhandled error while rendering response: %s" % error
            )
            return NOT_DONE_YET

        # The handler_method is going to handle everything
        if response == NOT_DONE_YET:
            return NOT_DONE_YET

        # Flask style response
        elif isinstance(response, tuple):
            self.render_tuple(request, response)
            return NOT_DONE_YET

        # handler_method() is returns a Deferred which means
        # we have to handle writing the response ourselves
        elif isinstance(response, Deferred):
            self.render_deferred(request, response)
            return NOT_DONE_YET

        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Unhandled type %s in response" % response
            )
            return NOT_DONE_YET
<|endoftext|>


<commit_before># No shebang line, this module is meant to be imported
#
# Copyright 2013 Oliver Palmer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Resource
--------

Base resources which can be used to build top leve
documents, pages, or other types of data for the web.
"""

from json import loads

try:
    from httplib import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED, INTERNAL_SERVER_ERROR)
except ImportError:  # pragma: no cover
    from http.client import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED, INTERNAL_SERVER_ERROR)

try:
    from itertools import ifilter as filter_
except ImportError:  # pragma: no cover
    filter_ = filter

try:
    from itertools import imap as map_
except ImportError:  # pragma: no cover
    map_ = map

from twisted.internet.defer import Deferred, inlineCallbacks
from twisted.web.server import NOT_DONE_YET
from twisted.web.resource import Resource as _Resource
from voluptuous import Invalid, Schema

from pyfarm.core.enums import STRING_TYPES
from pyfarm.agent.http.core import template
from pyfarm.agent.logger import getLogger

logger = getLogger("agent.http.resource")

class Resource(_Resource):
    """
    Basic subclass of :class:`._Resource` for passing requests to
    specific methods.  Unlike :class:`._Resource` however this will
    will also handle:

        * Templates
        * Content type discovery and validation
        * Handling of deferred responses
        * Validation of POST/PUT data against a schema
    """
    TEMPLATE = NotImplemented
    SUPPORTED_CONTENT_TYPES = set(["text/html", "application/json"])

    # Used by API endpoints for data validation
    # format of this attri
    SCHEMAS = {}

    def __init__(self):
        _Resource.__init__(self)
        assert isinstance(self.SUPPORTED_CONTENT_TYPES, set)

    @property
    def template(self):
        """
        Loads the template provided but the partial path in ``TEMPLATE`` on
        the class.
        """
        if self.TEMPLATE is NotImplemented:
            raise NotImplementedError("You must set `TEMPLATE` first")
        return template.load(self.TEMPLATE)

    def response_types(self, request, default=None):
        """
        Returns an instance of :class:`frozenset` which contains the type
        or types of responses we can send.  We look at the ``Accept`` and
        ``Content-Type`` headers to determine this information.  If values
        are present in the ``Accept`` header these will be returned instead
        of values in ``Content-Type``.  If neither of these headers are present
        we'll return what was provided to ``default`` instead.

        .. note::

            This method's purpose is only to return what kinds of responses
            are acceptable based on the request.  Ultimately, it's up to the
            calling method to make the final determination about what kind of
            response to send.
        """
        assert default is None or isinstance(default, (list, tuple, set))
        accepts = request.requestHeaders.getRawHeaders("Accept")
        content_types = request.requestHeaders.getRawHeaders("Content-Type")

        if accepts is not None:
            return frozenset(accepts)
        elif content_types is not None:
            return frozenset(content_types)
        elif default is None:
            return frozenset()
        else:
            return frozenset(default)

    def putChild(self, path, child):
        """
        Overrides the builtin putChild() so we can return the results for
        each call and use them externally.
        """
        assert isinstance(path, STRING_TYPES)
        assert isinstance(child, Resource)
        _Resource.putChild(self, path, child)
        return child

    def error(self, request, code, message):
        """
        Writes the proper out an error response message depending on the
        content type in the request
        """
        content_types = self.response_types(request, default=["text/html"])
        logger.error(message)

        if "text/html" in content_types:
            request.setResponseCode(code)
            html_error = template.load("error.html")
            result = html_error.render(
                code=code, code_msg=responses[code], message=message)
            request.write(result)

        elif "application/json" in content_types:
            request.setResponseCode(code)
            request.write({"error": message})

        else:
            request.setResponseCode(UNSUPPORTED_MEDIA_TYPE)
            request.write(
                {"error": "Can only handle text/html or application/json here"})

        request.finish()

    def render_tuple(self, request, response):
        """
        Takes a response tuple of ``(body, code, headers)`` or
        ``(body, code)`` and renders the resulting data onto
        the request.
        """
        if len(response) == 3:
            body, code, headers = response

            if isinstance(headers, dict):
                # Set the response headers
                for header, value in headers.items():
                    # Response header values in Twisted are supposed
                    # to be strings, unlike request headers, according
                    # to the documentation.  Internally it seems to set
                    # it as a list however that's not something the
                    # setHeader() api exposes.
                    if not isinstance(value, STRING_TYPES):
                        self.error(
                            request, INTERNAL_SERVER_ERROR,
                            "Expected string for header values"
                        )
                        return

                    request.setHeader(header, value)

            request.setResponseCode(code)
            request.write(body)
            request.finish()

        elif len(response) == 2:
            body, code = response
            request.setResponseCode(code)
            request.write(body)
            request.finish()

        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Expected two or three length tuple for response"
            )

    @inlineCallbacks
    def render_deferred(self, request, deferred):
        """
        An inline callback used to unpack a deferred
        response object.
        """
        response = yield deferred
        if isinstance(response, tuple):
            self.render_tuple(request, response)
        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Expected a tuple to be returned from the deferred response.")

    def render(self, request):
        # Check to ensure that the content type being requested
        content_types = self.response_types(
            request, default=["text/html", "application/json"])

        if not self.SUPPORTED_CONTENT_TYPES & content_types:
            self.error(
                request, UNSUPPORTED_MEDIA_TYPE,
                "%s is not a support content type for this url" % content_types)
            return NOT_DONE_YET

        kwargs = {"request": request}

        try:
            handler_method = getattr(self, request.method.lower())
        except AttributeError:
            self.error(
                request, METHOD_NOT_ALLOWED,
                "Method %s is not supported" % request.method)
            return NOT_DONE_YET

        # Attempt to load the data for the incoming request if appropriate
        if ("application/json" in content_types
                and request.method in ("POST", "PUT")):
            data = request.content.read().strip()
            if data:
                try:
                    data = loads(data)
                except ValueError as e:
                    self.error(
                        request, BAD_REQUEST,
                        "Failed to decode json data: %r" % e)
                    return NOT_DONE_YET

                # We have data, check to see if we have a schema
                # and if we do does it validate.
                schema = self.SCHEMAS.get(request)
                if isinstance(schema, Schema):
                    try:
                        schema(data)
                    except Invalid as e:
                        self.error(
                            request, BAD_REQUEST,
                            "Failed to validate the request data "
                            "against the schema: %s" % e)
                        return NOT_DONE_YET

            kwargs.update(data=data)

        try:
            response = handler_method(**kwargs)
        except Exception as error:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Unhandled error while rendering response: %s" % error
            )
            return NOT_DONE_YET

        # The handler_method is going to handle everything
        if response == NOT_DONE_YET:
            return NOT_DONE_YET

        # Flask style response
        elif isinstance(response, tuple):
            self.render_tuple(request, response)
            return NOT_DONE_YET

        # handler_method() is returns a Deferred which means
        # we have to handle writing the response ourselves
        elif isinstance(response, Deferred):
            self.render_deferred(request, response)
            return NOT_DONE_YET

        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Unhandled type %s in response" % response
            )
            return NOT_DONE_YET
<commit_msg>remove unused imports<commit_after># No shebang line, this module is meant to be imported
#
# Copyright 2013 Oliver Palmer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Resource
--------

Base resources which can be used to build top leve
documents, pages, or other types of data for the web.
"""

from json import loads

try:
    from httplib import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED, INTERNAL_SERVER_ERROR)
except ImportError:  # pragma: no cover
    from http.client import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED, INTERNAL_SERVER_ERROR)

from twisted.internet.defer import Deferred, inlineCallbacks
from twisted.web.server import NOT_DONE_YET
from twisted.web.resource import Resource as _Resource
from voluptuous import Invalid, Schema

from pyfarm.core.enums import STRING_TYPES
from pyfarm.agent.http.core import template
from pyfarm.agent.logger import getLogger

logger = getLogger("agent.http.resource")

class Resource(_Resource):
    """
    Basic subclass of :class:`._Resource` for passing requests to
    specific methods.  Unlike :class:`._Resource` however this will
    will also handle:

        * Templates
        * Content type discovery and validation
        * Handling of deferred responses
        * Validation of POST/PUT data against a schema
    """
    TEMPLATE = NotImplemented
    SUPPORTED_CONTENT_TYPES = set(["text/html", "application/json"])

    # Used by API endpoints for data validation
    # format of this attri
    SCHEMAS = {}

    def __init__(self):
        _Resource.__init__(self)
        assert isinstance(self.SUPPORTED_CONTENT_TYPES, set)

    @property
    def template(self):
        """
        Loads the template provided but the partial path in ``TEMPLATE`` on
        the class.
        """
        if self.TEMPLATE is NotImplemented:
            raise NotImplementedError("You must set `TEMPLATE` first")
        return template.load(self.TEMPLATE)

    def response_types(self, request, default=None):
        """
        Returns an instance of :class:`frozenset` which contains the type
        or types of responses we can send.  We look at the ``Accept`` and
        ``Content-Type`` headers to determine this information.  If values
        are present in the ``Accept`` header these will be returned instead
        of values in ``Content-Type``.  If neither of these headers are present
        we'll return what was provided to ``default`` instead.

        .. note::

            This method's purpose is only to return what kinds of responses
            are acceptable based on the request.  Ultimately, it's up to the
            calling method to make the final determination about what kind of
            response to send.
        """
        assert default is None or isinstance(default, (list, tuple, set))
        accepts = request.requestHeaders.getRawHeaders("Accept")
        content_types = request.requestHeaders.getRawHeaders("Content-Type")

        if accepts is not None:
            return frozenset(accepts)
        elif content_types is not None:
            return frozenset(content_types)
        elif default is None:
            return frozenset()
        else:
            return frozenset(default)

    def putChild(self, path, child):
        """
        Overrides the builtin putChild() so we can return the results for
        each call and use them externally.
        """
        assert isinstance(path, STRING_TYPES)
        assert isinstance(child, Resource)
        _Resource.putChild(self, path, child)
        return child

    def error(self, request, code, message):
        """
        Writes the proper out an error response message depending on the
        content type in the request
        """
        content_types = self.response_types(request, default=["text/html"])
        logger.error(message)

        if "text/html" in content_types:
            request.setResponseCode(code)
            html_error = template.load("error.html")
            result = html_error.render(
                code=code, code_msg=responses[code], message=message)
            request.write(result)

        elif "application/json" in content_types:
            request.setResponseCode(code)
            request.write({"error": message})

        else:
            request.setResponseCode(UNSUPPORTED_MEDIA_TYPE)
            request.write(
                {"error": "Can only handle text/html or application/json here"})

        request.finish()

    def render_tuple(self, request, response):
        """
        Takes a response tuple of ``(body, code, headers)`` or
        ``(body, code)`` and renders the resulting data onto
        the request.
        """
        if len(response) == 3:
            body, code, headers = response

            if isinstance(headers, dict):
                # Set the response headers
                for header, value in headers.items():
                    # Response header values in Twisted are supposed
                    # to be strings, unlike request headers, according
                    # to the documentation.  Internally it seems to set
                    # it as a list however that's not something the
                    # setHeader() api exposes.
                    if not isinstance(value, STRING_TYPES):
                        self.error(
                            request, INTERNAL_SERVER_ERROR,
                            "Expected string for header values"
                        )
                        return

                    request.setHeader(header, value)

            request.setResponseCode(code)
            request.write(body)
            request.finish()

        elif len(response) == 2:
            body, code = response
            request.setResponseCode(code)
            request.write(body)
            request.finish()

        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Expected two or three length tuple for response"
            )

    @inlineCallbacks
    def render_deferred(self, request, deferred):
        """
        An inline callback used to unpack a deferred
        response object.
        """
        response = yield deferred
        if isinstance(response, tuple):
            self.render_tuple(request, response)
        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Expected a tuple to be returned from the deferred response.")

    def render(self, request):
        # Check to ensure that the content type being requested
        content_types = self.response_types(
            request, default=["text/html", "application/json"])

        if not self.SUPPORTED_CONTENT_TYPES & content_types:
            self.error(
                request, UNSUPPORTED_MEDIA_TYPE,
                "%s is not a support content type for this url" % content_types)
            return NOT_DONE_YET

        kwargs = {"request": request}

        try:
            handler_method = getattr(self, request.method.lower())
        except AttributeError:
            self.error(
                request, METHOD_NOT_ALLOWED,
                "Method %s is not supported" % request.method)
            return NOT_DONE_YET

        # Attempt to load the data for the incoming request if appropriate
        if ("application/json" in content_types
                and request.method in ("POST", "PUT")):
            data = request.content.read().strip()
            if data:
                try:
                    data = loads(data)
                except ValueError as e:
                    self.error(
                        request, BAD_REQUEST,
                        "Failed to decode json data: %r" % e)
                    return NOT_DONE_YET

                # We have data, check to see if we have a schema
                # and if we do does it validate.
                schema = self.SCHEMAS.get(request)
                if isinstance(schema, Schema):
                    try:
                        schema(data)
                    except Invalid as e:
                        self.error(
                            request, BAD_REQUEST,
                            "Failed to validate the request data "
                            "against the schema: %s" % e)
                        return NOT_DONE_YET

            kwargs.update(data=data)

        try:
            response = handler_method(**kwargs)
        except Exception as error:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Unhandled error while rendering response: %s" % error
            )
            return NOT_DONE_YET

        # The handler_method is going to handle everything
        if response == NOT_DONE_YET:
            return NOT_DONE_YET

        # Flask style response
        elif isinstance(response, tuple):
            self.render_tuple(request, response)
            return NOT_DONE_YET

        # handler_method() is returns a Deferred which means
        # we have to handle writing the response ourselves
        elif isinstance(response, Deferred):
            self.render_deferred(request, response)
            return NOT_DONE_YET

        else:
            self.error(
                request, INTERNAL_SERVER_ERROR,
                "Unhandled type %s in response" % response
            )
            return NOT_DONE_YET
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

import astropy.units as u
from astropy.tests.helper import assert_quantity_allclose
from astropy.coordinates import SkyCoord, EarthLocation
from astropy.time import Time
from astropy.constants import c as speed_of_light

from sunpy.coordinates.ephemeris import *

def test_get_body_heliographic_stonyhurst():
    # Validate against published values from the Astronomical Almanac (2013)
    e1 = get_body_heliographic_stonyhurst('earth', '2013-Jan-01')
    assert_quantity_allclose(e1.lon, 0*u.deg, atol=1e-12*u.deg)
    assert_quantity_allclose(e1.lat, -3.03*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(e1.radius, 0.9832947*u.AU, atol=5e-7*u.AU)

    e2 = get_body_heliographic_stonyhurst('earth', '2013-Sep-01')
    # https://github.com/sunpy/sunpy/issues/2727
    assert_quantity_allclose((e2.lon+1*u.deg)%(360*u.deg), 1*u.deg, atol=1e-12*u.deg)
    assert_quantity_allclose(e2.lat, 7.19*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(e2.radius, 1.0092561*u.AU, atol=5e-7*u.AU)

def test_get_body_heliographic_stonyhurst_light_travel_time():
    # Tests whether the apparent position of the Sun accoutns for light travel time
    t = Time('2012-06-05 22:34:48.350')  # An arbitrary test time

    # Use the implemented correction for light travel time
    implementation = get_body_heliographic_stonyhurst('sun', t, observer=get_earth(t))
    implementation_icrs = SkyCoord(implementation).icrs.cartesian

    # Use a manual correction for light travel time
    light_travel_time = get_earth(t).radius / speed_of_light
    manual = get_body_heliographic_stonyhurst('sun', t - light_travel_time)
    manual_icrs = SkyCoord(manual).icrs.cartesian

    difference = (implementation_icrs - manual_icrs).norm()
    assert_quantity_allclose(difference, 0*u.m, atol=1*u.m)

def test_get_earth():
    # Validate against published values from the Astronomical Almanac (2013)
    e1 = get_earth('2013-Jan-01')
    assert e1.lon == 0*u.deg
    assert_quantity_allclose(e1.lat, -3.03*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(e1.radius, 0.9832947*u.AU, atol=5e-7*u.AU)

    e2 = get_earth('2013-Sep-01')
    assert e2.lon == 0*u.deg
    assert_quantity_allclose(e2.lat, 7.19*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(e2.radius, 1.0092561*u.AU, atol=5e-7*u.AU)

def test_get_sun_B0():
    # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191)
    assert_quantity_allclose(get_sun_B0('1992-Oct-13'), 5.99*u.deg, atol=5e-3*u.deg)

def test_get_sun_B0_array_time():
    # Validate against published values from the Astronomical Almanac (2013)
    sun_B0 = get_sun_B0(Time(['2013-04-01', '2013-12-01']))
    assert_quantity_allclose(sun_B0[0], -6.54*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(sun_B0[1], 0.88*u.deg, atol=5e-3*u.deg)

def test_get_sun_L0():
    # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191)
    assert_quantity_allclose(get_sun_L0('1992-Oct-13'), 238.6317*u.deg, atol=5e-5*u.deg)

def test_get_sun_L0_array_time():
    # Validate against published values from the Astronomical Almanac (2013)
    sun_L0 = get_sun_L0(Time(['2013-04-01', '2013-12-01']))
    assert_quantity_allclose(sun_L0[0], 221.44*u.deg, atol=3e-2*u.deg)
    assert_quantity_allclose(sun_L0[1], 237.83*u.deg, atol=3e-2*u.deg)

def test_get_sun_P():
    # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191)
    assert_quantity_allclose(get_sun_P('1992-Oct-13'), 26.27*u.deg, atol=5e-3*u.deg)

def test_get_sun_P_array_time():
    # Validate against published values from the Astronomical Almanac (2013)
    sun_P = get_sun_P(Time(['2013-04-01', '2013-12-01']))
    assert_quantity_allclose(sun_P[0], -26.15*u.deg, atol=1e-2*u.deg)
    assert_quantity_allclose(sun_P[1], 16.05*u.deg, atol=1e-2*u.deg)

def test_get_sunearth_distance():
    # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191)
    assert_quantity_allclose(get_sunearth_distance('1992-Oct-13'), 0.997608*u.AU, atol=5e-7*u.AU)

def test_get_sunearth_distance_array_time():
    # Validate against published values from the Astronomical Almanac (2013)
    sunearth_distance = get_sunearth_distance(Time(['2013-04-01', '2013-12-01']))
    assert_quantity_allclose(sunearth_distance[0], 0.9992311*u.AU, atol=5e-7*u.AU)
    assert_quantity_allclose(sunearth_distance[1], 0.9861362*u.AU, atol=5e-7*u.AU)

def test_get_sun_orientation():
    # Not currently aware of a published value to check against, so just self-check for now

    # Check the Northern Hemisphere
    angle = get_sun_orientation(EarthLocation(lat=40*u.deg, lon=-75*u.deg), '2017-07-18 12:00')
    assert_quantity_allclose(angle, -59.4*u.deg, atol=0.1*u.deg)

    # Check the Southern Hemisphere
    angle = get_sun_orientation(EarthLocation(lat=-40*u.deg, lon=-75*u.deg), '2017-02-18 13:00')
    assert_quantity_allclose(angle, -110.8*u.deg, atol=0.1*u.deg)
<commit_msg>Added test for querying JPL HORIZONS<commit_after># -*- coding: utf-8 -*-

import astropy.units as u
from astropy.tests.helper import assert_quantity_allclose
from astropy.coordinates import SkyCoord, EarthLocation
from astropy.time import Time
from astropy.constants import c as speed_of_light
import pytest

from sunpy.coordinates.ephemeris import *

def test_get_body_heliographic_stonyhurst():
    # Validate against published values from the Astronomical Almanac (2013)
    e1 = get_body_heliographic_stonyhurst('earth', '2013-Jan-01')
    assert_quantity_allclose(e1.lon, 0*u.deg, atol=1e-12*u.deg)
    assert_quantity_allclose(e1.lat, -3.03*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(e1.radius, 0.9832947*u.AU, atol=5e-7*u.AU)

    e2 = get_body_heliographic_stonyhurst('earth', '2013-Sep-01')
    # https://github.com/sunpy/sunpy/issues/2727
    assert_quantity_allclose((e2.lon+1*u.deg)%(360*u.deg), 1*u.deg, atol=1e-12*u.deg)
    assert_quantity_allclose(e2.lat, 7.19*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(e2.radius, 1.0092561*u.AU, atol=5e-7*u.AU)

def test_get_body_heliographic_stonyhurst_light_travel_time():
    # Tests whether the apparent position of the Sun accoutns for light travel time
    t = Time('2012-06-05 22:34:48.350')  # An arbitrary test time

    # Use the implemented correction for light travel time
    implementation = get_body_heliographic_stonyhurst('sun', t, observer=get_earth(t))
    implementation_icrs = SkyCoord(implementation).icrs.cartesian

    # Use a manual correction for light travel time
    light_travel_time = get_earth(t).radius / speed_of_light
    manual = get_body_heliographic_stonyhurst('sun', t - light_travel_time)
    manual_icrs = SkyCoord(manual).icrs.cartesian

    difference = (implementation_icrs - manual_icrs).norm()
    assert_quantity_allclose(difference, 0*u.m, atol=1*u.m)

def test_get_earth():
    # Validate against published values from the Astronomical Almanac (2013)
    e1 = get_earth('2013-Jan-01')
    assert e1.lon == 0*u.deg
    assert_quantity_allclose(e1.lat, -3.03*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(e1.radius, 0.9832947*u.AU, atol=5e-7*u.AU)

    e2 = get_earth('2013-Sep-01')
    assert e2.lon == 0*u.deg
    assert_quantity_allclose(e2.lat, 7.19*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(e2.radius, 1.0092561*u.AU, atol=5e-7*u.AU)

def test_get_sun_B0():
    # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191)
    assert_quantity_allclose(get_sun_B0('1992-Oct-13'), 5.99*u.deg, atol=5e-3*u.deg)

def test_get_sun_B0_array_time():
    # Validate against published values from the Astronomical Almanac (2013)
    sun_B0 = get_sun_B0(Time(['2013-04-01', '2013-12-01']))
    assert_quantity_allclose(sun_B0[0], -6.54*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(sun_B0[1], 0.88*u.deg, atol=5e-3*u.deg)

def test_get_sun_L0():
    # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191)
    assert_quantity_allclose(get_sun_L0('1992-Oct-13'), 238.6317*u.deg, atol=5e-5*u.deg)

def test_get_sun_L0_array_time():
    # Validate against published values from the Astronomical Almanac (2013)
    sun_L0 = get_sun_L0(Time(['2013-04-01', '2013-12-01']))
    assert_quantity_allclose(sun_L0[0], 221.44*u.deg, atol=3e-2*u.deg)
    assert_quantity_allclose(sun_L0[1], 237.83*u.deg, atol=3e-2*u.deg)

def test_get_sun_P():
    # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191)
    assert_quantity_allclose(get_sun_P('1992-Oct-13'), 26.27*u.deg, atol=5e-3*u.deg)

def test_get_sun_P_array_time():
    # Validate against published values from the Astronomical Almanac (2013)
    sun_P = get_sun_P(Time(['2013-04-01', '2013-12-01']))
    assert_quantity_allclose(sun_P[0], -26.15*u.deg, atol=1e-2*u.deg)
    assert_quantity_allclose(sun_P[1], 16.05*u.deg, atol=1e-2*u.deg)

def test_get_sunearth_distance():
    # Validate against a published value from Astronomical Algorithms (Meeus 1998, p.191)
    assert_quantity_allclose(get_sunearth_distance('1992-Oct-13'), 0.997608*u.AU, atol=5e-7*u.AU)

def test_get_sunearth_distance_array_time():
    # Validate against published values from the Astronomical Almanac (2013)
    sunearth_distance = get_sunearth_distance(Time(['2013-04-01', '2013-12-01']))
    assert_quantity_allclose(sunearth_distance[0], 0.9992311*u.AU, atol=5e-7*u.AU)
    assert_quantity_allclose(sunearth_distance[1], 0.9861362*u.AU, atol=5e-7*u.AU)

def test_get_sun_orientation():
    # Not currently aware of a published value to check against, so just self-check for now

    # Check the Northern Hemisphere
    angle = get_sun_orientation(EarthLocation(lat=40*u.deg, lon=-75*u.deg), '2017-07-18 12:00')
    assert_quantity_allclose(angle, -59.4*u.deg, atol=0.1*u.deg)

    # Check the Southern Hemisphere
    angle = get_sun_orientation(EarthLocation(lat=-40*u.deg, lon=-75*u.deg), '2017-02-18 13:00')
    assert_quantity_allclose(angle, -110.8*u.deg, atol=0.1*u.deg)

@pytest.mark.remote_data
def test_get_horizons_coord():
    # Validate against published values from the Astronomical Almanac (2013)
    e1 = get_horizons_coord('Geocenter', '2013-Jan-01')
    assert_quantity_allclose((e1.lon + 1*u.deg) % (360*u.deg), 1*u.deg, atol=5e-6*u.deg)
    assert_quantity_allclose(e1.lat, -3.03*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(e1.radius, 0.9832947*u.AU, atol=5e-7*u.AU)

    e2 = get_horizons_coord('Geocenter', '2013-Sep-01')
    assert_quantity_allclose((e2.lon + 1*u.deg) % (360*u.deg), 1*u.deg, atol=5e-6*u.deg)
    assert_quantity_allclose(e2.lat, 7.19*u.deg, atol=5e-3*u.deg)
    assert_quantity_allclose(e2.radius, 1.0092561*u.AU, atol=5e-7*u.AU)
<|endoftext|>


<commit_before># No shebang line, this module is meant to be imported
#
# Copyright 2013 Oliver Palmer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Resource
--------

Base resources which can be used to build top leve
documents, pages, or other types of data for the web.
"""

from json import loads

try:
    from httplib import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED)
except ImportError:  # pragma: no cover
    from http.client import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED)

try:
    from itertools import ifilter as filter_
except ImportError:  # pragma: no cover
    filter_ = filter

try:
    from itertools import imap as map_
except ImportError:  # pragma: no cover
    map_ = map

from twisted.web.server import NOT_DONE_YET
from twisted.web.resource import Resource as _Resource
from voluptuous import Invalid, Schema

from pyfarm.core.enums import STRING_TYPES
from pyfarm.agent.http.core import template
from pyfarm.agent.logger import getLogger

logger = getLogger("agent.http.resource")

class Resource(_Resource):
    """
    Basic subclass of :class:`._Resource` for passing requests to
    specific methods.  Unlike :class:`._Resource` however this will
    will also handle:

        * rewriting of request objects
        * templating
        * content type discovery and validation
        * unpacking of request data
        * rerouting of request to specific internal methods
    """
    TEMPLATE = NotImplemented
    CONTENT_TYPES = set(["text/html", "application/json"])
    LOAD_DATA_FOR_METHODS = set(["POST", "PUT"])

    # Used by APIResource
    SCHEMAS = {}

    def __init__(self):
        _Resource.__init__(self)
        assert isinstance(self.CONTENT_TYPES, set)

    @property
    def template(self):
        """
        Loads the template provided but the partial path in ``TEMPLATE`` on
        the class.
        """
        if self.TEMPLATE is NotImplemented:
            raise NotImplementedError("You must set `TEMPLATE` first")
        return template.load(self.TEMPLATE)

    @property
    def methods(self):
        """A set containing all the methods this resource implements."""
        methods = set()
        for method in ("get", "post", "put", "delete", "head"):
            attribute_count = 0
            for attribute_name in (method, "render_%s" % method.upper()):
                attribute = getattr(self, attribute_name, None)
                if attribute is not None:
                    attribute_count += 1
                    methods.add(method)

            if attribute_count == 2:
                raise ValueError(
                    "%s has both `%s` and `%s` methods" % (
                        self.__class__.__name__,
                        method, "render_%s" % method.upper()))

        return methods

    def content_types(self, request, default=None):
        """Returns the content type(s) present in the request"""
        headers = request.requestHeaders.getRawHeaders("content-type")
        if isinstance(default, STRING_TYPES):
            default = [default]
        elif default is None:
            default = []
        return set(headers) if headers is not None else set(default)

    def putChild(self, path, child):
        """
        Overrides the builtin putChild() so we can return the results for
        each call and use them externally
        """
        _Resource.putChild(self, path, child)
        return child

    def error(self, request, code, message):
        """
        Writes the proper out an error response message depending on the
        content type in the request
        """
        content_types = self.content_types(request, default="text/html")
        logger.error(message)

        if "text/html" in content_types:
            request.setResponseCode(code)
            html_error = template.load("error.html")
            deferred = html_error.render(
                code=code, code_msg=responses[code], message=message)
            deferred.addCallback(request.write).addCallback(
                lambda _: request.finish())

        elif "application/json" in content_types:
            request.setResponseCode(code)
            request.write({"error": message})
            request.finish()

        else:
            request.setResponseCode(UNSUPPORTED_MEDIA_TYPE)
            request.write(
                {"error": "Can only handle text/html or application/json here"})
            request.finish()

    def render(self, request):
        # make sure that the requested content type is supported
        content_type = self.content_types(request, default=["text/html",
                                                            "application/json"])
        if not self.CONTENT_TYPES & content_type:
            self.error(
                request, UNSUPPORTED_MEDIA_TYPE,
                "%s is not a support content type for this url" % content_type)
            return NOT_DONE_YET

        # Try to find the method this web request is making by first trying
        # our usual convention then the 'standard' convention.
        request_methods = (request.method.lower(), "render_%s" % request.method)
        for method_name in request_methods:
            if hasattr(self, method_name):
                kwargs = {"request": request}

                # Unpack the incoming data for the request
                if "application/json" in content_type \
                        and request.method in self.LOAD_DATA_FOR_METHODS:
                    request_content = request.content.read()

                    # Check to see if we have any incoming data at all
                    if not request_content.strip():
                        self.error(request, BAD_REQUEST, "No data provided")
                        return NOT_DONE_YET

                    # Either load the data or handle the error, don't call
                    # the method unless we're successful.
                    try:
                        data = loads(request_content)

                    except ValueError as e:
                        self.error(
                            request, BAD_REQUEST,
                            "Failed to decode json data: %r" % e)
                        return NOT_DONE_YET

                    # We have data, check to see if we have a schema
                    # and if we do does it validate.
                    schema = self.SCHEMAS.get(request.method)
                    if isinstance(schema, Schema):
                        try:
                            schema(data)
                        except Invalid as e:
                            self.error(
                                request, BAD_REQUEST,
                                "Failed to validate the request data "
                                "against the schema: %s" % e)
                            return NOT_DONE_YET

                    kwargs.update(data=data)

                return getattr(self, method_name)(**kwargs)

        # If we could not find function to call for the given method
        # produce an error.
        else:
            supported_methods = self.methods
            message = "%r only supports the %s method(s)" % (
                request.uri,
                ", ".join(list(map_(str.upper, supported_methods))))

            self.error(request, METHOD_NOT_ALLOWED, message)
            return NOT_DONE_YET
<commit_msg>adding TODOs<commit_after># No shebang line, this module is meant to be imported
#
# Copyright 2013 Oliver Palmer
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Resource
--------

Base resources which can be used to build top leve
documents, pages, or other types of data for the web.
"""

from json import loads

try:
    from httplib import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED)
except ImportError:  # pragma: no cover
    from http.client import (
        responses, NOT_FOUND, BAD_REQUEST, UNSUPPORTED_MEDIA_TYPE,
        METHOD_NOT_ALLOWED)

try:
    from itertools import ifilter as filter_
except ImportError:  # pragma: no cover
    filter_ = filter

try:
    from itertools import imap as map_
except ImportError:  # pragma: no cover
    map_ = map

from twisted.web.server import NOT_DONE_YET
from twisted.web.resource import Resource as _Resource
from voluptuous import Invalid, Schema

from pyfarm.core.enums import STRING_TYPES
from pyfarm.agent.http.core import template
from pyfarm.agent.logger import getLogger

logger = getLogger("agent.http.resource")

class Resource(_Resource):
    """
    Basic subclass of :class:`._Resource` for passing requests to
    specific methods.  Unlike :class:`._Resource` however this will
    will also handle:

        * rewriting of request objects
        * templating
        * content type discovery and validation
        * unpacking of request data
        * rerouting of request to specific internal methods
    """
    TEMPLATE = NotImplemented
    CONTENT_TYPES = set(["text/html", "application/json"])
    LOAD_DATA_FOR_METHODS = set(["POST", "PUT"])

    # Used by APIResource
    SCHEMAS = {}

    def __init__(self):
        _Resource.__init__(self)
        assert isinstance(self.CONTENT_TYPES, set)

    @property
    def template(self):
        """
        Loads the template provided but the partial path in ``TEMPLATE`` on
        the class.
        """
        if self.TEMPLATE is NotImplemented:
            raise NotImplementedError("You must set `TEMPLATE` first")
        return template.load(self.TEMPLATE)

    @property
    def methods(self):
        """A set containing all the methods this resource implements."""
        methods = set()
        for method in ("get", "post", "put", "delete", "head"):
            attribute_count = 0
            for attribute_name in (method, "render_%s" % method.upper()):
                attribute = getattr(self, attribute_name, None)
                if attribute is not None:
                    attribute_count += 1
                    methods.add(method)

            if attribute_count == 2:
                raise ValueError(
                    "%s has both `%s` and `%s` methods" % (
                        self.__class__.__name__,
                        method, "render_%s" % method.upper()))

        return methods

    def content_types(self, request, default=None):
        """Returns the content type(s) present in the request"""
        headers = request.requestHeaders.getRawHeaders("content-type")
        if isinstance(default, STRING_TYPES):
            default = [default]
        elif default is None:
            default = []
        return set(headers) if headers is not None else set(default)

    def putChild(self, path, child):
        """
        Overrides the builtin putChild() so we can return the results for
        each call and use them externally
        """
        _Resource.putChild(self, path, child)
        return child

    def error(self, request, code, message):
        """
        Writes the proper out an error response message depending on the
        content type in the request
        """
        content_types = self.content_types(request, default="text/html")
        logger.error(message)

        if "text/html" in content_types:
            request.setResponseCode(code)
            html_error = template.load("error.html")
            deferred = html_error.render(
                code=code, code_msg=responses[code], message=message)
            deferred.addCallback(request.write).addCallback(
                lambda _: request.finish())

        elif "application/json" in content_types:
            request.setResponseCode(code)
            request.write({"error": message})
            request.finish()

        else:
            request.setResponseCode(UNSUPPORTED_MEDIA_TYPE)
            request.write(
                {"error": "Can only handle text/html or application/json here"})
            request.finish()

    def render(self, request):
        # make sure that the requested content type is supported
        content_type = self.content_types(request, default=["text/html",
                                                            "application/json"])
        if not self.CONTENT_TYPES & content_type:
            self.error(
                request, UNSUPPORTED_MEDIA_TYPE,
                "%s is not a support content type for this url" % content_type)
            return NOT_DONE_YET

        # Try to find the method this web request is making by first trying
        # our usual convention then the 'standard' convention.
        request_methods = (request.method.lower(), "render_%s" % request.method)
        for method_name in request_methods:
            if hasattr(self, method_name):
                kwargs = {"request": request}

                # Unpack the incoming data for the request
                if "application/json" in content_type \
                        and request.method in self.LOAD_DATA_FOR_METHODS:
                    request_content = request.content.read()

                    # Check to see if we have any incoming data at all
                    if not request_content.strip():
                        self.error(request, BAD_REQUEST, "No data provided")
                        return NOT_DONE_YET

                    # Either load the data or handle the error, don't call
                    # the method unless we're successful.
                    try:
                        data = loads(request_content)

                    except ValueError as e:
                        self.error(
                            request, BAD_REQUEST,
                            "Failed to decode json data: %r" % e)
                        return NOT_DONE_YET

                    # We have data, check to see if we have a schema
                    # and if we do does it validate.
                    schema = self.SCHEMAS.get(request.method)
                    if isinstance(schema, Schema):
                        try:
                            schema(data)
                        except Invalid as e:
                            self.error(
                                request, BAD_REQUEST,
                                "Failed to validate the request data "
                                "against the schema: %s" % e)
                            return NOT_DONE_YET

                    kwargs.update(data=data)

                # TODO: tuple response support (like Flask)
                # TODO: deferred support for inlineCallbacks
                return getattr(self, method_name)(**kwargs)

        # If we could not find function to call for the given method
        # produce an error.
        else:
            supported_methods = self.methods
            message = "%r only supports the %s method(s)" % (
                request.uri,
                ", ".join(list(map_(str.upper, supported_methods))))

            self.error(request, METHOD_NOT_ALLOWED, message)
            return NOT_DONE_YET
<|endoftext|>


<commit_before>import json
from .course import Course
from .coursetime import courseTime
from datetime import datetime

class CourseManager():
    """A class for managing courses.

    Attributes:
        courses (list): A list of all course objects being handled by the session.
        saved (bool): Whether or not the course information is consistent with
                      the file on the disk.
        last_course_id (int): The greatest course id in use by a Course object.
    """
    def __init__(self, store=None):
        """Initialize a CourseManager.

        Arguments:
            store (Gtk.ListStore): a store to append data into.
        """
        self.store = store

        self.courses = {}
        self.ge_map = {}
        self.user = {
                'year': 1
                }

        today = datetime.today()

        fall = datetime(today.year, 9, 15)
        winter = datetime(today.year, 1, 1)
        spring = datetime(today.year, 3, 31)
        summer = datetime(today.year, 6, 15)

        if fall <= today <= datetime(today.year, 12, 31):
            self.quarter = 0
        elif winter <= today < spring:
            self.quarter = 1
        elif spring <= today < summer:
            self.quarter = 2
        else:
            self.quarter = 3

        self.saved = True
        self.last_course_id = 0

    def load_file(self, filename):
        """Load a file into the course manager.

        Arguments:
            filename (str): The path to the JSON file
                            containing the course information.

        Returns:
            1 if successful, 0 otherwise.
        """
        with open(filename, 'r') as jsonfile:
            try:
                courses = json.loads(jsonfile.read())
            except ValueError:
                return 0

        file_courses = courses
        course_id = 0
        course_ids = []

        tmp_cs = file_courses['courses']

        if isinstance(tmp_cs, list):
            cs = {}
            for course_object in tmp_cs:
                object_id = course_object.pop('course_id')
                cs[object_id] = course_object
        elif isinstance(tmp_cs, dict):
            cs = tmp_cs
        else:
            raise Exception("Invalid course object!")
            return 0

        for course_id, course in cs.items():
            course_id = int(course_id)
            if isinstance(course['prereqs'], str):
                course['prereqs'] = [x.strip() for x in
                    course['prereqs'].split(',')]

            if 'ge_type' not in course:
                course['ge_type'] = None

            course['time'] = courseTime(int(course['time'][0]), course['time'][1])

            self.courses[course_id] = course

            if self.store:
                self.store.append([
                    course['catalog'],
                    str(
                        str(course['time'][0]) +
                        ', ' +
                        course['time'][1]
                        ),
                    course['credits'],
                    course['course_type'],
                    course_id
                ])
            course_ids.append(course_id)

        self.last_course_id = max(course_ids)
        return 1

    def edit_entry(self, chosen_course):
        """Edit existing entry.

        Arguments:
            chosen_course (Course): The course to edit.
            selection (Gtk.TreeSelection): An optional Gtk TreeSelection representing
                the current selection in the treeview that is to be edited.

        """
        c_id = chosen_course.course_id

        for row in self.store:
            if row[4] == c_id:
                course_iter = row.iter

        self.store[course_iter] = [
                chosen_course.catalog,
                str(
                    str(chosen_course.time[0]) +
                    ', ' +
                    chosen_course.time[1]
                    ),
                chosen_course.credits,
                chosen_course.course_type,
                c_id
                ]

        # Convert the tuple to a courseTime
        time = courseTime(chosen_course.time[0], chosen_course.time[1])

        # Course ID from the arguments
        self.courses[c_id]['title']       = chosen_course.title
        self.courses[c_id]['catalog']     = chosen_course.catalog
        self.courses[c_id]['credits']     = chosen_course.credits
        self.courses[c_id]['prereqs']     = chosen_course.prereqs
        self.courses[c_id]['time']        = time
        self.courses[c_id]['course_type'] = chosen_course.course_type
        self.courses[c_id]['ge_type']     = chosen_course.ge_type

        self.saved = False
        return chosen_course.course_id

    def delete_entry(self, selected_id):
        """Delete existing entry.

        Arguments:
            selected_id (int): The id of the course to delete.
        """
        for row in self.store:
            if row[4] == selected_id:
                self.store.remove(row.iter)

        entry = self.courses.pop(selected_id)

        self.saved = False
        return entry

    def add_entry(self, course):
        """Add a course to the CourseManager's list."""
        self.saved = False
        course = course.export()
        c_id = course.pop('course_id')

        if not c_id:
            c_id = self.last_course_id
        elif c_id in self.courses:
            c_id = self.last_course_id + 1
            self.last_course_id = self.last_course_id + 1

        self.courses[c_id] = course
        if self.store:
            self.store.append([
                course['catalog'],
                str(
                    str(course['time'][0]) +
                    ', ' +
                    course['time'][1]
                ),
                course['credits'],
                course['course_type'],
                c_id
            ])

        self.last_course_id = self.last_course_id + 1

    def check_prereq_conflicts(self, entry):
        time = courseTime(int(entry.time[0]), entry.time[1])

        for c_id, course in self.courses.items():
            if course['time'] > time:
                if course['catalog'] in entry.prereqs:
                    return True
                    break

        return False

    def get_course(self, c_id):
        course = self.courses[c_id]
        return Course(
                course['title'],
                course['catalog'],
                course['credits'],
                course['prereqs'],
                course['time'],
                course['course_type'],
                course['ge_type'],
                c_id)

    def save(self, filename):
        """Save all courses to the given filename."""
        with open(filename, 'w') as flowfile:
            courses = {
                    'courses' : self.courses
                    }
            flowfile.write(json.dumps(courses, indent=4))
        self.saved = True
<commit_msg>Also give warning if prereq in same quarter<commit_after>import json
from .course import Course
from .coursetime import courseTime
from datetime import datetime

class CourseManager():
    """A class for managing courses.

    Attributes:
        courses (list): A list of all course objects being handled by the session.
        saved (bool): Whether or not the course information is consistent with
                      the file on the disk.
        last_course_id (int): The greatest course id in use by a Course object.
    """
    def __init__(self, store=None):
        """Initialize a CourseManager.

        Arguments:
            store (Gtk.ListStore): a store to append data into.
        """
        self.store = store

        self.courses = {}
        self.ge_map = {}
        self.user = {
                'year': 1
                }

        today = datetime.today()

        fall = datetime(today.year, 9, 15)
        winter = datetime(today.year, 1, 1)
        spring = datetime(today.year, 3, 31)
        summer = datetime(today.year, 6, 15)

        if fall <= today <= datetime(today.year, 12, 31):
            self.quarter = 0
        elif winter <= today < spring:
            self.quarter = 1
        elif spring <= today < summer:
            self.quarter = 2
        else:
            self.quarter = 3

        self.saved = True
        self.last_course_id = 0

    def load_file(self, filename):
        """Load a file into the course manager.

        Arguments:
            filename (str): The path to the JSON file
                            containing the course information.

        Returns:
            1 if successful, 0 otherwise.
        """
        with open(filename, 'r') as jsonfile:
            try:
                courses = json.loads(jsonfile.read())
            except ValueError:
                return 0

        file_courses = courses
        course_id = 0
        course_ids = []

        tmp_cs = file_courses['courses']

        if isinstance(tmp_cs, list):
            cs = {}
            for course_object in tmp_cs:
                object_id = course_object.pop('course_id')
                cs[object_id] = course_object
        elif isinstance(tmp_cs, dict):
            cs = tmp_cs
        else:
            raise Exception("Invalid course object!")
            return 0

        for course_id, course in cs.items():
            course_id = int(course_id)
            if isinstance(course['prereqs'], str):
                course['prereqs'] = [x.strip() for x in
                    course['prereqs'].split(',')]

            if 'ge_type' not in course:
                course['ge_type'] = None

            course['time'] = courseTime(int(course['time'][0]), course['time'][1])

            self.courses[course_id] = course

            if self.store:
                self.store.append([
                    course['catalog'],
                    str(
                        str(course['time'][0]) +
                        ', ' +
                        course['time'][1]
                        ),
                    course['credits'],
                    course['course_type'],
                    course_id
                ])
            course_ids.append(course_id)

        self.last_course_id = max(course_ids)
        return 1

    def edit_entry(self, chosen_course):
        """Edit existing entry.

        Arguments:
            chosen_course (Course): The course to edit.
            selection (Gtk.TreeSelection): An optional Gtk TreeSelection representing
                the current selection in the treeview that is to be edited.

        """
        c_id = chosen_course.course_id

        for row in self.store:
            if row[4] == c_id:
                course_iter = row.iter

        self.store[course_iter] = [
                chosen_course.catalog,
                str(
                    str(chosen_course.time[0]) +
                    ', ' +
                    chosen_course.time[1]
                    ),
                chosen_course.credits,
                chosen_course.course_type,
                c_id
                ]

        # Convert the tuple to a courseTime
        time = courseTime(chosen_course.time[0], chosen_course.time[1])

        # Course ID from the arguments
        self.courses[c_id]['title']       = chosen_course.title
        self.courses[c_id]['catalog']     = chosen_course.catalog
        self.courses[c_id]['credits']     = chosen_course.credits
        self.courses[c_id]['prereqs']     = chosen_course.prereqs
        self.courses[c_id]['time']        = time
        self.courses[c_id]['course_type'] = chosen_course.course_type
        self.courses[c_id]['ge_type']     = chosen_course.ge_type

        self.saved = False
        return chosen_course.course_id

    def delete_entry(self, selected_id):
        """Delete existing entry.

        Arguments:
            selected_id (int): The id of the course to delete.
        """
        for row in self.store:
            if row[4] == selected_id:
                self.store.remove(row.iter)

        entry = self.courses.pop(selected_id)

        self.saved = False
        return entry

    def add_entry(self, course):
        """Add a course to the CourseManager's list."""
        self.saved = False
        course = course.export()
        c_id = course.pop('course_id')

        if not c_id:
            c_id = self.last_course_id
        elif c_id in self.courses:
            c_id = self.last_course_id + 1
            self.last_course_id = self.last_course_id + 1

        self.courses[c_id] = course
        if self.store:
            self.store.append([
                course['catalog'],
                str(
                    str(course['time'][0]) +
                    ', ' +
                    course['time'][1]
                ),
                course['credits'],
                course['course_type'],
                c_id
            ])

        self.last_course_id = self.last_course_id + 1

    def check_prereq_conflicts(self, entry):
        time = courseTime(int(entry.time[0]), entry.time[1])

        for c_id, course in self.courses.items():
            if course['time'] >= time:
                if course['catalog'] in entry.prereqs:
                    return True
                    break

        return False

    def get_course(self, c_id):
        course = self.courses[c_id]
        return Course(
                course['title'],
                course['catalog'],
                course['credits'],
                course['prereqs'],
                course['time'],
                course['course_type'],
                course['ge_type'],
                c_id)

    def save(self, filename):
        """Save all courses to the given filename."""
        with open(filename, 'w') as flowfile:
            courses = {
                    'courses' : self.courses
                    }
            flowfile.write(json.dumps(courses, indent=4))
        self.saved = True
<|endoftext|>