<commit_before># -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------
# Copyright © 2016, Continuum Analytics, Inc. All rights reserved.
#
# The full license is in the file LICENSE.txt, distributed with this software.
# ----------------------------------------------------------------------------
"""Types related to project requirements."""
from __future__ import absolute_import

from abc import ABCMeta, abstractmethod
from copy import deepcopy

from anaconda_project.internal.metaclass import with_metaclass
from anaconda_project.internal.py2_compat import is_string
from anaconda_project.status import Status

class UserConfigOverrides(object):
    """Class containing user-forced configuration for the prepare process."""

    def __init__(self, env_spec_name=None):
        """Construct a set of user overrides for the prepare process."""
        self._env_spec_name = env_spec_name

    @property
    def env_spec_name(self):
        """The user-specified name of the conda environment spec to use, or None if not specified."""
        return self._env_spec_name

    @env_spec_name.setter
    def env_spec_name(self, value):
        """Change the conda environment name override."""
        self._env_spec_name = value

class RequirementStatus(Status):
    """Class describing the status of a requirement.

    Values of this class are immutable; to get updated status, you
    would call ``recheck()`` to get a new status.

    """

    def __init__(self, requirement, has_been_provided, status_description, provider, analysis, latest_provide_result):
        """Construct an abstract RequirementStatus."""
        self._requirement = requirement
        self._has_been_provided = has_been_provided
        self._status_description = status_description
        self._provider = provider
        self._analysis = analysis
        self._latest_provide_result = latest_provide_result

    def __bool__(self):
        """True if the requirement is met."""
        return self.has_been_provided

    def __nonzero__(self):
        """True if the requirement is met."""
        return self.__bool__()  # pragma: no cover (py2 only)

    @property
    def requirement(self):
        """Get the requirement we are the status of."""
        return self._requirement

    @property
    def has_been_provided(self):
        """Get True if the requirement has been met."""
        return self._has_been_provided

    @property
    def status_description(self):
        """Get a description of how the requirement has been met (or why it hasn't been)."""
        return self._status_description

    @property
    def provider(self):
        """Get the provider for this requirement."""
        return self._provider

    @property
    def analysis(self):
        """Get the provider's analysis of the status."""
        return self._analysis

    @property
    def latest_provide_result(self):
        """Get the latest ``ProvideResult`` or None if we haven't provided yet."""
        return self._latest_provide_result

    @property
    def logs(self):
        """Get logs relevant to the status, from either checking status or attempting to provide it."""
        if self.latest_provide_result is None:
            return []
        else:
            return self.latest_provide_result.logs

    @property
    def errors(self):
        """Get error logs relevant to the status, from either checking status or attempting to provide it."""
        if self.latest_provide_result is None:
            return []
        else:
            return self.latest_provide_result.errors

    def recheck(self, environ, local_state_file, default_env_spec_name, overrides=None, latest_provide_result=None):
        """Get a new ``RequirementStatus`` reflecting the current state.

        This calls ``Requirement.check_status()`` which can do network and filesystem IO,
        so be cautious about where you call it.
        """
        if latest_provide_result is None:
            latest_provide_result = self._latest_provide_result
        return self.requirement.check_status(environ, local_state_file, default_env_spec_name, overrides,
                                             latest_provide_result)

class Requirement(with_metaclass(ABCMeta)):
    """Describes a requirement of the project (from the project config).

    Note that this is not specifically a package requirement;
    this class is more general, it can be a requirement for any
    condition at all (that a service is running, that a file
    exists, or even that a package is intalled - anything you can
    think of).

    """

    def __init__(self, registry, options):
        """Construct a Requirement.

        Args:
            registry (PluginRegistry): the plugin registry we came from
            options (dict): dict of requirement options from the project config
        """
        self.registry = registry

        if options is None:
            self.options = dict()
        else:
            self.options = deepcopy(options)
            # always convert the default to a string (it's allowed to be a number
            # in the config file, but env vars have to be strings), unless
            # it's a dict because we use a dict for encrypted defaults
            if 'default' in self.options and not (is_string(self.options['default']) or isinstance(
                    self.options['default'], dict)):
                self.options['default'] = str(self.options['default'])

    @property
    @abstractmethod
    def title(self):
        """Human-readable short name of the requirement."""
        pass  # pragma: no cover

    def _description(self, default):
        """Use this in subclasses to implement the description property."""
        if 'description' in self.options:
            return self.options['description']
        else:
            return default

    @property
    @abstractmethod
    def description(self):
        """Human-readable about-one-sentence hint or tooltip for the requirement."""
        pass  # pragma: no cover

    @property
    def ignore_patterns(self):
        """Set of ignore patterns for files this requirement's provider might autogenerate."""
        return set()

    def _create_status(self, environ, local_state_file, default_env_spec_name, overrides, latest_provide_result,
                       has_been_provided, status_description, provider_class_name):
        provider = self.registry.find_provider_by_class_name(provider_class_name)
        analysis = provider.analyze(self, environ, local_state_file, default_env_spec_name, overrides)
        return RequirementStatus(self,
                                 has_been_provided=has_been_provided,
                                 status_description=status_description,
                                 provider=provider,
                                 analysis=analysis,
                                 latest_provide_result=latest_provide_result)

    def _create_status_from_analysis(self, environ, local_state_file, default_env_spec_name, overrides,
                                     latest_provide_result, provider_class_name, status_getter):
        provider = self.registry.find_provider_by_class_name(provider_class_name)
        analysis = provider.analyze(self, environ, local_state_file, default_env_spec_name, overrides)
        (has_been_provided, status_description) = status_getter(environ, local_state_file, analysis)
        return RequirementStatus(self,
                                 has_been_provided=has_been_provided,
                                 status_description=status_description,
                                 provider=provider,
                                 analysis=analysis,
                                 latest_provide_result=latest_provide_result)

    @abstractmethod
    def check_status(self, environ, local_state_file, default_env_spec_name, overrides, latest_provide_result=None):
        """Check on the requirement and return a ``RequirementStatus`` with the current status.

        This may attempt to talk to servers, check that files
        exist on disk, and other work of that nature to verify the
        requirement's status, so be careful about when and how
        often this gets called.

        Args:
            environ (dict): use this rather than the system environment directly
            local_state_file (LocalStateFile): project local state
            default_env_spec_name (str): fallback env spec name
            overrides (UserConfigOverrides): user-supplied forced choices
            latest_provide_result (ProvideResult): most recent result of ``provide()`` or None

        Returns:
            a ``RequirementStatus``

        """
        pass  # pragma: no cover (abstract method)

# suffixes that change the default for the "encrypted" option
_secret_suffixes = ('_PASSWORD', '_ENCRYPTED', '_SECRET_KEY', '_SECRET')

class EnvVarRequirement(Requirement):
    """A requirement that a certain environment variable be set."""

    @classmethod
    def _parse_default(self, options, env_var, problems):
        assert (isinstance(options, dict))

        raw_default = options.get('default', None)

        if raw_default is None:
            good_default = True
        elif isinstance(raw_default, bool):
            # we have to check bool since it's considered an int apparently
            good_default = False
        elif is_string(raw_default) or isinstance(raw_default, (int, float)):
            good_default = True
        elif isinstance(raw_default, dict):
            # we only allow a dict if it represents an encrypted value
            if ('key' in raw_default) and ('encrypted' in raw_default):
                good_default = True
            else:
                good_default = False
        else:
            good_default = False

        if 'default' in options and raw_default is None:
            # convert null to be the same as simply missing
            del options['default']

        if good_default:
            return True
        else:
            if isinstance(raw_default, dict):
                problems.append(("default value for variable {env_var} can be a dict but only if it " +
                                 "contains 'key' and 'encrypted' fields; found {value}").format(env_var=env_var,
                                                                                value=dict(
                                                                                raw_default)))
            else:
                problems.append(
                    "default value for variable {env_var} must be null, a string, or a number, not {value}.".format(
                        env_var=env_var,
                        value=raw_default))
            return False

    def __init__(self, registry, env_var, options=None):
        """Construct an EnvVarRequirement for the given ``env_var`` with the given options."""
        super(EnvVarRequirement, self).__init__(registry, options)
        assert env_var is not None
        self.env_var = env_var

    def __repr__(self):
        """Custom repr of EnvVarRequirement."""
        return "%s(env_var='%s')" % (self.__class__.__name__, self.env_var)

    @property
    def title(self):
        """Override superclass title."""
        return self.env_var

    @property
    def description(self):
        """Override superclass description."""
        return self._description("%s environment variable must be set." % (self.env_var))

    @property
    def encrypted(self):
        """Get whether this is a password-type value we should encrypt when possible."""
        if 'encrypted' in self.options:
            return self.options['encrypted']
        else:
            return any(self.env_var.endswith(suffix) for suffix in _secret_suffixes)

    def _get_value_of_env_var(self, environ):
        """A "protected" method for subtypes to use."""
        value = environ.get(self.env_var, None)
        if value == "":  # do we really want this, maybe empty string is a valid value sometimes?
            value = None
        return value

    def _unset_message(self):
        """A "protected" method for subtypes to use."""
        return "Environment variable {env_var} is not set.".format(env_var=self.env_var)

    def _set_message(self, environ):
        """A "protected" method for subtypes to use."""
        if self.encrypted:
            # don't include the value if it's an encrypted variable.
            return "Environment variable {env_var} is set.".format(env_var=self.env_var)
        else:
            return "Environment variable {env_var} set to '{value}'".format(env_var=self.env_var,
                                                                            value=self._get_value_of_env_var(environ))

    def check_status(self, environ, local_state_file, default_env_spec_name, overrides, latest_provide_result=None):
        """Override superclass to get our status."""
        value = self._get_value_of_env_var(environ)

        has_been_provided = value is not None
        if has_been_provided:
            status_description = self._set_message(environ)
        else:
            status_description = self._unset_message()

        return self._create_status(environ,
                                   local_state_file,
                                   default_env_spec_name=default_env_spec_name,
                                   overrides=overrides,
                                   has_been_provided=has_been_provided,
                                   status_description=status_description,
                                   provider_class_name='EnvVarProvider',
                                   latest_provide_result=latest_provide_result)
<commit_msg>In beta mode, don't autoset "encrypted" flag on anything.<commit_after># -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------
# Copyright © 2016, Continuum Analytics, Inc. All rights reserved.
#
# The full license is in the file LICENSE.txt, distributed with this software.
# ----------------------------------------------------------------------------
"""Types related to project requirements."""
from __future__ import absolute_import

from abc import ABCMeta, abstractmethod
from copy import deepcopy

import anaconda_project
from anaconda_project.internal.metaclass import with_metaclass
from anaconda_project.internal.py2_compat import is_string
from anaconda_project.status import Status

class UserConfigOverrides(object):
    """Class containing user-forced configuration for the prepare process."""

    def __init__(self, env_spec_name=None):
        """Construct a set of user overrides for the prepare process."""
        self._env_spec_name = env_spec_name

    @property
    def env_spec_name(self):
        """The user-specified name of the conda environment spec to use, or None if not specified."""
        return self._env_spec_name

    @env_spec_name.setter
    def env_spec_name(self, value):
        """Change the conda environment name override."""
        self._env_spec_name = value

class RequirementStatus(Status):
    """Class describing the status of a requirement.

    Values of this class are immutable; to get updated status, you
    would call ``recheck()`` to get a new status.

    """

    def __init__(self, requirement, has_been_provided, status_description, provider, analysis, latest_provide_result):
        """Construct an abstract RequirementStatus."""
        self._requirement = requirement
        self._has_been_provided = has_been_provided
        self._status_description = status_description
        self._provider = provider
        self._analysis = analysis
        self._latest_provide_result = latest_provide_result

    def __bool__(self):
        """True if the requirement is met."""
        return self.has_been_provided

    def __nonzero__(self):
        """True if the requirement is met."""
        return self.__bool__()  # pragma: no cover (py2 only)

    @property
    def requirement(self):
        """Get the requirement we are the status of."""
        return self._requirement

    @property
    def has_been_provided(self):
        """Get True if the requirement has been met."""
        return self._has_been_provided

    @property
    def status_description(self):
        """Get a description of how the requirement has been met (or why it hasn't been)."""
        return self._status_description

    @property
    def provider(self):
        """Get the provider for this requirement."""
        return self._provider

    @property
    def analysis(self):
        """Get the provider's analysis of the status."""
        return self._analysis

    @property
    def latest_provide_result(self):
        """Get the latest ``ProvideResult`` or None if we haven't provided yet."""
        return self._latest_provide_result

    @property
    def logs(self):
        """Get logs relevant to the status, from either checking status or attempting to provide it."""
        if self.latest_provide_result is None:
            return []
        else:
            return self.latest_provide_result.logs

    @property
    def errors(self):
        """Get error logs relevant to the status, from either checking status or attempting to provide it."""
        if self.latest_provide_result is None:
            return []
        else:
            return self.latest_provide_result.errors

    def recheck(self, environ, local_state_file, default_env_spec_name, overrides=None, latest_provide_result=None):
        """Get a new ``RequirementStatus`` reflecting the current state.

        This calls ``Requirement.check_status()`` which can do network and filesystem IO,
        so be cautious about where you call it.
        """
        if latest_provide_result is None:
            latest_provide_result = self._latest_provide_result
        return self.requirement.check_status(environ, local_state_file, default_env_spec_name, overrides,
                                             latest_provide_result)

class Requirement(with_metaclass(ABCMeta)):
    """Describes a requirement of the project (from the project config).

    Note that this is not specifically a package requirement;
    this class is more general, it can be a requirement for any
    condition at all (that a service is running, that a file
    exists, or even that a package is intalled - anything you can
    think of).

    """

    def __init__(self, registry, options):
        """Construct a Requirement.

        Args:
            registry (PluginRegistry): the plugin registry we came from
            options (dict): dict of requirement options from the project config
        """
        self.registry = registry

        if options is None:
            self.options = dict()
        else:
            self.options = deepcopy(options)
            # always convert the default to a string (it's allowed to be a number
            # in the config file, but env vars have to be strings), unless
            # it's a dict because we use a dict for encrypted defaults
            if 'default' in self.options and not (is_string(self.options['default']) or isinstance(
                    self.options['default'], dict)):
                self.options['default'] = str(self.options['default'])

    @property
    @abstractmethod
    def title(self):
        """Human-readable short name of the requirement."""
        pass  # pragma: no cover

    def _description(self, default):
        """Use this in subclasses to implement the description property."""
        if 'description' in self.options:
            return self.options['description']
        else:
            return default

    @property
    @abstractmethod
    def description(self):
        """Human-readable about-one-sentence hint or tooltip for the requirement."""
        pass  # pragma: no cover

    @property
    def ignore_patterns(self):
        """Set of ignore patterns for files this requirement's provider might autogenerate."""
        return set()

    def _create_status(self, environ, local_state_file, default_env_spec_name, overrides, latest_provide_result,
                       has_been_provided, status_description, provider_class_name):
        provider = self.registry.find_provider_by_class_name(provider_class_name)
        analysis = provider.analyze(self, environ, local_state_file, default_env_spec_name, overrides)
        return RequirementStatus(self,
                                 has_been_provided=has_been_provided,
                                 status_description=status_description,
                                 provider=provider,
                                 analysis=analysis,
                                 latest_provide_result=latest_provide_result)

    def _create_status_from_analysis(self, environ, local_state_file, default_env_spec_name, overrides,
                                     latest_provide_result, provider_class_name, status_getter):
        provider = self.registry.find_provider_by_class_name(provider_class_name)
        analysis = provider.analyze(self, environ, local_state_file, default_env_spec_name, overrides)
        (has_been_provided, status_description) = status_getter(environ, local_state_file, analysis)
        return RequirementStatus(self,
                                 has_been_provided=has_been_provided,
                                 status_description=status_description,
                                 provider=provider,
                                 analysis=analysis,
                                 latest_provide_result=latest_provide_result)

    @abstractmethod
    def check_status(self, environ, local_state_file, default_env_spec_name, overrides, latest_provide_result=None):
        """Check on the requirement and return a ``RequirementStatus`` with the current status.

        This may attempt to talk to servers, check that files
        exist on disk, and other work of that nature to verify the
        requirement's status, so be careful about when and how
        often this gets called.

        Args:
            environ (dict): use this rather than the system environment directly
            local_state_file (LocalStateFile): project local state
            default_env_spec_name (str): fallback env spec name
            overrides (UserConfigOverrides): user-supplied forced choices
            latest_provide_result (ProvideResult): most recent result of ``provide()`` or None

        Returns:
            a ``RequirementStatus``

        """
        pass  # pragma: no cover (abstract method)

# suffixes that change the default for the "encrypted" option
_secret_suffixes = ('_PASSWORD', '_ENCRYPTED', '_SECRET_KEY', '_SECRET')
if anaconda_project._beta_test_mode:
    _secret_suffixes = ()  # credentials stuff is just confusing for now # pragma: no cover (beta mode)

class EnvVarRequirement(Requirement):
    """A requirement that a certain environment variable be set."""

    @classmethod
    def _parse_default(self, options, env_var, problems):
        assert (isinstance(options, dict))

        raw_default = options.get('default', None)

        if raw_default is None:
            good_default = True
        elif isinstance(raw_default, bool):
            # we have to check bool since it's considered an int apparently
            good_default = False
        elif is_string(raw_default) or isinstance(raw_default, (int, float)):
            good_default = True
        elif isinstance(raw_default, dict):
            # we only allow a dict if it represents an encrypted value
            if ('key' in raw_default) and ('encrypted' in raw_default):
                good_default = True
            else:
                good_default = False
        else:
            good_default = False

        if 'default' in options and raw_default is None:
            # convert null to be the same as simply missing
            del options['default']

        if good_default:
            return True
        else:
            if isinstance(raw_default, dict):
                problems.append(("default value for variable {env_var} can be a dict but only if it " +
                                 "contains 'key' and 'encrypted' fields; found {value}").format(env_var=env_var,
                                                                                value=dict(
                                                                                raw_default)))
            else:
                problems.append(
                    "default value for variable {env_var} must be null, a string, or a number, not {value}.".format(
                        env_var=env_var,
                        value=raw_default))
            return False

    def __init__(self, registry, env_var, options=None):
        """Construct an EnvVarRequirement for the given ``env_var`` with the given options."""
        super(EnvVarRequirement, self).__init__(registry, options)
        assert env_var is not None
        self.env_var = env_var

    def __repr__(self):
        """Custom repr of EnvVarRequirement."""
        return "%s(env_var='%s')" % (self.__class__.__name__, self.env_var)

    @property
    def title(self):
        """Override superclass title."""
        return self.env_var

    @property
    def description(self):
        """Override superclass description."""
        return self._description("%s environment variable must be set." % (self.env_var))

    @property
    def encrypted(self):
        """Get whether this is a password-type value we should encrypt when possible."""
        if 'encrypted' in self.options:
            return self.options['encrypted']
        else:
            return any(self.env_var.endswith(suffix) for suffix in _secret_suffixes)

    def _get_value_of_env_var(self, environ):
        """A "protected" method for subtypes to use."""
        value = environ.get(self.env_var, None)
        if value == "":  # do we really want this, maybe empty string is a valid value sometimes?
            value = None
        return value

    def _unset_message(self):
        """A "protected" method for subtypes to use."""
        return "Environment variable {env_var} is not set.".format(env_var=self.env_var)

    def _set_message(self, environ):
        """A "protected" method for subtypes to use."""
        if self.encrypted:
            # don't include the value if it's an encrypted variable.
            return "Environment variable {env_var} is set.".format(env_var=self.env_var)
        else:
            return "Environment variable {env_var} set to '{value}'".format(env_var=self.env_var,
                                                                            value=self._get_value_of_env_var(environ))

    def check_status(self, environ, local_state_file, default_env_spec_name, overrides, latest_provide_result=None):
        """Override superclass to get our status."""
        value = self._get_value_of_env_var(environ)

        has_been_provided = value is not None
        if has_been_provided:
            status_description = self._set_message(environ)
        else:
            status_description = self._unset_message()

        return self._create_status(environ,
                                   local_state_file,
                                   default_env_spec_name=default_env_spec_name,
                                   overrides=overrides,
                                   has_been_provided=has_been_provided,
                                   status_description=status_description,
                                   provider_class_name='EnvVarProvider',
                                   latest_provide_result=latest_provide_result)
<|endoftext|>


<commit_before>"""
API methods for module level data.
"""
from rest_framework import generics, status

from analytics_data_api.constants import (
    learner
)
from analytics_data_api.v0.exceptions import (
    LearnerEngagementTimelineNotFoundError,
    LearnerNotFoundError,
    ParameterValueError,
)
from analytics_data_api.v0.models import (
    ModuleEngagement,
    ModuleEngagementMetricRanges,
    RosterEntry
)
from analytics_data_api.v0.serializers import (
    CourseLearnerMetadataSerializer,
    ElasticsearchDSLSearchSerializer,
    EngagementDaySerializer,
    LearnerSerializer,
)
from analytics_data_api.v0.views import CourseViewMixin
from analytics_data_api.v0.views.utils import split_query_argument

class LearnerView(CourseViewMixin, generics.RetrieveAPIView):
    """
    Get a particular student's data for a particular course.

    **Example Request**

        GET /api/v0/learners/{username}/?course_id={course_id}

    **Response Values**

        Returns the learner metadata and engagement data:

            * username: User's username.
            * enrollment_mode: Enrollment mode (e.g. "honor).
            * name: User's full name.
            * email: User's email.
            * segments: Classification for this course based on engagement, (e.g. "has_potential").
            * engagements: Summary of engagement events for a time span.
                * videos_viewed: Number of times a video was played.
                * problems_completed: Unique number of problems completed.
                * problems_attempted: Unique number of problems attempted.
                * discussions_contributed: Number of discussions (e.g. forum posts).

    **Parameters**

        You can specify the course ID for which you want data.

        course_id -- The course within which user data is requested.

    """
    serializer_class = LearnerSerializer
    username = None
    lookup_field = 'username'

    def get(self, request, *args, **kwargs):
        self.username = self.kwargs.get('username')
        return super(LearnerView, self).get(request, *args, **kwargs)

    def get_queryset(self):
        return RosterEntry.get_course_user(self.course_id, self.username)

    def get_object(self, queryset=None):
        queryset = self.get_queryset()
        if len(queryset) == 1:
            return queryset[0]
        raise LearnerNotFoundError(username=self.username, course_id=self.course_id)

class LearnerListView(CourseViewMixin, generics.ListAPIView):
    """
    Get a paginated list of student data for a particular course.

    **Example Request**

        GET /api/v0/learners/?course_id={course_id}

    **Response Values**

        Returns a paginated list of learner metadata and engagement data.
        Pagination data is returned in the top-level of the returned JSON
        object:

            * count: The number of learners matching the query.
            * page: The current one-indexed page number.
            * next: A hyperlink to the next page if one exists, otherwise null.
            * previous: A hyperlink to the previous page if one exists, otherwise null.

        The 'results' key in the returned JSON object maps to an array of
        learners which contains at most a full page's worth of learners.  Each
        learner is a JSON object containing the following keys:

            * username: User's username.
            * enrollment_mode: Enrollment mode (e.g. "honor).
            * name: User's full name.
            * email: User's email.
            * segments: Classification for this course based on engagement, (e.g. "has_potential").
            * engagements: Summary of engagement events for a time span.
                * videos_viewed: Number of times a video was played.
                * problems_completed: Unique number of problems completed.
                * problems_attempted: Unique number of problems attempted.
                * discussions_contributed: Number of discussions (e.g. forum posts).

    **Parameters**

        You can filter the list of learners by course ID and other parameters
        such as enrollment mode and text search.  You can also control the
        page size and page number of the response, as well as sort the learners
        in the response.

        course_id -- The course within which user data is requested.
        page -- The page of results which should be returned.
        page_size -- The maximum number of results which should be returned per page.
        text_search -- A string to search over the name, username, and email of learners.
        segments -- A comma-separated string of segments to which
            learners should belong.  Semgents are "OR"-ed together.
            Cannot use in combination with `ignore_segments`
            argument.
        ignore_segments -- A comma-separated string of segments to
            which learners should NOT belong.  Semgents are "OR"-ed
            together.  Cannot use in combination with `segments`
            argument.
        cohort -- The cohort to which all returned learners must
            belong.
        enrollment_mode -- The enrollment mode to which all returned
            learners must belong.
        order_by -- The field for sorting the response.  Defaults to 'username'.
        sort_order -- The sort direction.  One of 'asc' or 'desc'.
            Defaults to 'asc'.

    """
    serializer_class = LearnerSerializer
    pagination_serializer_class = ElasticsearchDSLSearchSerializer
    paginate_by_param = 'page_size'
    paginate_by = learner.LEARNER_API_DEFAULT_LIST_PAGE_SIZE
    max_paginate_by = 100  # TODO -- tweak during load testing

    def _validate_query_params(self):
        """Validates various querystring parameters."""
        query_params = self.request.QUERY_PARAMS
        page = query_params.get('page')
        if page:
            try:
                page = int(page)
            except ValueError:
                raise ParameterValueError('Page must be an integer')
            finally:
                if page < 1:
                    raise ParameterValueError(
                        'Page numbers are one-indexed, therefore the page value must be greater than 0'
                    )
        page_size = query_params.get('page_size')
        if page_size:
            try:
                page_size = int(page_size)
            except ValueError:
                raise ParameterValueError('Page size must be an integer')
            finally:
                if page_size > self.max_paginate_by or page_size < 1:
                    raise ParameterValueError('Page size must be in the range [1, {}]'.format(self.max_paginate_by))

    def get_queryset(self):
        """
        Fetches the user list from elasticsearch.  Note that an
        elasticsearch_dsl `Search` object is returned, not an actual
        queryset.
        """
        self._validate_query_params()
        query_params = self.request.QUERY_PARAMS

        order_by = query_params.get('order_by')
        sort_order = query_params.get('sort_order')
        sort_policies = [{
            'order_by': order_by,
            'sort_order': sort_order
        }]

        # Ordering by problem_attempts_per_completed can be ambiguous because
        # values could be infinite (e.g. divide by zero) if no problems were completed.
        # Instead, secondary sorting by attempt_ratio_order will produce a sensible ordering.
        if order_by == 'problem_attempts_per_completed':
            sort_policies.append({
                'order_by': 'attempt_ratio_order',
                'sort_order': 'asc' if sort_order == 'desc' else 'desc'
            })

        params = {
            'segments': split_query_argument(query_params.get('segments')),
            'ignore_segments': split_query_argument(query_params.get('ignore_segments')),
            'cohort': query_params.get('cohort'),
            'enrollment_mode': query_params.get('enrollment_mode'),
            'text_search': query_params.get('text_search'),
            'sort_policies': sort_policies,
        }
        # Remove None values from `params` so that we don't overwrite default
        # parameter values in `get_users_in_course`.
        params = {key: val for key, val in params.items() if val is not None}
        try:
            return RosterEntry.get_users_in_course(self.course_id, **params)
        except ValueError as e:
            raise ParameterValueError(e.message)

class EngagementTimelineView(CourseViewMixin, generics.ListAPIView):
    """
    Get a particular learner's engagement timeline for a particular course.  Days
    without data will not be returned.

    **Example Request**

        GET /api/v0/engagement_timeline/{username}/?course_id={course_id}

    **Response Values**

        Returns the engagement timeline.

            * days: Array of the learner's daily engagement timeline.
                * problems_attempted: Unique number of unique problems attempted.
                * problems_completed: Unique number of problems completed.
                * discussions_contributed: Number of discussions participated in (e.g. forum posts)
                * videos_viewed: Number of videos watched.
                * problem_attempts_per_completed: TBD

    **Parameters**

        You can specify course ID for which you want data.

        course_id -- The course within which user data is requested.

    """
    serializer_class = EngagementDaySerializer
    username = None
    lookup_field = 'username'

    def list(self, request, *args, **kwargs):
        response = super(EngagementTimelineView, self).list(request, *args, **kwargs)
        if response.status_code == status.HTTP_200_OK:
            response.data = {'days': response.data}
        return response

    def get(self, request, *args, **kwargs):
        self.username = self.kwargs.get('username')
        return super(EngagementTimelineView, self).get(request, *args, **kwargs)

    def get_queryset(self):
        queryset = ModuleEngagement.objects.get_timelines(self.course_id, self.username)
        if len(queryset) == 0:
            raise LearnerEngagementTimelineNotFoundError(username=self.username, course_id=self.course_id)
        return queryset

class CourseLearnerMetadata(CourseViewMixin, generics.RetrieveAPIView):
    """
    Get metadata on learners within a course. Includes data on segments,
    cohorts, enrollment modes, and an engagement rubric.

    **Example Request**

        GET /api/v0/course_learner_metadata/{course_id}/

    **Response Values**

        Returns a JSON object with the following keys:

            * cohorts: An object mapping the names of cohorts in the course to
              the number of students belonging to those cohorts.
            * segments: An object mapping the names of segments in the course
              to the number of students belonging to those segments.  The
              current set of segments are: "highly_engaged", "disengaging",
              "struggling", "inactive", and "unenrolled".
            * enrollment_modes: An object mapping the names of enrollment modes
              in the course to the number of students belonging to those
              enrollment modes.  Examples include "honor" and "verified".
            * engagement_ranges: An object containing ranges of learner
              engagement with the courseware.  Each range has 'below_average',
              'average', and 'above_average' keys which map to two-element
              arrays of which the first element is the lower bound (inclusive)
              and the second element is the upper bound (exclusive).  It has
              the following keys:
                * date_range: The time duration for which this data applies
                * problems_attempted: engagement ranges for the number of
                  problems attempted in the date range.
                * problems_completed: engagement ranges for the number of
                  problems completed in the date range.
                * problem_attempts_per_completed: engagement ranges for the
                  number of problem attempts per completed problem in the date
                  range.
                * discussions_contributed: engagement ranges for the number of
                  discussions contributed in the date range.

    """
    serializer_class = CourseLearnerMetadataSerializer

    def get_object(self, queryset=None):
        # Because we're serializing data from both Elasticsearch and MySQL into
        # the same JSON object, we have to pass both sources of data in a dict
        # to our custom course metadata serializer.
        return {
            'es_data': RosterEntry.get_course_metadata(self.course_id),
            'engagement_ranges': ModuleEngagementMetricRanges.objects.filter(course_id=self.course_id)
        }
<commit_msg>Copy editing pass through the descriptive strings.<commit_after>"""
API methods for module level data.
"""
from rest_framework import generics, status

from analytics_data_api.constants import (
    learner
)
from analytics_data_api.v0.exceptions import (
    LearnerEngagementTimelineNotFoundError,
    LearnerNotFoundError,
    ParameterValueError,
)
from analytics_data_api.v0.models import (
    ModuleEngagement,
    ModuleEngagementMetricRanges,
    RosterEntry
)
from analytics_data_api.v0.serializers import (
    CourseLearnerMetadataSerializer,
    ElasticsearchDSLSearchSerializer,
    EngagementDaySerializer,
    LearnerSerializer,
)
from analytics_data_api.v0.views import CourseViewMixin
from analytics_data_api.v0.views.utils import split_query_argument

class LearnerView(CourseViewMixin, generics.RetrieveAPIView):
    """
    Get data for a particular learner in a particular course.

    **Example Request**

        GET /api/v0/learners/{username}/?course_id={course_id}

    **Response Values**

        Returns metadata and engagement data for the learner in JSON format.

            * username: The username of the enrolled learner.
            * enrollment_mode: The learner's selected learning track (for
              example, "audit" or "verified").
            * name: The learner's full name.
            * email: The learner's email address.
            * segments: Classification, based on engagement, of this learner's
              work in this course (for example, "highly_engaged" or
              "struggling").
            * engagements: Summary of engagement events for a time span.
                * videos_viewed: Number of times any course video was played.
                * problems_completed: Number of unique problems the learner
                  answered correctly.
                * problems_attempted: Number of unique problems attempted.
                  This is a count of the individual problems the learner
                  tried. Each problem in a course can increment this count by
                  a maximum of 1.
                * discussions_contributed: Number of posts, responses, or
                  comments the learner contributed to course discussions.

    **Parameters**

        You can specify the course ID for which you want data.

        course_id -- The course identifier for which user data is requested.
        For example, edX/DemoX/Demo_Course.

    """
    serializer_class = LearnerSerializer
    username = None
    lookup_field = 'username'

    def get(self, request, *args, **kwargs):
        self.username = self.kwargs.get('username')
        return super(LearnerView, self).get(request, *args, **kwargs)

    def get_queryset(self):
        return RosterEntry.get_course_user(self.course_id, self.username)

    def get_object(self, queryset=None):
        queryset = self.get_queryset()
        if len(queryset) == 1:
            return queryset[0]
        raise LearnerNotFoundError(username=self.username, course_id=self.course_id)

class LearnerListView(CourseViewMixin, generics.ListAPIView):
    """
    Get a paginated list of data for all learners in a course.

    **Example Request**

        GET /api/v0/learners/?course_id={course_id}

    **Response Values**

        Returns a paginated list of learner metadata and engagement data.
        Pagination data is returned in the top level of the returned JSON
        object.

            * count: The number of learners that match the query.
            * page: The current one-indexed page number.
            * next: A hyperlink to the next page if one exists, otherwise null.
            * previous: A hyperlink to the previous page if one exists,
              otherwise null.

        The 'results' key in the returned object maps to an array of
        learners that contains, at most, a full page's worth of learners. For
        each learner there is an object that contains the following keys.

            * username: The username of an enrolled learner.
            * enrollment_mode: The learner's selected learning track (for
              example, "audit" or "verified").
            * name: The learner's full name.
            * email: The learner's email address.
            * segments: Classification, based on engagement, of each learner's
              work in this course (for example, "highly_engaged" or
              "struggling").
            * engagements: Summary of engagement events for a time span.
                * videos_viewed: Number of times any course video was played.
                * problems_completed: Number of unique problems the learner
                  answered correctly.
                * problems_attempted: Number of unique problems attempted.
                  This is a count of the individual problems the learner
                  tried. Each problem in a course can increment this count by
                  a maximum of 1.
                * discussions_contributed: Number of posts, responses, or
                  comments the learner contributed to course discussions.

    **Parameters**

        You can filter the list of learners by course ID and by other
        parameters, including enrollment mode and text search. You can also
        control the page size and page number of the response, as well as sort
        the learners in the response.

        course_id -- The course identifier for which user data is requested.
            For example, edX/DemoX/Demo_Course.
        page -- The page of results that should be returned.
        page_size -- The maximum number of results to return per page.
        text_search -- An alphanumeric string that is used to search name,
            username, and email address values to find learners.
        segments -- A comma-separated list of segment names that is used
            to select learners. Only learners who are categorized in at least
            one of the segments are returned. Cannot be used in combination
            with the `ignore_segments` argument.
        ignore_segments -- A comma-separated list of segment names that is
            used to exclude learners. Only learners who are NOT categorized
            in any of the segments are returned. Cannot be used in combination
            with the `segments` argument.
        cohort -- The cohort to which all returned learners must
            belong.
        enrollment_mode -- The learning track to which all returned
            learners must belong.
        order_by -- The field for sorting the response. Defaults to 'username'.
        sort_order -- The sort direction.  One of 'asc' (ascending) or 'desc'
            (descending). Defaults to 'asc'.

    """
    serializer_class = LearnerSerializer
    pagination_serializer_class = ElasticsearchDSLSearchSerializer
    paginate_by_param = 'page_size'
    paginate_by = learner.LEARNER_API_DEFAULT_LIST_PAGE_SIZE
    max_paginate_by = 100  # TODO -- tweak during load testing

    def _validate_query_params(self):
        """Validates various querystring parameters."""
        query_params = self.request.QUERY_PARAMS
        page = query_params.get('page')
        if page:
            try:
                page = int(page)
            except ValueError:
                raise ParameterValueError('Page must be an integer')
            finally:
                if page < 1:
                    raise ParameterValueError(
                        'Page numbers are one-indexed, therefore the page value must be greater than 0'
                    )
        page_size = query_params.get('page_size')
        if page_size:
            try:
                page_size = int(page_size)
            except ValueError:
                raise ParameterValueError('Page size must be an integer')
            finally:
                if page_size > self.max_paginate_by or page_size < 1:
                    raise ParameterValueError('Page size must be in the range [1, {}]'.format(self.max_paginate_by))

    def get_queryset(self):
        """
        Fetches the user list from elasticsearch.  Note that an
        elasticsearch_dsl `Search` object is returned, not an actual
        queryset.
        """
        self._validate_query_params()
        query_params = self.request.QUERY_PARAMS

        order_by = query_params.get('order_by')
        sort_order = query_params.get('sort_order')
        sort_policies = [{
            'order_by': order_by,
            'sort_order': sort_order
        }]

        # Ordering by problem_attempts_per_completed can be ambiguous because
        # values could be infinite (e.g. divide by zero) if no problems were completed.
        # Instead, secondary sorting by attempt_ratio_order will produce a sensible ordering.
        if order_by == 'problem_attempts_per_completed':
            sort_policies.append({
                'order_by': 'attempt_ratio_order',
                'sort_order': 'asc' if sort_order == 'desc' else 'desc'
            })

        params = {
            'segments': split_query_argument(query_params.get('segments')),
            'ignore_segments': split_query_argument(query_params.get('ignore_segments')),
            'cohort': query_params.get('cohort'),
            'enrollment_mode': query_params.get('enrollment_mode'),
            'text_search': query_params.get('text_search'),
            'sort_policies': sort_policies,
        }
        # Remove None values from `params` so that we don't overwrite default
        # parameter values in `get_users_in_course`.
        params = {key: val for key, val in params.items() if val is not None}
        try:
            return RosterEntry.get_users_in_course(self.course_id, **params)
        except ValueError as e:
            raise ParameterValueError(e.message)

class EngagementTimelineView(CourseViewMixin, generics.ListAPIView):
    """
    Get a particular learner's engagement timeline for a particular course.
    Days without data are not returned.

    **Example Request**

        GET /api/v0/engagement_timeline/{username}/?course_id={course_id}

    **Response Values**

        Returns the engagement timeline in an array.

            * days: An array of the learner's daily engagement timeline.
                * problems_attempted: Number of unique problems attempted.
                  This is a count of the individual problems the learner
                  tried. Each problem in a course can increment this count by
                  a maximum of 1.
                * problems_completed: Number of unique problems the learner
                  answered correctly.
                * discussions_contributed: Number of posts, responses, or
                  comments the learner contributed to course discussions.
                * videos_viewed: Number of times any course video was played.
                * problem_attempts_per_completed: TBD

    **Parameters**

        You can specify the course ID for which you want data.

        course_id -- The course identifier for which user data is requested.
        For example, edX/DemoX/Demo_Course.

    """
    serializer_class = EngagementDaySerializer
    username = None
    lookup_field = 'username'

    def list(self, request, *args, **kwargs):
        response = super(EngagementTimelineView, self).list(request, *args, **kwargs)
        if response.status_code == status.HTTP_200_OK:
            response.data = {'days': response.data}
        return response

    def get(self, request, *args, **kwargs):
        self.username = self.kwargs.get('username')
        return super(EngagementTimelineView, self).get(request, *args, **kwargs)

    def get_queryset(self):
        queryset = ModuleEngagement.objects.get_timelines(self.course_id, self.username)
        if len(queryset) == 0:
            raise LearnerEngagementTimelineNotFoundError(username=self.username, course_id=self.course_id)
        return queryset

class CourseLearnerMetadata(CourseViewMixin, generics.RetrieveAPIView):
    """
    Get metadata about the learners in a course. Includes data on segments,
    cohorts, and enrollment modes. Also includes an engagement rubric.

    **Example Request**

        GET /api/v0/course_learner_metadata/{course_id}/

    **Response Values**

        Returns an object with the following keys.

            * cohorts: An object that maps the names of cohorts in the course
              to the number of learners belonging to those cohorts.
            * segments: An object that maps the names of segments in the course
              to the number of learners belonging to those segments. The
              current set of segments is "highly_engaged", "disengaging",
              "struggling", "inactive", and "unenrolled".
            * enrollment_modes: An object that maps the names of learning
              tracks in the course to the number of learners belonging to those
              tracks. Examples include "audit" and "verified".
            * engagement_ranges: An object containing ranges of learner
              engagement with the courseware. Each range has 'below_average',
              'average', and 'above_average' keys. These keys map to
              two-element arrays, in which the first element is the lower bound
              (inclusive) and the second element is the upper bound
              (exclusive). It has the following keys.
                * date_range: The time period to which this data applies.
                * problems_attempted: Engagement ranges for the number of
                  unique problems tried in the date range.
                * problems_completed: Engagement ranges for the number of
                  unique problems answered correctly in the date range.
                * problem_attempts_per_completed: Engagement ranges for the
                  number of problem attempts per completed problem in the date
                  range.
                * discussions_contributed: Engagement ranges for the number of
                  times learners participated in discussions in the date range.

    """
    serializer_class = CourseLearnerMetadataSerializer

    def get_object(self, queryset=None):
        # Because we're serializing data from both Elasticsearch and MySQL into
        # the same JSON object, we have to pass both sources of data in a dict
        # to our custom course metadata serializer.
        return {
            'es_data': RosterEntry.get_course_metadata(self.course_id),
            'engagement_ranges': ModuleEngagementMetricRanges.objects.filter(course_id=self.course_id)
        }
<|endoftext|>


<commit_before>"""
Concrete CollectorStrategy classes for the GitHub built-in module
"""
import re

from anchorhub.collector import CollectorStrategy
import anchorhub.builtin.regex.markdown as mdrx

class MarkdownATXCollectorStrategy(CollectorStrategy):
    """
    Concrete collector strategy used to parse ATX style headers that have
    AnchorHub tags specified

    ATX style headers begin with 1-5 hash '#' characters, and then use
    the rest of the line to specify the text of the header. For example:

    # This is an ATX header!
    ### So is this!
    """
    def __init__(self, opts):
        """
        Initializes the object to utilize the AnchorHub tag wrapper
        as specified.

        :param opts: Namespace with the attribute 'wrapper_pattern',
            typically obtained through command-line argument parsing
        """
        self._open = opts.open
        self._close = opts.close
        self._header_pattern = r"^#+ .+" + opts.wrapper_regex + r"\s*$"
        self._regex = re.compile(self._header_pattern, re.UNICODE)

    def test(self, file_lines, index):
        """
        Is this line an ATX header with an AnchorHub tag specified? Return
        True if it is.

        :param file_lines: List of strings corresponding to lines in a text file
        :param index: index of file_lines corresponding to the current line
        :return: True if the line in file_lines at index is an ATX header
            with an AnchorHub tag declared. False otherwise
        """
        return self._regex.match(file_lines[index])

    def get(self, file_lines, index):
        """
        Extract the specified AnchorHub tag, as well as the portion of the
        line that should be converted from the ATX style Markdown header.

        :param file_lines: List of strings corresponding to lines in a text file
        :param index: index of file_lines corresponding to the current line
        :return: [tag, string] - tag: the extracted AnchorHub tag. string -
            the portion of the header that should be converted into an anchor
        """
        line = file_lines[index]
        start_index = line.find('# ') + 2   # Start index for header text
        start_tag = line.rfind(self._open)  # Start index of AnchorHub tag
        end_tag = line.rfind(self._close)   # End index of AnchorHub tag

        # The magic '+1' below knocks out the hash '#' character from extraction
        tag = line[start_tag + len(self._open) + 1:end_tag]
        string = line[start_index:start_tag]
        return [tag, string]

class MarkdownSetextCollectorStrategy(CollectorStrategy):
    """
    Concrete collector strategy used to parse Setext style headers that have
    AnchorHub tags specified

    Setext style headers are 'underlined' with a line comprised entirely of
    equals-signs or hyphens. These are valid Setext headers:

        This is an H1 header
        ====================

        This is an H2 header
        -

    Note that the underlining characters are mutually exclusive. This is
    _not_ a valid header:

        This is not a header
        ====----
    """
    def __init__(self, opts):
        """
        Initializes the object to utilize the AnchorHub tag wrapper
        as specified.

        :param opts: Namespace with the attribute 'wrapper_pattern',
            typically obtained through command-line argument parsing
        """
        self._open = opts.open
        self._close = opts.close
        self._header_pattern = opts.wrapper_regex + r"\s*$"
        self._header_regex = re.compile(self._header_pattern, re.UNICODE)
        self._underline_regex = re.compile(mdrx.setext_underline)

    def test(self, file_lines, index):
        """
         This is line a Setext header with an AnchorHub tag specified? Return
         True if it is.

         file_lines and index _must_ be provided to this function, or it will
         throw a ValueError

         :param file_lines:
         :param index:
         :return: True if the line in line_files at index is a Setext header
             with an AnchorHub tag declared. False otherwise
         """
        if file_lines is None: raise ValueError("file_lines list must be "
                                                "provided to test() method in "
                                                "MarkdownSetextCollectorStrategy")
        if index is None: raise ValueError("index must be provided to test() "
                                           "method in "
                                           "MarkdownSetextCollectorStrategy")

        # If index is at len(file_lines) - 1, it's the last line in file
        # Since it needs an underline, cannot be a header
        index_in_bounds = index < len(file_lines) - 1

        if (self._header_regex.search(file_lines[index]) and
                self._underline_regex.match(file_lines[index+1]) and
                index_in_bounds):
            return True
        else:
            return False

    def get(self, file_lines, index):
        """
        Extract the specified AnchorHub tag, as well as the portion of the
        line that should be converted from the ATX style Markdown header.

        :param file_lines: List of strings corresponding to lines in a text file
        :param index: index of file_lines corresponding to the current line
        :return: [tag, string] - tag: the extracted AnchorHub tag. string -
            the portion of the header that should be converted into an anchor
        """
        line = file_lines[index]
        start_tag = line.rfind(self._open)  # Start index of AnchorHub tag
        end_tag = line.rfind(self._close)   # End index of AnchorHub tag

        # The magic '+1' below knocks out the hash '#' character from extraction
        tag = line[start_tag + len(self._open) + 1:end_tag]
        string = line[:start_tag]
        return [tag, string]

<commit_msg>Fix mis-ordered booleans for short-circuiting<commit_after>"""
Concrete CollectorStrategy classes for the GitHub built-in module
"""
import re

from anchorhub.collector import CollectorStrategy
import anchorhub.builtin.regex.markdown as mdrx

class MarkdownATXCollectorStrategy(CollectorStrategy):
    """
    Concrete collector strategy used to parse ATX style headers that have
    AnchorHub tags specified

    ATX style headers begin with 1-5 hash '#' characters, and then use
    the rest of the line to specify the text of the header. For example:

    # This is an ATX header!
    ### So is this!
    """
    def __init__(self, opts):
        """
        Initializes the object to utilize the AnchorHub tag wrapper
        as specified.

        :param opts: Namespace with the attribute 'wrapper_pattern',
            typically obtained through command-line argument parsing
        """
        self._open = opts.open
        self._close = opts.close
        self._header_pattern = r"^#+ .+" + opts.wrapper_regex + r"\s*$"
        self._regex = re.compile(self._header_pattern, re.UNICODE)

    def test(self, file_lines, index):
        """
        Is this line an ATX header with an AnchorHub tag specified? Return
        True if it is.

        :param file_lines: List of strings corresponding to lines in a text file
        :param index: index of file_lines corresponding to the current line
        :return: True if the line in file_lines at index is an ATX header
            with an AnchorHub tag declared. False otherwise
        """
        return self._regex.match(file_lines[index])

    def get(self, file_lines, index):
        """
        Extract the specified AnchorHub tag, as well as the portion of the
        line that should be converted from the ATX style Markdown header.

        :param file_lines: List of strings corresponding to lines in a text file
        :param index: index of file_lines corresponding to the current line
        :return: [tag, string] - tag: the extracted AnchorHub tag. string -
            the portion of the header that should be converted into an anchor
        """
        line = file_lines[index]
        start_index = line.find('# ') + 2   # Start index for header text
        start_tag = line.rfind(self._open)  # Start index of AnchorHub tag
        end_tag = line.rfind(self._close)   # End index of AnchorHub tag

        # The magic '+1' below knocks out the hash '#' character from extraction
        tag = line[start_tag + len(self._open) + 1:end_tag]
        string = line[start_index:start_tag]
        return [tag, string]

class MarkdownSetextCollectorStrategy(CollectorStrategy):
    """
    Concrete collector strategy used to parse Setext style headers that have
    AnchorHub tags specified

    Setext style headers are 'underlined' with a line comprised entirely of
    equals-signs or hyphens. These are valid Setext headers:

        This is an H1 header
        ====================

        This is an H2 header
        -

    Note that the underlining characters are mutually exclusive. This is
    _not_ a valid header:

        This is not a header
        ====----
    """
    def __init__(self, opts):
        """
        Initializes the object to utilize the AnchorHub tag wrapper
        as specified.

        :param opts: Namespace with the attribute 'wrapper_pattern',
            typically obtained through command-line argument parsing
        """
        self._open = opts.open
        self._close = opts.close
        self._header_pattern = opts.wrapper_regex + r"\s*$"
        self._header_regex = re.compile(self._header_pattern, re.UNICODE)
        self._underline_regex = re.compile(mdrx.setext_underline)

    def test(self, file_lines, index):
        """
         This is line a Setext header with an AnchorHub tag specified? Return
         True if it is.

         file_lines and index _must_ be provided to this function, or it will
         throw a ValueError

         :param file_lines:
         :param index:
         :return: True if the line in line_files at index is a Setext header
             with an AnchorHub tag declared. False otherwise
         """
        if file_lines is None: raise ValueError("file_lines list must be "
                                                "provided to test() method in "
                                                "MarkdownSetextCollectorStrategy")
        if index is None: raise ValueError("index must be provided to test() "
                                           "method in "
                                           "MarkdownSetextCollectorStrategy")

        # If index is at len(file_lines) - 1, it's the last line in file
        # Since it needs an underline, cannot be a header
        index_in_bounds = index < len(file_lines) - 1

        if (index_in_bounds and self._header_regex.search(file_lines[index]) and
                self._underline_regex.match(file_lines[index+1])):
            return True
        else:
            return False

    def get(self, file_lines, index):
        """
        Extract the specified AnchorHub tag, as well as the portion of the
        line that should be converted from the ATX style Markdown header.

        :param file_lines: List of strings corresponding to lines in a text file
        :param index: index of file_lines corresponding to the current line
        :return: [tag, string] - tag: the extracted AnchorHub tag. string -
            the portion of the header that should be converted into an anchor
        """
        line = file_lines[index]
        start_tag = line.rfind(self._open)  # Start index of AnchorHub tag
        end_tag = line.rfind(self._close)   # End index of AnchorHub tag

        # The magic '+1' below knocks out the hash '#' character from extraction
        tag = line[start_tag + len(self._open) + 1:end_tag]
        string = line[:start_tag]
        return [tag, string]

<|endoftext|>


<commit_before>#!/usr/bin/python

import os
import sys

ANDROID_SDK_PATH = "/opt/android-sdk-linux"
ANDROID_NDK_PATH = None
INSTALL_DIRECTORY = None
CLASS_PATH = None
TMP_HEADER_PATH="tmp_include"
HEADER_EXTS = set(['h', 'hpp'])
SYS_INCLUDES = ["platforms/android-8/arch-arm/usr/include", "sources/cxx-stl/gnu-libstdc++/include", "sources/cxx-stl/gnu-libstdc++/libs/armeabi/include"]

PROJECT_NAME = "OpenCV-branch"
TARGET_LIBS = ["libopencv_java.so"]
ARCH = "armeabi"
GCC_OPTIONS = "-fpermissive"
EXCLUDE_HEADERS = set(["hdf5.h", "eigen.hpp", "cxeigen.hpp"]);

def FindClasses(root, prefix):
    classes = []
    if ("" != prefix):
	prefix = prefix + "."
    for path in os.listdir(root):
	currentPath = os.path.join(root, path)
	if (os.path.isdir(currentPath)):
	    classes += FindClasses(currentPath, prefix + path)
	else:
	    name = str.split(path, ".")[0]
	    ext = str.split(path, ".")[1]
	    if (ext == "class"):
		#print("class: %s" % (prefix + name))
		classes.append(prefix+name)
    return classes

def FindHeaders(root):
    headers = []
    for path in os.listdir(root):
	currentPath = os.path.join(root, path)
	if (os.path.isdir(currentPath)):
	    headers += FindHeaders(currentPath)
	else:
	    ext = str.split(path, ".")[-1]
	    #print("%s: \"%s\"" % (currentPath, ext))
	    if (ext in HEADER_EXTS):
		#print("Added as header file")
		if (path not in EXCLUDE_HEADERS):
		    headers.append(currentPath)
    return headers

if (len(sys.argv) < 3):
    print("Error: Invalid command line arguments")
    exit(-1)

INSTALL_DIRECTORY = sys.argv[1]
PROJECT_NAME = sys.argv[2]

CLASS_PATH = os.path.join(INSTALL_DIRECTORY, "sdk/java/bin/classes")
if (not os.path.exists(CLASS_PATH)):
    print("Error: no java classes found in \"%s\"", CLASS_PATH)
    exit(-2)

if (os.environ.has_key("NDK_ROOT")):
    ANDROID_NDK_PATH = os.environ["NDK_ROOT"];
    print("Using Android NDK from NDK_ROOT (\"%s\")" % ANDROID_NDK_PATH)

if (not ANDROID_NDK_PATH):
    pipe = os.popen("which ndk-build")
    tmp = str.strip(pipe.readline(), "\n")
    while(not tmp):
	tmp = str.strip(pipe.readline(), "\n")
    pipe.close()
    ANDROID_NDK_PATH = os.path.split(tmp)[0]
    print("Using Android NDK from PATH (\"%s\")" % ANDROID_NDK_PATH)

print("Using Android SDK from \"%s\"" % ANDROID_SDK_PATH)

outputFileName = PROJECT_NAME + ".xml"
try:
    outputFile = open(outputFileName, "w")
except:
    print("Error: Cannot open output file \"%s\" for writing" % outputFileName)

allJavaClasses = FindClasses(CLASS_PATH, "")
if (not allJavaClasses):
    print("Error: No Java classes found :(")
    exit(-1)

if (not os.path.exists(TMP_HEADER_PATH)):
    os.makedirs(os.path.join(os.getcwd(), TMP_HEADER_PATH))

print("Generating JNI headers for Java API ...")
AndroidJavaDeps = os.path.join(ANDROID_SDK_PATH, "platforms/android-11/android.jar")
for currentClass in allJavaClasses:
    os.system("javah -d %s -classpath %s:%s %s" % (TMP_HEADER_PATH, CLASS_PATH, AndroidJavaDeps, currentClass))

print("Building JNI headers list ...")
jniHeaders = FindHeaders(TMP_HEADER_PATH)
#print(jniHeaders)

print("Building Native OpenCV header list ...")
cHeaders = FindHeaders(os.path.join(INSTALL_DIRECTORY, "sdk/native/jni/include/opencv"))
cppHeaders = FindHeaders(os.path.join(INSTALL_DIRECTORY, "sdk/native/jni/include/opencv2"))
#print(cHeaders)
#print(cppHeaders)

print("Writing config file ...")
outputFile.write("<descriptor>\n\n<version>\n\t%s\n</version>\n\n<headers>\n" % PROJECT_NAME)
outputFile.write("\t"   + "\n\t".join(cHeaders))
outputFile.write("\n\t" + "\n\t".join(cppHeaders))
outputFile.write("\n\t" + "\n\t".join(jniHeaders))
outputFile.write("\n</headers>\n\n")

includes = []
for inc in SYS_INCLUDES:
    includes.append(os.path.join(ANDROID_NDK_PATH, inc))

outputFile.write("<include_paths>\n\tOpenCV-2.4.2-branch/sdk/native/jni/include\n\tOpenCV-2.4.2-branch/sdk/native/jni/include/opencv\n\tOpenCV-2.4.2-branch/sdk/native/jni/include/opencv2\n")

outputFile.write("\t%s\n</include_paths>\n\n" % "\n\t".join(includes))

libraries = []
for lib in TARGET_LIBS:
    libraries.append(os.path.join(INSTALL_DIRECTORY, "sdk/native/libs", ARCH, lib))

outputFile.write("<libs>\n\t%s\n</libs>\n\n" % "\n\t".join(libraries))
outputFile.write("<gcc_options>\n\t%s\n</gcc_options>\n\n</descriptor>" % GCC_OPTIONS)

print("done!")
<commit_msg>ABI Compatibility config generator bug fix.<commit_after>#!/usr/bin/python

import os
import sys

ANDROID_SDK_PATH = "/opt/android-sdk-linux"
ANDROID_NDK_PATH = None
INSTALL_DIRECTORY = None
CLASS_PATH = None
TMP_HEADER_PATH="tmp_include"
HEADER_EXTS = set(['h', 'hpp'])
SYS_INCLUDES = ["platforms/android-8/arch-arm/usr/include", "sources/cxx-stl/gnu-libstdc++/include", "sources/cxx-stl/gnu-libstdc++/libs/armeabi/include"]

PROJECT_NAME = "OpenCV-branch"
TARGET_LIBS = ["libopencv_java.so"]
ARCH = "armeabi"
GCC_OPTIONS = "-fpermissive"
EXCLUDE_HEADERS = set(["hdf5.h", "eigen.hpp", "cxeigen.hpp"]);

def FindClasses(root, prefix):
    classes = []
    if ("" != prefix):
	prefix = prefix + "."
    for path in os.listdir(root):
	currentPath = os.path.join(root, path)
	if (os.path.isdir(currentPath)):
	    classes += FindClasses(currentPath, prefix + path)
	else:
	    name = str.split(path, ".")[0]
	    ext = str.split(path, ".")[1]
	    if (ext == "class"):
		#print("class: %s" % (prefix + name))
		classes.append(prefix+name)
    return classes

def FindHeaders(root):
    headers = []
    for path in os.listdir(root):
	currentPath = os.path.join(root, path)
	if (os.path.isdir(currentPath)):
	    headers += FindHeaders(currentPath)
	else:
	    ext = str.split(path, ".")[-1]
	    #print("%s: \"%s\"" % (currentPath, ext))
	    if (ext in HEADER_EXTS):
		#print("Added as header file")
		if (path not in EXCLUDE_HEADERS):
		    headers.append(currentPath)
    return headers

if (len(sys.argv) < 3):
    print("Error: Invalid command line arguments")
    exit(-1)

INSTALL_DIRECTORY = sys.argv[1]
PROJECT_NAME = sys.argv[2]

CLASS_PATH = os.path.join(INSTALL_DIRECTORY, "sdk/java/bin/classes")
if (not os.path.exists(CLASS_PATH)):
    print("Error: no java classes found in \"%s\"" % CLASS_PATH)
    exit(-2)

if (os.environ.has_key("NDK_ROOT")):
    ANDROID_NDK_PATH = os.environ["NDK_ROOT"];
    print("Using Android NDK from NDK_ROOT (\"%s\")" % ANDROID_NDK_PATH)

if (not ANDROID_NDK_PATH):
    pipe = os.popen("which ndk-build")
    tmp = str.strip(pipe.readline(), "\n")
    while(not tmp):
	tmp = str.strip(pipe.readline(), "\n")
    pipe.close()
    ANDROID_NDK_PATH = os.path.split(tmp)[0]
    print("Using Android NDK from PATH (\"%s\")" % ANDROID_NDK_PATH)

print("Using Android SDK from \"%s\"" % ANDROID_SDK_PATH)

outputFileName = PROJECT_NAME + ".xml"
try:
    outputFile = open(outputFileName, "w")
except:
    print("Error: Cannot open output file \"%s\" for writing" % outputFileName)

allJavaClasses = FindClasses(CLASS_PATH, "")
if (not allJavaClasses):
    print("Error: No Java classes found :(")
    exit(-1)

if (not os.path.exists(TMP_HEADER_PATH)):
    os.makedirs(os.path.join(os.getcwd(), TMP_HEADER_PATH))

print("Generating JNI headers for Java API ...")
AndroidJavaDeps = os.path.join(ANDROID_SDK_PATH, "platforms/android-11/android.jar")
for currentClass in allJavaClasses:
    os.system("javah -d %s -classpath %s:%s %s" % (TMP_HEADER_PATH, CLASS_PATH, AndroidJavaDeps, currentClass))

print("Building JNI headers list ...")
jniHeaders = FindHeaders(TMP_HEADER_PATH)
#print(jniHeaders)

print("Building Native OpenCV header list ...")
cHeaders = FindHeaders(os.path.join(INSTALL_DIRECTORY, "sdk/native/jni/include/opencv"))
cppHeaders = FindHeaders(os.path.join(INSTALL_DIRECTORY, "sdk/native/jni/include/opencv2"))
#print(cHeaders)
#print(cppHeaders)

print("Writing config file ...")
outputFile.write("<descriptor>\n\n<version>\n\t%s\n</version>\n\n<headers>\n" % PROJECT_NAME)
outputFile.write("\t"   + "\n\t".join(cHeaders))
outputFile.write("\n\t" + "\n\t".join(cppHeaders))
outputFile.write("\n\t" + "\n\t".join(jniHeaders))
outputFile.write("\n</headers>\n\n")

includes = [os.path.join(INSTALL_DIRECTORY, "sdk", "native", "jni", "include"),
    os.path.join(INSTALL_DIRECTORY, "sdk", "native", "jni", "include", "opencv"),
    os.path.join(INSTALL_DIRECTORY, "sdk", "native", "jni", "include", "opencv2")]

for inc in SYS_INCLUDES:
    includes.append(os.path.join(ANDROID_NDK_PATH, inc))

outputFile.write("<include_paths>\n\t%s\n</include_paths>\n\n" % "\n\t".join(includes))

libraries = []
for lib in TARGET_LIBS:
    libraries.append(os.path.join(INSTALL_DIRECTORY, "sdk/native/libs", ARCH, lib))

outputFile.write("<libs>\n\t%s\n</libs>\n\n" % "\n\t".join(libraries))
outputFile.write("<gcc_options>\n\t%s\n</gcc_options>\n\n</descriptor>" % GCC_OPTIONS)

print("done!")
<|endoftext|>


<commit_before>#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
# Meta-info

Author: Nelson Brochado

Created: 01/08/2015

Updated: 19/09/2017

# Description

Checking recursively if a string is a palindrome, which is a string that reads
the same way forward and backward. For example, "anna" is a palindrome, whereas
"prime" is not.

# References

- https://en.wikipedia.org/wiki/Palindrome
"""

__all__ = ["is_palindrome"]

def _is_palindrome_aux(s: str, l: int, r: int) -> bool:
    """l is the index that indexes s from the left and, similarly, r indexes it
    from the right."""
    if l >= r:
        return True
    if s[l] == s[r]:
        return _is_palindrome_aux(s, l + 1, r - 1)
    else:
        return False

def is_palindrome(s: str) -> bool:
    """Returns true if the string s is a palindrome, false otherwise."""
    if len(s) <= 1:
        return True
    else:
        return _is_palindrome_aux(s, 0, len(s) - 1)
<commit_msg>iterative_is_palindrome function implemented<commit_after>#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
# Meta-info

Author: Nelson Brochado

Created: 01/08/2015

Updated: 15/03/2022

# Description

Checking recursively if a string is a palindrome, which is a string that reads
the same way forward and backward. For example, "anna" is a palindrome, whereas
"prime" is not.

# TODO

- This does not just apply to strings, but other sequences as well.

# References

- https://en.wikipedia.org/wiki/Palindrome
"""

__all__ = ["is_palindrome", "iterative_is_palindrome"]

def _is_palindrome_aux(s: str, l: int, r: int) -> bool:
    """l is the index that indexes s from the left and, similarly, r indexes it
    from the right."""
    if l >= r:
        return True
    if s[l] == s[r]:
        return _is_palindrome_aux(s, l + 1, r - 1)
    else:
        return False

def is_palindrome(s: str) -> bool:
    """Returns true if the string s is a palindrome, false otherwise."""
    if len(s) <= 1:
        return True
    else:
        return _is_palindrome_aux(s, 0, len(s) - 1)

def iterative_is_palindrome(s: str) -> bool:
    return s == s[::-1]

def test1():
    print(iterative_is_palindrome(""))

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


<commit_before>import boto.sts
import boto.ec2
from boto.exception import EC2ResponseError

from awacs.aws import Allow, Policy, Statement
from awacs import ec2

import iam_utils

customer_id = '540535557624'
role_name = 'easy-describe'
role_arn = 'arn:aws:iam::%s:role/%s' % (customer_id, role_name,)

sts_conn = boto.sts.STSConnection()

reduced_access_policy = Policy(
    Statement=[
        Statement(
            Effect=Allow,
            Action=[ec2.DescribeInstances],
            Resource=['*'],
        ),
    ]
)

def do_tags(token):
    # Using the temporary creds, run DescribeInstances
    ec2_conn = boto.ec2.connect_to_region('us-west-2', **token.to_boto_dict())
    instances = ec2_conn.get_only_instances()
    for instance in instances:
        print instance.id, instance.tags
        try:
            ec2_conn.create_tags(instance.id, {'Key': 'Value'})
        except EC2ResponseError, e:
            if e.error_code != 'UnauthorizedOperation':
                raise
            print 'UnauthorizedOperation! Cannot set tags.'

if __name__ == '__main__':
    # Get AWS to give us a set of temporary credentials that act inside of
    # `customer_id`'s account. Here we specify our reduced access policy which
    # does not specify the CreateTags operation. This means that when we call
    # do_tags it will fail.
    assumed_role = sts_conn.assume_role(
        role_arn, 'sec-403-demo', reduced_access_policy.to_json())
    token = iam_utils.Token(assumed_role.credentials)
    print '------------ Trying with reduced privileges'
    do_tags(token)

    # Here we assume role again, this time without the reduced access policy.
    # The tagging will work
    assumed_role = sts_conn.assume_role(role_arn, 'sec-403-demo')
    token = iam_utils.Token(assumed_role.credentials)
    print '------------ Trying with full privileges'
    do_tags(token)
<commit_msg>DateLessThan to have super short tokens<commit_after>import boto.sts
import boto.ec2
from boto.exception import EC2ResponseError
import datetime

from awacs.aws import Allow, Policy, Statement, DateLessThan, Condition
from awacs import ec2

import iam_utils

customer_id = '540535557624'
role_name = 'easy-describe'
role_arn = 'arn:aws:iam::%s:role/%s' % (customer_id, role_name,)

sts_conn = boto.sts.STSConnection()

now_plus_60 = datetime.datetime.utcnow() + datetime.timedelta(seconds=60)
big_iso_time = now_plus_60.isoformat()
iso_time = big_iso_time[:big_iso_time.rfind('.')] + 'Z'

reduced_access_policy = Policy(
    Statement=[
        Statement(
            Effect=Allow,
            Action=[ec2.DescribeInstances],
            Resource=['*'],
            # This reduction of policy knocks our creds down to only working
            # until iso_time. Kind of useful to work around the minimum
            # validity period of STS creds is 15 minutes.
            Condition=Condition(DateLessThan('aws:CurrentTime', iso_time)),
        ),
    ]
)

def do_tags(token):
    # Using the temporary creds, run DescribeInstances
    ec2_conn = boto.ec2.connect_to_region('us-west-2', **token.to_boto_dict())
    instances = ec2_conn.get_only_instances()
    for instance in instances:
        print instance.id, instance.tags
        try:
            ec2_conn.create_tags(instance.id, {'Key': 'Value'})
        except EC2ResponseError, e:
            if e.error_code != 'UnauthorizedOperation':
                raise
            print 'UnauthorizedOperation! Cannot set tags.'

if __name__ == '__main__':
    # Get AWS to give us a set of temporary credentials that act inside of
    # `customer_id`'s account. Here we specify our reduced access policy which
    # does not specify the CreateTags operation. This means that when we call
    # do_tags it will fail.
    assumed_role = sts_conn.assume_role(
        role_arn, 'sec-403-demo', reduced_access_policy.to_json())
    token = iam_utils.Token(assumed_role.credentials)
    print '------------ Trying with reduced privileges'
    do_tags(token)

    # Here we assume role again, this time without the reduced access policy.
    # The tagging will work
    assumed_role = sts_conn.assume_role(role_arn, 'sec-403-demo')
    token = iam_utils.Token(assumed_role.credentials)
    print '------------ Trying with full privileges'
    do_tags(token)
<|endoftext|>