<commit_before># -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import os
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import ARRAY, JSONB

engine = sa.create_engine(os.environ['OPENTRIALS_DATABASE_URL'])
metadata = sa.MetaData(bind=engine, reflect=True)

# Main tables

sa.Table('trial', metadata,

        # Columns

        # Meta
        sa.Column('uuid', sa.Text, primary_key=True),

        # General
        sa.Column('primary_id', sa.Text),
        sa.Column('secondary_ids', JSONB),
        sa.Column('registration_date', sa.Date),
        sa.Column('actualization_date', sa.Date),
        sa.Column('public_title', sa.Text),
        sa.Column('scientific_title', sa.Text),
        sa.Column('description', sa.Text),

        # Recruitment
        sa.Column('eligibility_criteria', sa.Text),
        sa.Column('target_sample_size', sa.Integer),
        sa.Column('first_enrollment_date', sa.Date),
        sa.Column('recruitment_status', sa.Text),

        # Study design
        sa.Column('study_type', sa.Text),
        sa.Column('study_design', sa.Text),
        sa.Column('study_phase', sa.Text),

        # Constraints

        # ...

)

sa.Table('trial_outcome', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('role', sa.Enum(
            'primary',
            'secondary',
            'other',
            name='trial_outcome_role',
        )),
        sa.Column('order', sa.Integer),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'role', 'order'),

)

sa.Table('trial_problem', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('problem_uuid', sa.Text, sa.ForeignKey('problem.uuid')),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'problem_uuid'),

)

sa.Table('trial_intervention', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('intervention_uuid', sa.Text, sa.ForeignKey('intervention.uuid')),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'intervention_uuid'),

)

sa.Table('trial_location', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('location_uuid', sa.Text, sa.ForeignKey('location.uuid')),
        sa.Column('role', sa.Enum(
            'recruitment_countries',
            'other',
            name='trial_location_role',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'location_uuid'),

)

sa.Table('trial_person', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('person_uuid', sa.Text, sa.ForeignKey('person.uuid')),
        sa.Column('role', sa.Enum(
            'public_queries',
            'scientific_queries',
            'other',
            name='trial_person_role',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'person_uuid'),

)

sa.Table('trial_organisation', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('organisation_uuid', sa.Text, sa.ForeignKey('organisation.uuid')),
        sa.Column('role', sa.Enum(
            'primary_sponsor',
            'sponsor',
            'funder',
            'other',
            name='trial_organisation_role',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'organisation_uuid'),

)

sa.Table('trial_publication', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('publication_uuid', sa.Text, sa.ForeignKey('publication.uuid')),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'publication_uuid'),

)

sa.Table('trial_document', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('document_uuid', sa.Text, sa.ForeignKey('document.uuid')),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'document_uuid'),

)

sa.Table('record', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('source', sa.Text, sa.ForeignKey('source.uuid')),
        sa.Column('data', JSONB),

)

sa.Table('publication', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('source', sa.Text, sa.ForeignKey('source.uuid')),
        sa.Column('name', sa.Text),

)

sa.Table('document', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('source', sa.Text, sa.ForeignKey('source.uuid')),
        sa.Column('name', sa.Text),

)

# Reference tables

sa.Table('source', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),
        sa.Column('type', sa.Enum(
            'register',
            'other',
            name='source_type',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.UniqueConstraint('name', 'type'),

)

sa.Table('problem', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),
        sa.Column('type', sa.Enum(
            'condition',
            'other',
            name='problem_type',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.UniqueConstraint('name', 'type'),

)

sa.Table('intervention', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),
        sa.Column('type', sa.Enum(
            'drug',
            'other',
            name='intervention_type',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.UniqueConstraint('name', 'type'),

)

sa.Table('location', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),
        sa.Column('type', sa.Enum(
            'country',
            'city',
            'other',
            name='location_type',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.UniqueConstraint('name', 'type'),

)

sa.Table('organisation', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),

        # Constraints

        # sa.UniqueConstraint('name'),

)

sa.Table('person', metadata,

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),

        # Constraints

        # sa.UniqueConstraint('name'),

)

metadata.create_all()
<commit_msg>updated schema<commit_after># -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import os
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import ARRAY, JSONB

engine = sa.create_engine(os.environ['OPENTRIALS_DATABASE_URL'])
metadata = sa.MetaData(bind=engine, reflect=True)

# Main tables

sa.Table('trial', metadata,

        # Columns

        # Meta
        sa.Column('uuid', sa.Text, primary_key=True),

        # General
        sa.Column('primary_register', sa.Text),
        sa.Column('primary_id', sa.Text),
        sa.Column('secondary_ids', JSONB),
        sa.Column('registration_date', sa.Date),
        sa.Column('public_title', sa.Text),
        sa.Column('scientific_title', sa.Text),
        sa.Column('description', sa.Text),

        # Recruitment
        sa.Column('eligibility_criteria', sa.Text),
        sa.Column('target_sample_size', sa.Integer),
        sa.Column('first_enrollment_date', sa.Date),
        sa.Column('recruitment_status', sa.Text),

        # Study design
        sa.Column('study_type', sa.Text),
        sa.Column('study_design', sa.Text),
        sa.Column('study_phase', sa.Text),

        # Constraints

        sa.UniqueConstraint('primary_register', 'primary_id'),

)

sa.Table('trial_record', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('record_uuid', sa.Text, sa.ForeignKey('record.uuid')),
        sa.Column('role', sa.Enum(
            'primary',
            'secondary',
            name='trial_record_role',
        )),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'record_uuid'),

)

sa.Table('trial_publication', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('publication_uuid', sa.Text, sa.ForeignKey('publication.uuid')),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'publication_uuid'),

)

sa.Table('trial_document', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('document_uuid', sa.Text, sa.ForeignKey('document.uuid')),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'document_uuid'),

)

sa.Table('trial_outcome', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('role', sa.Enum(
            'primary',
            'secondary',
            'other',
            name='trial_outcome_role',
        )),
        sa.Column('order', sa.Integer),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'role', 'order'),

)

sa.Table('trial_problem', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('problem_uuid', sa.Text, sa.ForeignKey('problem.uuid')),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'problem_uuid'),

)

sa.Table('trial_intervention', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('intervention_uuid', sa.Text, sa.ForeignKey('intervention.uuid')),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'intervention_uuid'),

)

sa.Table('trial_location', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('location_uuid', sa.Text, sa.ForeignKey('location.uuid')),
        sa.Column('role', sa.Enum(
            'recruitment_countries',
            'other',
            name='trial_location_role',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'location_uuid'),

)

sa.Table('trial_person', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('person_uuid', sa.Text, sa.ForeignKey('person.uuid')),
        sa.Column('role', sa.Enum(
            'public_queries',
            'scientific_queries',
            'other',
            name='trial_person_role',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'person_uuid'),

)

sa.Table('trial_organisation', metadata,

        # Columns

        sa.Column('trial_uuid', sa.Text, sa.ForeignKey('trial.uuid')),
        sa.Column('organisation_uuid', sa.Text, sa.ForeignKey('organisation.uuid')),
        sa.Column('role', sa.Enum(
            'primary_sponsor',
            'sponsor',
            'funder',
            'other',
            name='trial_organisation_role',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.PrimaryKeyConstraint('trial_uuid', 'organisation_uuid'),

)

sa.Table('record', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('source', sa.Text, sa.ForeignKey('source.uuid')),
        sa.Column('data', JSONB),

)

sa.Table('publication', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('source', sa.Text, sa.ForeignKey('source.uuid')),
        sa.Column('name', sa.Text),

)

sa.Table('document', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('source', sa.Text, sa.ForeignKey('source.uuid')),
        sa.Column('name', sa.Text),

)

# Reference tables

sa.Table('source', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),
        sa.Column('type', sa.Enum(
            'register',
            'other',
            name='source_type',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.UniqueConstraint('name', 'type'),

)

sa.Table('problem', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),
        sa.Column('type', sa.Enum(
            'condition',
            'other',
            name='problem_type',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.UniqueConstraint('name', 'type'),

)

sa.Table('intervention', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),
        sa.Column('type', sa.Enum(
            'drug',
            'other',
            name='intervention_type',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.UniqueConstraint('name', 'type'),

)

sa.Table('location', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),
        sa.Column('type', sa.Enum(
            'country',
            'city',
            'other',
            name='location_type',
        )),
        sa.Column('data', JSONB),

        # Constraints

        sa.UniqueConstraint('name', 'type'),

)

sa.Table('organisation', metadata,

        # Columns

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),

        # Constraints

        # develop based on de-duplication

)

sa.Table('person', metadata,

        sa.Column('uuid', sa.Text, primary_key=True),
        sa.Column('name', sa.Text),

        # Constraints

        # develop based on de-duplication

)

metadata.create_all()
<|endoftext|>


<commit_before>"""
dialogue Mnager

this class is responsible for
    1. loop up action executor by action
    2. handle an request
    3. dispatch a requset based on query

Author: Ming Fang <mingf@cs.cmu.edu>
"""

from weiss.dialogue.actions import *
from weiss.dialogue.responseGenerator import responseHandler
from weiss.flows.factory import getFlowManager
from weiss.planner.factory import getPlanner
from weiss.utils.switch import switch

from django.utils import timezone

from weiss.models import History

import HTMLParser
import datetime
import traceback

logger = logging.getLogger(__name__)

class DialogueManager(object):
    def __init__(self):
        self._planner = getPlanner()
        self._fmgr = getFlowManager()
        self._html_parser = HTMLParser.HTMLParser()

    @property
    def planner(self):
        return self._planner

    @property
    def fmgr(self):
        return self._fmgr

    @property
    def html_parser(self):
        return self._html_parser

    def getExecutor(self, action):
        """return action handler according to action
        :param action Action enum
        :rtype : method
        """
        for case in switch(action):
            if case(Action.NextRandomComment):
                return nextRandomCmt

            elif case(Action.NextOppositeComment):
                return nextRandomOppositeCmt

            elif case(Action.NextNegativeComment):
                return nextRandomNegativeCmt

            elif case(Action.NextPositiveComment):
                return nextRandomPositiveCmt

            elif case(Action.NextRandomEntity):
                return nextRandomEntity

            elif case(Action.SentimentStats):
                return sentimentStats

            elif case(Action.EntitySelection):
                return entitySelection

            elif case(Action.TypeSelection):
                return typeSelection

            elif case(Action.Greeting):
                return greeting

            elif case(Action.UnknownAction):
                return unknownAction

            elif case(Action.EntityConfirmation):
                return entityConfirmation

            elif case(Action.NextSummary):
                return next_summary

            elif case(Action.NextPositiveSummary):
                return next_positive_summary

            elif case(Action.NextNegativeSummary):
                return next_negative_summary

            elif case(Action.NextOppositeSummary):
                return next_opposite_summary

            elif case():
                raise KeyError("No such action %s" % action)

    def handle(self, request, query_obj=None):
        """Handle a request from user, will call dispatch
        """
        if query_obj is not None:
            query = query_obj.query
        else:
            query = str(request.POST.get('queryinput', None))
        logger.info("query:%s" % query)

        flow = request.session.get('flow', None)

        if flow is None:
            logger.info("No such flow %s" % query_obj)
            return None

        decision = self.planner.plan(query, flow)
        self.dispatch(flow, query, decision)

        flow.save_into(request)
        return flow

    def dispatch(self, flow, query, decision):
        """Dispatch a request based on query
        """
        flow.action = decision['aid']
        action = flow.action
        logger.info(action.name)

        if query is None:
            query = action.name + " : " + decision['keywords']

        logger.info("Dispatch action: %s, %s" % (action.value, action.name))

        flow.start_line(action, query)

        executor = self.getExecutor(action)

        executor(flow, decision)  # transition happens inside

        try:
            response = responseHandler(flow)
        except Exception as e:
            response = "Failed to generate response. Please check the log"
            logger.error("%s", e.message)
            print traceback.format_exc()

        flow.end_line(response)

        return

    def start_new_dialogue(self, request):
        flow = self.fmgr.new(request)
        greeting_executor = self.getExecutor(Action.Greeting)
        greeting_executor(flow, None)
        response = responseHandler(flow)
        flow.start_line(Action.Greeting)
        flow.end_line(response)
        flow.save_into(request)
        return flow

    def end_dialogue(self, user):
        self.fmgr.delete(user)

    def get_dialogue(self, user_id, limit=10):
        ten_min_ago = timezone.now() - datetime.timedelta(minutes=10)  # 10 min ago
        lines = History.objects.filter(Q(userid=user_id), Q(time__gt=ten_min_ago)).order_by("-time")[:limit]
        if len(lines) == 0:
            # If no line in the last 10 min, relax the time constraint
            lines = History.objects.filter(Q(userid=user_id)).order_by("-time")[:1]
        num_greeting = 0
        # Do not show multiple Greeting
        for line in reversed(lines):
            if line.aid.aid != Action.Greeting.value:
                break
            num_greeting += 1

        # more than 1 greeting
        if num_greeting > 1:
            num_greeting -= 1 # keep at least one
            num_keep = len(lines) - num_greeting
            lines = lines[:num_keep]

        for line in lines:
            line.response = self.html_parser.unescape(line.response)
        return lines
<commit_msg>fix encoding issue<commit_after>"""
dialogue Mnager

this class is responsible for
    1. loop up action executor by action
    2. handle an request
    3. dispatch a requset based on query

Author: Ming Fang <mingf@cs.cmu.edu>
"""

from weiss.dialogue.actions import *
from weiss.dialogue.responseGenerator import responseHandler
from weiss.flows.factory import getFlowManager
from weiss.planner.factory import getPlanner
from weiss.utils.switch import switch

from django.utils import timezone

from weiss.models import History

import HTMLParser
import datetime
import traceback

logger = logging.getLogger(__name__)

class DialogueManager(object):
    def __init__(self):
        self._planner = getPlanner()
        self._fmgr = getFlowManager()
        self._html_parser = HTMLParser.HTMLParser()

    @property
    def planner(self):
        return self._planner

    @property
    def fmgr(self):
        return self._fmgr

    @property
    def html_parser(self):
        return self._html_parser

    def getExecutor(self, action):
        """return action handler according to action
        :param action Action enum
        :rtype : method
        """
        for case in switch(action):
            if case(Action.NextRandomComment):
                return nextRandomCmt

            elif case(Action.NextOppositeComment):
                return nextRandomOppositeCmt

            elif case(Action.NextNegativeComment):
                return nextRandomNegativeCmt

            elif case(Action.NextPositiveComment):
                return nextRandomPositiveCmt

            elif case(Action.NextRandomEntity):
                return nextRandomEntity

            elif case(Action.SentimentStats):
                return sentimentStats

            elif case(Action.EntitySelection):
                return entitySelection

            elif case(Action.TypeSelection):
                return typeSelection

            elif case(Action.Greeting):
                return greeting

            elif case(Action.UnknownAction):
                return unknownAction

            elif case(Action.EntityConfirmation):
                return entityConfirmation

            elif case(Action.NextSummary):
                return next_summary

            elif case(Action.NextPositiveSummary):
                return next_positive_summary

            elif case(Action.NextNegativeSummary):
                return next_negative_summary

            elif case(Action.NextOppositeSummary):
                return next_opposite_summary

            elif case():
                raise KeyError("No such action %s" % action)

    def handle(self, request, query_obj=None):
        """Handle a request from user, will call dispatch
        """
        if query_obj is not None:
            query = query_obj.query
        else:
            query = str(request.POST.get('queryinput', None))
        logger.info("query:%s" % query)

        flow = request.session.get('flow', None)

        if flow is None:
            logger.info("No such flow %s" % query_obj)
            return None

        decision = self.planner.plan(query, flow)
        self.dispatch(flow, query, decision)

        flow.save_into(request)
        return flow

    def dispatch(self, flow, query, decision):
        """Dispatch a request based on query
        """
        flow.action = decision['aid']
        action = flow.action
        logger.info(action.name)

        if query is None:
            query = action.name + " : " + decision['keywords']

        logger.info("Dispatch action: %s, %s" % (action.value, action.name))

        flow.start_line(action, query)

        executor = self.getExecutor(action)

        executor(flow, decision)  # transition happens inside

        try:
            response = responseHandler(flow)
            response = self.html_parser.unescape(response)
        except Exception as e:
            response = "Failed to generate response. Please check the log"
            logger.error("%s", e.message)
            print traceback.format_exc()

        flow.end_line(response)

        return

    def start_new_dialogue(self, request):
        flow = self.fmgr.new(request)
        greeting_executor = self.getExecutor(Action.Greeting)
        greeting_executor(flow, None)
        response = responseHandler(flow)
        flow.start_line(Action.Greeting)
        flow.end_line(response)
        flow.save_into(request)
        return flow

    def end_dialogue(self, user):
        self.fmgr.delete(user)

    def get_dialogue(self, user_id, limit=10):
        ten_min_ago = timezone.now() - datetime.timedelta(minutes=10)  # 10 min ago
        lines = History.objects.filter(Q(userid=user_id), Q(time__gt=ten_min_ago)).order_by("-time")[:limit]
        if len(lines) == 0:
            # If no line in the last 10 min, relax the time constraint
            lines = History.objects.filter(Q(userid=user_id)).order_by("-time")[:1]
        num_greeting = 0
        # Do not show multiple Greeting
        for line in reversed(lines):
            if line.aid.aid != Action.Greeting.value:
                break
            num_greeting += 1

        # more than 1 greeting
        if num_greeting > 1:
            num_greeting -= 1 # keep at least one
            num_keep = len(lines) - num_greeting
            lines = lines[:num_keep]

        return lines
<|endoftext|>


<commit_before>import os
import abc
import asyncio
import functools
import itertools

import furl
import aiohttp

from waterbutler.core import exceptions

def build_url(base, *segments, **query):
    url = furl.furl(base)
    segments = filter(
        lambda segment: segment,
        map(
            lambda segment: segment.strip('/'),
            itertools.chain(url.path.segments, segments)
        )
    )
    url.path = os.path.join(*segments)
    url.args = query
    return url.url

class BaseProvider(metaclass=abc.ABCMeta):
    """The base class for all providers.
    Every provider must, at the least,
    implement all abstract methods in this class

    .. note::
        When adding a new provider you must add it to setup.py's
        `entry_points` under the `waterbutler.providers` key formatted
        as: `<provider name> = waterbutler.providers.yourprovider:<FullProviderName>`

        Keep in mind that `yourprovider` modules must export the provider class
    """

    BASE_URL = None

    def __init__(self, auth, credentials, settings):
        """
        :param dict auth: Information about the user this provider will act on the behalf of
        :param dict credentials: The credentials used to authenticate with the provider,
            ofter an OAuth 2 token
        :param dict settings: Configuration settings for this provider,
            often folder or repo
        """
        self.auth = auth
        self.credentials = credentials
        self.settings = settings

    @abc.abstractproperty
    def NAME(self):
        raise NotImplementedError

    def __eq__(self, other):
        try:
            return (
                type(self) == type(other) and
                self.credentials == other.credentials
            )
        except AttributeError:
            return False

    def serialized(self):
        return {
            'name': self.NAME,
            'auth': self.auth,
            'settings': self.settings,
            'credentials': self.credentials,
        }

    def build_url(self, *segments, **query):
        """A nice wrapped around furl, builds urls based on self.BASE_URL

        :param (str, ...) segments: A tuple of string joined into /foo/bar/..
        :param dict query: A dictionary that will be turned into query parameters ?foo=bar
        :rtype: str
        """
        return build_url(self.BASE_URL, *segments, **query)

    @property
    def default_headers(self):
        """Headers to be included with every request
        Commonly OAuth headers or Content-Type
        """
        return {}

    def build_headers(self, **kwargs):
        headers = self.default_headers
        headers.update(kwargs)
        return {
            key: value
            for key, value in headers.items()
            if value is not None
        }

    @asyncio.coroutine
    def make_request(self, *args, **kwargs):
        """A wrapper around :func:`aiohttp.request`. Inserts default headers.

        :param str method: The HTTP method
        :param str url: The url to send the request to
        :keyword expects: An optional tuple of HTTP status codes as integers raises an exception
            if the returned status code is not in it.
        :type expects: tuple of ints
        :param Exception throws: The exception to be raised from expects
        :param tuple \*args: args passed to :func:`aiohttp.request`
        :param dict \*kwargs: kwargs passed to :func:`aiohttp.request`
        :rtype: :class:`aiohttp.Response`
        :raises ProviderError: Raised if expects is defined
        """
        kwargs['headers'] = self.build_headers(**kwargs.get('headers', {}))
        expects = kwargs.pop('expects', None)
        throws = kwargs.pop('throws', exceptions.ProviderError)
        response = yield from aiohttp.request(*args, **kwargs)
        if expects and response.status not in expects:
            raise (yield from exceptions.exception_from_response(response, error=throws, **kwargs))
        return response

    @asyncio.coroutine
    def move(self, dest_provider, src_path, dest_path, rename=None, conflict='replace', handle_naming=True):
        """Moves a file or folder from the current provider to the specified one
        Performs a copy and then a delete.
        Calls :func:`BaseProvider.intra_move` if possible.

        :param BaseProvider dest_provider: The provider to move to
        :param dict source_options: A dict to be sent to either :func:`BaseProvider.intra_move`
            or :func:`BaseProvider.copy` and :func:`BaseProvider.delete`
        :param dict dest_options: A dict to be sent to either :func:`BaseProvider.intra_move`
            or :func:`BaseProvider.copy`
        """
        args = (dest_provider, src_path, dest_path)
        kwargs = {'rename': rename, 'conflict': conflict}

        if handle_naming:
            dest_path = yield from dest_provider.handle_naming(
                dest_path,
                rename or src_path.name,
                is_dir=src_path.is_dir,
                conflict=conflict
            )
            args = (dest_provider, src_path, dest_path)
            kwargs = {}

        if self.can_intra_move(dest_provider, src_path):
            return (yield from self.intra_move(*args))

        if src_path.is_dir:
            metadata, created = yield from self._folder_file_op(self.move, *args, **kwargs)
        else:
            metadata, created = yield from self.copy(*args, handle_naming=False, **kwargs)

        yield from self.delete(src_path)

        return metadata, created

    @asyncio.coroutine
    def copy(self, dest_provider, src_path, dest_path, rename=None, conflict='replace', handle_naming=True):
        args = (dest_provider, src_path, dest_path)
        kwargs = {'rename': rename, 'conflict': conflict, 'handle_naming': handle_naming}

        if handle_naming:
            dest_path = yield from dest_provider.handle_naming(
                dest_path,
                rename or src_path.name,
                is_dir=src_path.is_dir,
                conflict=conflict
            )
            args = (dest_provider, src_path, dest_path)
            kwargs = {}

        if self.can_intra_copy(dest_provider, src_path):
                return (yield from self.intra_copy(*args))

        if src_path.is_dir:
            return (yield from self._folder_file_op(self.copy, *args, **kwargs))

        return (yield from dest_provider.upload(
            (yield from self.download(src_path)),
            dest_path
        ))

    @asyncio.coroutine
    def _folder_file_op(self, func, dest_provider, src_path, dest_path, **kwargs):
        assert src_path.is_dir, 'src_path must be a directory'
        assert asyncio.iscoroutinefunction(func), 'func must be a coroutine'

        try:
            folder = yield from dest_provider.create_folder(dest_path)
            created = True
        except exceptions.CreateFolderError as e:
            if e.code != 409:
                raise
            created = False

            #TODO

        futures = []
        for item in (yield from self.metadata(src_path)):
            futures.append(
                asyncio.async(
                    func(
                        dest_provider,
                        # TODO figure out a way to cut down on all the requests made here
                        (yield from self.revalidate_path(src_path, item['name'], folder=item['kind'] == 'folder')),
                        (yield from dest_provider.revalidate_path(dest_path, item['name'], folder=item['kind'] == 'folder')),
                        # src_path.child(item['name'], _id=item.get('id'), folder=item['kind'] == 'folder'),
                        # dest_path.child(item['name'], _id=item.get('id'), folder=item['kind'] == 'folder'),
                        handle_naming=False,
                    )
                )
            )

        if not futures:
            folder['children'] = []
            return folder, created

        finished, pending = yield from asyncio.wait(futures, return_when=asyncio.FIRST_EXCEPTION)

        if len(pending) != 0:
            finished.pop().result()

        folder['children'] = [
            future.result()
            for future in finished
        ]

        return folder, created

    @asyncio.coroutine
    def handle_naming(self, path, rename, is_dir=False, conflict='replace'):
        dest_path, _ = yield from self.handle_name_conflict(
            (yield from self.revalidate_path(
                path, rename, folder=is_dir
            )), conflict=conflict)

        return dest_path

    def can_intra_copy(self, other, path):
        """Indicates if a quick copy can be performed
        between the current and `other`.

        .. note::
            Defaults to False

        :param waterbutler.core.provider.BaseProvider other: The provider to check against
        :rtype: bool
        """
        return False

    def can_intra_move(self, other, path):
        """Indicates if a quick move can be performed
        between the current and `other`.

        .. note::
            Defaults to False

        :param waterbutler.core.provider.BaseProvider other: The provider to check against
        :rtype: bool
        """
        return False

    def intra_copy(self, dest_provider, source_options, dest_options):
        raise NotImplementedError

    @asyncio.coroutine
    def intra_move(self, dest_provider, src_path, dest_path):
        data, created = yield from self.intra_copy(dest_provider, src_path, dest_path)
        yield from self.delete(src_path)
        return data, created

    @asyncio.coroutine
    def exists(self, path, **kwargs):
        try:
            return (yield from self.metadata(path, **kwargs))
        except exceptions.MetadataError as e:
            if e.code != 404:
                raise
        return False

    @asyncio.coroutine
    def handle_name_conflict(self, path, conflict='replace', **kwargs):
        """Given a name and a conflict resolution pattern determine
        the correct file path to upload to and indicate if that file exists or not

        :param WaterbutlerPath path: An object supporting the waterbutler path API
        :param str conflict: replace or keep
        :rtype: (WaterButlerPath, dict or None)
        """
        exists = yield from self.exists(path, **kwargs)
        if not exists or conflict != 'keep':
            return path, exists

        while (yield from self.exists(path.increment_name(), **kwargs)):
            pass
        # path.increment_name()
        # exists = self.exists(str(path))
        return path, False

    @asyncio.coroutine
    def revalidate_path(self, base, path, folder=False):
        return base.child(path, folder=folder)

    @abc.abstractmethod
    def download(self, **kwargs):
        pass

    @abc.abstractmethod
    def upload(self, stream, conflict='replace', **kwargs):
        pass

    @abc.abstractmethod
    def delete(self, **kwargs):
        pass

    @abc.abstractmethod
    def metadata(self, **kwargs):
        pass

    @abc.abstractmethod
    def validate_path(self, path, **kwargs):
        pass

    def revisions(self, **kwargs):
        return []  # TODO Raise 405 by default h/t @rliebz

    def create_folder(self, *args, **kwargs):
        """Create a folder in the current provider
        returns True if the folder was created; False if it already existed

        :rtype FolderMetadata:
        :raises: waterbutler.ProviderError
        """
        raise exceptions.ProviderError({'message': 'Folder creation not supported.'}, code=405)
<commit_msg>Revalidate folder paths upon creation<commit_after>import os
import abc
import asyncio
import functools
import itertools

import furl
import aiohttp

from waterbutler.core import exceptions

def build_url(base, *segments, **query):
    url = furl.furl(base)
    segments = filter(
        lambda segment: segment,
        map(
            lambda segment: segment.strip('/'),
            itertools.chain(url.path.segments, segments)
        )
    )
    url.path = os.path.join(*segments)
    url.args = query
    return url.url

class BaseProvider(metaclass=abc.ABCMeta):
    """The base class for all providers.
    Every provider must, at the least,
    implement all abstract methods in this class

    .. note::
        When adding a new provider you must add it to setup.py's
        `entry_points` under the `waterbutler.providers` key formatted
        as: `<provider name> = waterbutler.providers.yourprovider:<FullProviderName>`

        Keep in mind that `yourprovider` modules must export the provider class
    """

    BASE_URL = None

    def __init__(self, auth, credentials, settings):
        """
        :param dict auth: Information about the user this provider will act on the behalf of
        :param dict credentials: The credentials used to authenticate with the provider,
            ofter an OAuth 2 token
        :param dict settings: Configuration settings for this provider,
            often folder or repo
        """
        self.auth = auth
        self.credentials = credentials
        self.settings = settings

    @abc.abstractproperty
    def NAME(self):
        raise NotImplementedError

    def __eq__(self, other):
        try:
            return (
                type(self) == type(other) and
                self.credentials == other.credentials
            )
        except AttributeError:
            return False

    def serialized(self):
        return {
            'name': self.NAME,
            'auth': self.auth,
            'settings': self.settings,
            'credentials': self.credentials,
        }

    def build_url(self, *segments, **query):
        """A nice wrapped around furl, builds urls based on self.BASE_URL

        :param (str, ...) segments: A tuple of string joined into /foo/bar/..
        :param dict query: A dictionary that will be turned into query parameters ?foo=bar
        :rtype: str
        """
        return build_url(self.BASE_URL, *segments, **query)

    @property
    def default_headers(self):
        """Headers to be included with every request
        Commonly OAuth headers or Content-Type
        """
        return {}

    def build_headers(self, **kwargs):
        headers = self.default_headers
        headers.update(kwargs)
        return {
            key: value
            for key, value in headers.items()
            if value is not None
        }

    @asyncio.coroutine
    def make_request(self, *args, **kwargs):
        """A wrapper around :func:`aiohttp.request`. Inserts default headers.

        :param str method: The HTTP method
        :param str url: The url to send the request to
        :keyword expects: An optional tuple of HTTP status codes as integers raises an exception
            if the returned status code is not in it.
        :type expects: tuple of ints
        :param Exception throws: The exception to be raised from expects
        :param tuple \*args: args passed to :func:`aiohttp.request`
        :param dict \*kwargs: kwargs passed to :func:`aiohttp.request`
        :rtype: :class:`aiohttp.Response`
        :raises ProviderError: Raised if expects is defined
        """
        kwargs['headers'] = self.build_headers(**kwargs.get('headers', {}))
        expects = kwargs.pop('expects', None)
        throws = kwargs.pop('throws', exceptions.ProviderError)
        response = yield from aiohttp.request(*args, **kwargs)
        if expects and response.status not in expects:
            raise (yield from exceptions.exception_from_response(response, error=throws, **kwargs))
        return response

    @asyncio.coroutine
    def move(self, dest_provider, src_path, dest_path, rename=None, conflict='replace', handle_naming=True):
        """Moves a file or folder from the current provider to the specified one
        Performs a copy and then a delete.
        Calls :func:`BaseProvider.intra_move` if possible.

        :param BaseProvider dest_provider: The provider to move to
        :param dict source_options: A dict to be sent to either :func:`BaseProvider.intra_move`
            or :func:`BaseProvider.copy` and :func:`BaseProvider.delete`
        :param dict dest_options: A dict to be sent to either :func:`BaseProvider.intra_move`
            or :func:`BaseProvider.copy`
        """
        args = (dest_provider, src_path, dest_path)
        kwargs = {'rename': rename, 'conflict': conflict}

        if handle_naming:
            dest_path = yield from dest_provider.handle_naming(
                dest_path,
                rename or src_path.name,
                is_dir=src_path.is_dir,
                conflict=conflict
            )
            args = (dest_provider, src_path, dest_path)
            kwargs = {}

        if self.can_intra_move(dest_provider, src_path):
            return (yield from self.intra_move(*args))

        if src_path.is_dir:
            metadata, created = yield from self._folder_file_op(self.move, *args, **kwargs)
        else:
            metadata, created = yield from self.copy(*args, handle_naming=False, **kwargs)

        yield from self.delete(src_path)

        return metadata, created

    @asyncio.coroutine
    def copy(self, dest_provider, src_path, dest_path, rename=None, conflict='replace', handle_naming=True):
        args = (dest_provider, src_path, dest_path)
        kwargs = {'rename': rename, 'conflict': conflict, 'handle_naming': handle_naming}

        if handle_naming:
            dest_path = yield from dest_provider.handle_naming(
                dest_path,
                rename or src_path.name,
                is_dir=src_path.is_dir,
                conflict=conflict
            )
            args = (dest_provider, src_path, dest_path)
            kwargs = {}

        if self.can_intra_copy(dest_provider, src_path):
                return (yield from self.intra_copy(*args))

        if src_path.is_dir:
            return (yield from self._folder_file_op(self.copy, *args, **kwargs))

        return (yield from dest_provider.upload(
            (yield from self.download(src_path)),
            dest_path
        ))

    @asyncio.coroutine
    def _folder_file_op(self, func, dest_provider, src_path, dest_path, **kwargs):
        assert src_path.is_dir, 'src_path must be a directory'
        assert asyncio.iscoroutinefunction(func), 'func must be a coroutine'

        try:
            folder = yield from dest_provider.create_folder(dest_path)
            created = True
        except exceptions.CreateFolderError as e:
            if e.code != 409:
                raise
            created = False
            #TODO

        dest_path = yield from dest_provider.revalidate_path(dest_path.parent, dest_path.name, folder=dest_path.is_dir)

        futures = []
        for item in (yield from self.metadata(src_path)):
            futures.append(
                asyncio.async(
                    func(
                        dest_provider,
                        # TODO figure out a way to cut down on all the requests made here
                        (yield from self.revalidate_path(src_path, item['name'], folder=item['kind'] == 'folder')),
                        (yield from dest_provider.revalidate_path(dest_path, item['name'], folder=item['kind'] == 'folder')),
                        # src_path.child(item['name'], _id=item.get('id'), folder=item['kind'] == 'folder'),
                        # dest_path.child(item['name'], _id=item.get('id'), folder=item['kind'] == 'folder'),
                        handle_naming=False,
                    )
                )
            )

        if not futures:
            folder['children'] = []
            return folder, created

        finished, pending = yield from asyncio.wait(futures, return_when=asyncio.FIRST_EXCEPTION)

        if len(pending) != 0:
            finished.pop().result()

        folder['children'] = [
            future.result()
            for future in finished
        ]

        return folder, created

    @asyncio.coroutine
    def handle_naming(self, path, rename, is_dir=False, conflict='replace'):
        dest_path, _ = yield from self.handle_name_conflict(
            (yield from self.revalidate_path(
                path, rename, folder=is_dir
            )), conflict=conflict)

        return dest_path

    def can_intra_copy(self, other, path):
        """Indicates if a quick copy can be performed
        between the current and `other`.

        .. note::
            Defaults to False

        :param waterbutler.core.provider.BaseProvider other: The provider to check against
        :rtype: bool
        """
        return False

    def can_intra_move(self, other, path):
        """Indicates if a quick move can be performed
        between the current and `other`.

        .. note::
            Defaults to False

        :param waterbutler.core.provider.BaseProvider other: The provider to check against
        :rtype: bool
        """
        return False

    def intra_copy(self, dest_provider, source_options, dest_options):
        raise NotImplementedError

    @asyncio.coroutine
    def intra_move(self, dest_provider, src_path, dest_path):
        data, created = yield from self.intra_copy(dest_provider, src_path, dest_path)
        yield from self.delete(src_path)
        return data, created

    @asyncio.coroutine
    def exists(self, path, **kwargs):
        try:
            return (yield from self.metadata(path, **kwargs))
        except exceptions.MetadataError as e:
            if e.code != 404:
                raise
        return False

    @asyncio.coroutine
    def handle_name_conflict(self, path, conflict='replace', **kwargs):
        """Given a name and a conflict resolution pattern determine
        the correct file path to upload to and indicate if that file exists or not

        :param WaterbutlerPath path: An object supporting the waterbutler path API
        :param str conflict: replace or keep
        :rtype: (WaterButlerPath, dict or None)
        """
        exists = yield from self.exists(path, **kwargs)
        if not exists or conflict != 'keep':
            return path, exists

        while (yield from self.exists(path.increment_name(), **kwargs)):
            pass
        # path.increment_name()
        # exists = self.exists(str(path))
        return path, False

    @asyncio.coroutine
    def revalidate_path(self, base, path, folder=False):
        return base.child(path, folder=folder)

    @abc.abstractmethod
    def download(self, **kwargs):
        pass

    @abc.abstractmethod
    def upload(self, stream, conflict='replace', **kwargs):
        pass

    @abc.abstractmethod
    def delete(self, **kwargs):
        pass

    @abc.abstractmethod
    def metadata(self, **kwargs):
        pass

    @abc.abstractmethod
    def validate_path(self, path, **kwargs):
        pass

    def revisions(self, **kwargs):
        return []  # TODO Raise 405 by default h/t @rliebz

    def create_folder(self, *args, **kwargs):
        """Create a folder in the current provider
        returns True if the folder was created; False if it already existed

        :rtype FolderMetadata:
        :raises: waterbutler.ProviderError
        """
        raise exceptions.ProviderError({'message': 'Folder creation not supported.'}, code=405)
<|endoftext|>


<commit_before>import abc
from nalaf.features import FeatureGenerator
from nltk.stem import PorterStemmer
import re

class EdgeFeatureGenerator(FeatureGenerator):
    """
    Abstract class for generating features for each edge in the dataset.
    Subclasses that inherit this class should:
    * Be named [Name]FeatureGenerator
    * Implement the abstract method generate
    * Append new items to the dictionary field "features" of each Edge in the dataset
    """

    @abc.abstractmethod
    def generate(self, dataset, feature_set, is_training_mode):
        """
        :type dataset: nalaf.structures.data.Dataset
        """
        pass

    def add_to_feature_set(self, feature_set, is_training_mode, edge, feature_name, value=1):
        """
        Return True if feature was added to feature_set. False, otherwise

        If the feature_name is None, the feature is not added in anycase. See: self.mk_feature_name
        """
        if feature_name is None:
            return False

        else:
            if not re.search('\[-?[0-9]+\]$', feature_name):
                feature_name = feature_name + "_[0]"  # See logic of definition in: FeatureDictionary

            if is_training_mode:
                if feature_name not in feature_set.keys():
                    feature_set[feature_name] = len(feature_set.keys()) + 1
                edge.features[feature_set[feature_name]] = value
                return True
            else:
                if feature_name in feature_set.keys():
                    edge.features[feature_set[feature_name]] = value
                    return True
                else:
                    return False

    def mk_feature_name(self, prefix, *args):
        if prefix is None:
            return None
        else:
            l = [str(x) for x in ([prefix] + list(args))]
            return "_".join(l)

# -----------------------------------------------------------------------------

class NamedEntityCountFeatureGenerator(EdgeFeatureGenerator):
    """
    Generates Named Entity Count for each sentence that contains an edge
    """

    def __init__(self, entity_type, prefix1):
        self.entity_type = entity_type
        """type of entity"""
        self.prefix1 = prefix1

    def generate(self, dataset, feature_set, is_training_mode):
        for edge in dataset.edges():
            entities = edge.part.get_entities_in_sentence(edge.sentence_id, self.entity_type)
            num_entities = len(entities)
            feature_name = self.mk_feature_name(self.prefix1, self.entity_type, 'count', num_entities)
            self.add_to_feature_set(feature_set, is_training_mode, edge, feature_name, value=1)

# TODO rest is potential material to delete, almost exact copy in relna's sentence.py

class BagOfWordsFeatureGenerator(FeatureGenerator):
    """
    Generates Bag of Words representation for each sentence that contains an edge

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type training_mode: bool
    """
    def __init__(self, feature_set, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.training_mode = training_mode
        """whether the mode is training or testing"""

    def generate(self, dataset):
        for edge in dataset.edges():
            sentence = edge.part.sentences[edge.sentence_id]
            if self.training_mode:
                for token in sentence:
                    feature_name = 'bow_' + token.word + '[0]'
                    if feature_name not in self.feature_set:
                        self.feature_set[feature_name] = len(self.feature_set.keys())+1
                    edge.features[self.feature_set[feature_name]] = 1
            else:
                for token in sentence:
                    feature_name = 'bow_' + token.word + '[0]'
                    if feature_name in self.feature_set.keys():
                        edge.features[self.feature_set[feature_name]] = 1

class StemmedBagOfWordsFeatureGenerator(FeatureGenerator):
    """
    Generates stemmed Bag of Words representation for each sentence that contains
    an edge, using the function given in the argument.

    By default it uses Porter stemmer

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type training_mode: bool
    """

    def __init__(self, feature_set, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.training_mode = training_mode
        """whether the mode is training or testing"""
        self.stemmer = PorterStemmer()

    def generate(self, dataset):
        for edge in dataset.edges():
            sentence = edge.part.sentences[edge.sentence_id]
            if self.training_mode:
                for token in sentence:
                    feature_name = 'bow_stem_' + self.stemmer.stem(token.word) + '[0]'
                    if feature_name not in self.feature_set:
                        self.feature_set[feature_name] = len(self.feature_set.keys())+1
                    edge.features[self.feature_set[feature_name]] = 1
            else:
                for token in sentence:
                    feature_name = 'bow_stem_' + self.stemmer.stem(token.word) + '[0]'
                    if feature_name in self.feature_set.keys():
                        edge.features[self.feature_set[feature_name]] = 1

class OrderOfEntitiesFeatureGenerator(FeatureGenerator):
    """
    Generates the order in which the entities are present in the sentence.
    That is, whether it is '...entity1...entity2...' or '...entity2...entity1...'

    Value of 1 means that the order is '...entity1...entity2...'
    Value of 0 means that the order is '...entity2...entity1...'

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type training_mode: bool
    """
    def __init__(self, feature_set, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.training_mode = training_mode
        """whether the mode is training or testing"""

    def generate(self, dataset):
        for edge in dataset.edges():
            feature_name = 'order_entities_[0]'
            if self.training_mode:
                if feature_name not in self.feature_set.keys():
                    self.feature_set[feature_name] = len(self.feature_set.keys())+1
                if edge.entity1.offset < edge.entity2.offset:
                    edge.features[self.feature_set[feature_name]] = 1
            else:
                if feature_name in self.feature_set.keys():
                    if edge.entity1.offset < edge.entity2.offset:
                        edge.features[self.feature_set[feature_name]] = 1

class CapitalizedTokenFeatureGenerator(FeatureGenerator):
    """
    Checks if the entity in the edge is capitalized or not.

    Value of 1 means that the entity is capitalized
    Value of 0 means that the entity is not capitalized

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type training_mode: bool
    """
    def __init__(self, feature_set, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.training_mode = training_mode
        """whether the mode is training or testing"""

    def generate(self, dataset):
        feature_name_1 = 'entity_1_capitalized_[0]'
        feature_name_2 = 'entity_2_capitalized_[0]'
        if self.training_mode:
            if feature_name_1 not in self.feature_set.keys():
                self.feature_set[feature_name_1] = len(self.feature_set.keys()) + 1
            if feature_name_2 not in self.feature_set.keys():
                self.feature_set[feature_name_2] = len(self.feature_set.keys()) + 1
            for edge in dataset.edges():
                if edge.entity1.text.isupper():
                    edge.features[self.feature_set[feature_name_1]] = 1
                if edge.entity2.text.isupper():
                    edge.features[self.feature_set[feature_name_2]] = 1
        else:
            for edge in dataset.edges():
                if feature_name_1 in self.feature_set.keys():
                    if edge.entity1.text.isupper():
                        edge.features[self.feature_set[feature_name_1]] = 1
                if feature_name_2 in self.feature_set.keys():
                    if edge.entity2.text.isupper():
                        edge.features[self.feature_set[feature_name_2]] = 1

class WordFilterFeatureGenerator(FeatureGenerator):
    """
    Checks if the sentence containing an edge contains any of the words
    given in the list.

    Value of 1 means that the sentence contains that word
    Value of 0 means that the sentence does not contain the word

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type words: list[str]
    :type stem: bool
    :type training_mode: bool
    """
    def __init__(self, feature_set, words, stem=True, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.words = words
        """a list of words to check for their presence in the sentence"""
        self.stem = stem
        """whether the words in the sentence and the list should be stemmed"""
        self.training_mode = True
        """whether the mode is training or testing"""
        self.stemmer = PorterStemmer()

    def generate(self, dataset):
        if self.stem:
            stemmed_words = [ self.stemmer.stem(word) for word in self.words ]
            if self.training_mode:
                for edge in dataset.edges():
                    sentence = edge.part.sentences[edge.sentence_id]
                    for token in sentence:
                        if self.stemmer.stem(token.word) in stemmed_words:
                            feature_name = 'word_filter_stem_' + self.stemmer.stem(token.word) + '[0]'
                            if feature_name not in self.feature_set.keys():
                                self.feature_set[feature_name] = len(self.feature_set.keys()) + 1
                            edge.features[self.feature_set[feature_name]] = 1
            else:
                for edge in dataset.edges():
                    sentence = edge.part.sentences[edge.sentence_id]
                    for token in sentence:
                        if self.stemmer.stem(token.word) in stemmed_words:
                            feature_name = 'word_filter_stem_' + self.stemmer.stem(token.word) + '[0]'
                            if feature_name in self.feature_set.keys():
                                edge.features[self.feature_set[feature_name]] = 1
        else:
            if self.training_mode:
                for edge in dataset.edges():
                    sentence = edge.part.sentences[edge.sentence_id]
                    for token in sentence:
                        if token.word in self.words:
                            feature_name = 'word_filter_' + token.word + '[0]'
                            if feature_name not in self.feature_set.keys():
                                self.feature_set[feature_name] = len(self.feature_set.keys()) + 1
                            edge.features[self.feature_set[feature_name]] = 1
            else:
                for edge in dataset.edges():
                    sentence = edge.part.sentences[edge.sentence_id]
                    for token in sentence:
                        if token in self.words:
                            feature_name = 'word_filter_' + token.word + '[0]'
                            if feature_name in self.feature_set.keys():
                                edge.features[self.feature_set[feature_name]] = 1

class NPChunkRootFeatureGenerator(FeatureGenerator):
    """
    Generate Noun Phrase Chunks for each sentence containing an edge and store
    the roots of each noun phrase chunk

    This requires the Python module SpaCy (http://spacy.io/) and NumPy. SpaCy
    can be installed by `pip3 install spacy`. Additionally it also requires
    the package data, which can be downloaded using
    `python3 -m spacy.en.download all`

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type nlp: spacy.en.English
    :type training_mode: bool
    """
    def __init__(self, feature_set, nlp, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.nlp = nlp
        """an instance of spacy.en.English"""
        self.training_mode = training_mode
        """whether the mode is training or testing"""

    def generate(self, dataset):
        if self.training_mode:
            for edge in dataset.edges():
                sent = edge.part.get_sentence_string_array()[edge.sentence_id]
                sentence = self.nlp(sent, parse=True, tag=True)
                for chunk in sentence.noun_chunks:
                    feature_name = 'np_chunk_root_' + chunk.root.orth_ + '[0]'
                    if feature_name not in self.feature_set.keys():
                        self.feature_set[feature_name] = len(self.feature_set.keys()) + 1
                    edge.features[self.feature_set[feature_name]] = 1
        else:
            for edge in dataset.edges():
                sent = edge.part.get_sentence_string_array()[edge.sentence_id]
                sentence = self.nlp(sent, parse=True, tag=True)
                for chunk in sentence.noun_chunks:
                    feature_name = 'np_chunk_root_' + chunk.root.orth_ + '[0]'
                    if feature_name in self.feature_set.keys():
                        edge.features[self.feature_set[feature_name]] = 1
<commit_msg>give info of mapping of features<commit_after>import abc
from nalaf.features import FeatureGenerator
from nltk.stem import PorterStemmer
import re

class EdgeFeatureGenerator(FeatureGenerator):
    """
    Abstract class for generating features for each edge in the dataset.
    Subclasses that inherit this class should:
    * Be named [Name]FeatureGenerator
    * Implement the abstract method generate
    * Append new items to the dictionary field "features" of each Edge in the dataset
    """

    @abc.abstractmethod
    def generate(self, dataset, feature_set, is_training_mode):
        """
        :type dataset: nalaf.structures.data.Dataset
        """
        pass

    def add_to_feature_set(self, feature_set, is_training_mode, edge, feature_name, value=1):
        """
        Return True if feature was added to feature_set. False, otherwise

        If the feature_name is None, the feature is not added in anycase. See: self.mk_feature_name
        """
        if feature_name is None:
            return False

        else:
            if not re.search('\[-?[0-9]+\]$', feature_name):
                feature_name = feature_name + "_[0]"  # See logic of definition in: FeatureDictionary

            if is_training_mode:
                if feature_name not in feature_set.keys():
                    index = len(feature_set.keys()) + 1
                    feature_set[feature_name] = index
                    print_debug("Feature map: {} == {}".format(str(index), feature_name))
                edge.features[feature_set[feature_name]] = value
                return True
            else:
                if feature_name in feature_set.keys():
                    edge.features[feature_set[feature_name]] = value
                    return True
                else:
                    return False

    def mk_feature_name(self, prefix, *args):
        if prefix is None:
            return None
        else:
            l = [str(x) for x in ([prefix] + list(args))]
            return "_".join(l)

# -----------------------------------------------------------------------------

class NamedEntityCountFeatureGenerator(EdgeFeatureGenerator):
    """
    Generates Named Entity Count for each sentence that contains an edge
    """

    def __init__(self, entity_type, prefix1):
        self.entity_type = entity_type
        """type of entity"""
        self.prefix1 = prefix1

    def generate(self, dataset, feature_set, is_training_mode):
        for edge in dataset.edges():
            entities = edge.part.get_entities_in_sentence(edge.sentence_id, self.entity_type)
            num_entities = len(entities)
            feature_name = self.mk_feature_name(self.prefix1, self.entity_type, 'count', num_entities)
            self.add_to_feature_set(feature_set, is_training_mode, edge, feature_name, value=1)

# TODO rest is potential material to delete, almost exact copy in relna's sentence.py

class BagOfWordsFeatureGenerator(FeatureGenerator):
    """
    Generates Bag of Words representation for each sentence that contains an edge

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type training_mode: bool
    """
    def __init__(self, feature_set, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.training_mode = training_mode
        """whether the mode is training or testing"""

    def generate(self, dataset):
        for edge in dataset.edges():
            sentence = edge.part.sentences[edge.sentence_id]
            if self.training_mode:
                for token in sentence:
                    feature_name = 'bow_' + token.word + '[0]'
                    if feature_name not in self.feature_set:
                        self.feature_set[feature_name] = len(self.feature_set.keys())+1
                    edge.features[self.feature_set[feature_name]] = 1
            else:
                for token in sentence:
                    feature_name = 'bow_' + token.word + '[0]'
                    if feature_name in self.feature_set.keys():
                        edge.features[self.feature_set[feature_name]] = 1

class StemmedBagOfWordsFeatureGenerator(FeatureGenerator):
    """
    Generates stemmed Bag of Words representation for each sentence that contains
    an edge, using the function given in the argument.

    By default it uses Porter stemmer

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type training_mode: bool
    """

    def __init__(self, feature_set, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.training_mode = training_mode
        """whether the mode is training or testing"""
        self.stemmer = PorterStemmer()

    def generate(self, dataset):
        for edge in dataset.edges():
            sentence = edge.part.sentences[edge.sentence_id]
            if self.training_mode:
                for token in sentence:
                    feature_name = 'bow_stem_' + self.stemmer.stem(token.word) + '[0]'
                    if feature_name not in self.feature_set:
                        self.feature_set[feature_name] = len(self.feature_set.keys())+1
                    edge.features[self.feature_set[feature_name]] = 1
            else:
                for token in sentence:
                    feature_name = 'bow_stem_' + self.stemmer.stem(token.word) + '[0]'
                    if feature_name in self.feature_set.keys():
                        edge.features[self.feature_set[feature_name]] = 1

class OrderOfEntitiesFeatureGenerator(FeatureGenerator):
    """
    Generates the order in which the entities are present in the sentence.
    That is, whether it is '...entity1...entity2...' or '...entity2...entity1...'

    Value of 1 means that the order is '...entity1...entity2...'
    Value of 0 means that the order is '...entity2...entity1...'

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type training_mode: bool
    """
    def __init__(self, feature_set, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.training_mode = training_mode
        """whether the mode is training or testing"""

    def generate(self, dataset):
        for edge in dataset.edges():
            feature_name = 'order_entities_[0]'
            if self.training_mode:
                if feature_name not in self.feature_set.keys():
                    self.feature_set[feature_name] = len(self.feature_set.keys())+1
                if edge.entity1.offset < edge.entity2.offset:
                    edge.features[self.feature_set[feature_name]] = 1
            else:
                if feature_name in self.feature_set.keys():
                    if edge.entity1.offset < edge.entity2.offset:
                        edge.features[self.feature_set[feature_name]] = 1

class CapitalizedTokenFeatureGenerator(FeatureGenerator):
    """
    Checks if the entity in the edge is capitalized or not.

    Value of 1 means that the entity is capitalized
    Value of 0 means that the entity is not capitalized

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type training_mode: bool
    """
    def __init__(self, feature_set, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.training_mode = training_mode
        """whether the mode is training or testing"""

    def generate(self, dataset):
        feature_name_1 = 'entity_1_capitalized_[0]'
        feature_name_2 = 'entity_2_capitalized_[0]'
        if self.training_mode:
            if feature_name_1 not in self.feature_set.keys():
                self.feature_set[feature_name_1] = len(self.feature_set.keys()) + 1
            if feature_name_2 not in self.feature_set.keys():
                self.feature_set[feature_name_2] = len(self.feature_set.keys()) + 1
            for edge in dataset.edges():
                if edge.entity1.text.isupper():
                    edge.features[self.feature_set[feature_name_1]] = 1
                if edge.entity2.text.isupper():
                    edge.features[self.feature_set[feature_name_2]] = 1
        else:
            for edge in dataset.edges():
                if feature_name_1 in self.feature_set.keys():
                    if edge.entity1.text.isupper():
                        edge.features[self.feature_set[feature_name_1]] = 1
                if feature_name_2 in self.feature_set.keys():
                    if edge.entity2.text.isupper():
                        edge.features[self.feature_set[feature_name_2]] = 1

class WordFilterFeatureGenerator(FeatureGenerator):
    """
    Checks if the sentence containing an edge contains any of the words
    given in the list.

    Value of 1 means that the sentence contains that word
    Value of 0 means that the sentence does not contain the word

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type words: list[str]
    :type stem: bool
    :type training_mode: bool
    """
    def __init__(self, feature_set, words, stem=True, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.words = words
        """a list of words to check for their presence in the sentence"""
        self.stem = stem
        """whether the words in the sentence and the list should be stemmed"""
        self.training_mode = True
        """whether the mode is training or testing"""
        self.stemmer = PorterStemmer()

    def generate(self, dataset):
        if self.stem:
            stemmed_words = [ self.stemmer.stem(word) for word in self.words ]
            if self.training_mode:
                for edge in dataset.edges():
                    sentence = edge.part.sentences[edge.sentence_id]
                    for token in sentence:
                        if self.stemmer.stem(token.word) in stemmed_words:
                            feature_name = 'word_filter_stem_' + self.stemmer.stem(token.word) + '[0]'
                            if feature_name not in self.feature_set.keys():
                                self.feature_set[feature_name] = len(self.feature_set.keys()) + 1
                            edge.features[self.feature_set[feature_name]] = 1
            else:
                for edge in dataset.edges():
                    sentence = edge.part.sentences[edge.sentence_id]
                    for token in sentence:
                        if self.stemmer.stem(token.word) in stemmed_words:
                            feature_name = 'word_filter_stem_' + self.stemmer.stem(token.word) + '[0]'
                            if feature_name in self.feature_set.keys():
                                edge.features[self.feature_set[feature_name]] = 1
        else:
            if self.training_mode:
                for edge in dataset.edges():
                    sentence = edge.part.sentences[edge.sentence_id]
                    for token in sentence:
                        if token.word in self.words:
                            feature_name = 'word_filter_' + token.word + '[0]'
                            if feature_name not in self.feature_set.keys():
                                self.feature_set[feature_name] = len(self.feature_set.keys()) + 1
                            edge.features[self.feature_set[feature_name]] = 1
            else:
                for edge in dataset.edges():
                    sentence = edge.part.sentences[edge.sentence_id]
                    for token in sentence:
                        if token in self.words:
                            feature_name = 'word_filter_' + token.word + '[0]'
                            if feature_name in self.feature_set.keys():
                                edge.features[self.feature_set[feature_name]] = 1

class NPChunkRootFeatureGenerator(FeatureGenerator):
    """
    Generate Noun Phrase Chunks for each sentence containing an edge and store
    the roots of each noun phrase chunk

    This requires the Python module SpaCy (http://spacy.io/) and NumPy. SpaCy
    can be installed by `pip3 install spacy`. Additionally it also requires
    the package data, which can be downloaded using
    `python3 -m spacy.en.download all`

    :type feature_set: nalaf.structures.data.FeatureDictionary
    :type nlp: spacy.en.English
    :type training_mode: bool
    """
    def __init__(self, feature_set, nlp, training_mode=True):
        self.feature_set = feature_set
        """the feature set for the dataset"""
        self.nlp = nlp
        """an instance of spacy.en.English"""
        self.training_mode = training_mode
        """whether the mode is training or testing"""

    def generate(self, dataset):
        if self.training_mode:
            for edge in dataset.edges():
                sent = edge.part.get_sentence_string_array()[edge.sentence_id]
                sentence = self.nlp(sent, parse=True, tag=True)
                for chunk in sentence.noun_chunks:
                    feature_name = 'np_chunk_root_' + chunk.root.orth_ + '[0]'
                    if feature_name not in self.feature_set.keys():
                        self.feature_set[feature_name] = len(self.feature_set.keys()) + 1
                    edge.features[self.feature_set[feature_name]] = 1
        else:
            for edge in dataset.edges():
                sent = edge.part.get_sentence_string_array()[edge.sentence_id]
                sentence = self.nlp(sent, parse=True, tag=True)
                for chunk in sentence.noun_chunks:
                    feature_name = 'np_chunk_root_' + chunk.root.orth_ + '[0]'
                    if feature_name in self.feature_set.keys():
                        edge.features[self.feature_set[feature_name]] = 1
<|endoftext|>


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

# This file is part of wger Workout Manager.
#
# wger Workout Manager is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# wger Workout Manager is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License

# Third Party
from rest_framework import serializers

# wger
from wger.exercises.models import (
    Alias,
    Equipment,
    Exercise,
    ExerciseBase,
    ExerciseCategory,
    ExerciseComment,
    ExerciseImage,
    ExerciseVideo,
    Muscle,
    Variation,
)

class ExerciseBaseSerializer(serializers.ModelSerializer):
    """
    Exercise base serializer
    """

    class Meta:
        model = ExerciseBase
        fields = [
            'id',
            'uuid',
            'creation_date',
            'update_date',
            'category',
            'muscles',
            'muscles_secondary',
            'equipment',
            'variations',
        ]

class EquipmentSerializer(serializers.ModelSerializer):
    """
    Equipment serializer
    """

    class Meta:
        model = Equipment
        fields = ['id', 'name']

class ExerciseImageSerializer(serializers.ModelSerializer):
    """
    ExerciseImage serializer
    """

    class Meta:
        model = ExerciseImage
        fields = [
            'id',
            'uuid',
            'exercise_base',
            'image',
            'is_main',
            'status',
            'style',
        ]

class ExerciseVideoSerializer(serializers.ModelSerializer):
    """
    ExerciseVideo serializer
    """
    exercise_base_uuid = serializers.ReadOnlyField(source='exercise_base.uuid')

    class Meta:
        model = ExerciseVideo
        fields = [
            'id',
            'uuid',
            'exercise_base',
            'exercise_base_uuid',
            'video',
            'is_main',
            'size',
            'duration',
            'width',
            'height',
            'codec',
            'codec_long',
            'license',
            'license_author',
        ]

class ExerciseCommentSerializer(serializers.ModelSerializer):
    """
    ExerciseComment serializer
    """

    id = serializers.IntegerField(required=False, read_only=True)

    class Meta:
        model = ExerciseComment
        fields = [
            'id',
            'exercise',
            'comment',
        ]

class ExerciseAliasSerializer(serializers.ModelSerializer):
    """
    ExerciseAlias serializer
    """

    class Meta:
        model = Alias
        fields = ['id', 'exercise', 'alias']

class ExerciseVariationSerializer(serializers.ModelSerializer):
    """
    Exercise variation serializer
    """

    class Meta:
        model = Variation
        fields = [
            'id',
        ]

class ExerciseInfoAliasSerializer(serializers.ModelSerializer):
    """
    Exercise alias serializer for info endpoint
    """

    class Meta:
        model = Alias
        fields = ['id', 'alias']

class ExerciseCategorySerializer(serializers.ModelSerializer):
    """
    ExerciseCategory serializer
    """

    class Meta:
        model = ExerciseCategory
        fields = ['id', 'name']

class MuscleSerializer(serializers.ModelSerializer):
    """
    Muscle serializer
    """

    class Meta:
        model = Muscle
        fields = [
            'id',
            'name',
            'is_front',
            'image_url_main',
            'image_url_secondary',
        ]

class ExerciseSerializer(serializers.ModelSerializer):
    """
    Exercise serializer

    The fields from the new ExerciseBase are retrieved here as to retain
    compatibility with the old model where all the fields where in Exercise.
    """
    category = serializers.PrimaryKeyRelatedField(queryset=ExerciseCategory.objects.all())
    muscles = serializers.PrimaryKeyRelatedField(many=True, queryset=Muscle.objects.all())
    muscles_secondary = serializers.PrimaryKeyRelatedField(many=True, queryset=Muscle.objects.all())
    equipment = serializers.PrimaryKeyRelatedField(many=True, queryset=Equipment.objects.all())
    variations = serializers.PrimaryKeyRelatedField(many=True, queryset=Variation.objects.all())

    class Meta:
        model = Exercise
        fields = (
            "id",
            "uuid",
            "name",
            "exercise_base",
            "description",
            "creation_date",
            "category",
            "muscles",
            "muscles_secondary",
            "equipment",
            "language",
            "license",
            "license_author",
            "variations",
        )

class ExerciseTranslationSerializer(serializers.ModelSerializer):
    """
    Exercise translation serializer
    """

    id = serializers.IntegerField(required=False, read_only=True)
    uuid = serializers.UUIDField(required=False, read_only=True)

    class Meta:
        model = Exercise
        fields = (
            "id",
            "uuid",
            "name",
            "exercise_base",
            "description",
            "creation_date",
            "language",
            "license",
            "license_author",
        )

class ExerciseInfoSerializer(serializers.ModelSerializer):
    """
    Exercise info serializer
    """

    images = ExerciseImageSerializer(many=True, read_only=True)
    videos = ExerciseVideoSerializer(many=True, read_only=True)
    comments = ExerciseCommentSerializer(source='exercisecomment_set', many=True, read_only=True)
    category = ExerciseCategorySerializer(read_only=True)
    muscles = MuscleSerializer(many=True, read_only=True)
    muscles_secondary = MuscleSerializer(many=True, read_only=True)
    equipment = EquipmentSerializer(many=True, read_only=True)
    variations = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    aliases = ExerciseInfoAliasSerializer(source='alias_set', many=True, read_only=True)

    class Meta:
        model = Exercise
        depth = 1
        fields = [
            "id",
            "name",
            "aliases",
            "uuid",
            "exercise_base_id",
            "description",
            "creation_date",
            "category",
            "muscles",
            "muscles_secondary",
            "equipment",
            "language",
            "license",
            "license_author",
            "images",
            "videos",
            "comments",
            "variations",
        ]

class ExerciseBaseInfoSerializer(serializers.ModelSerializer):
    """
    Exercise base info serializer
    """

    images = ExerciseImageSerializer(source='exerciseimage_set', many=True, read_only=True)
    category = ExerciseCategorySerializer(read_only=True)
    muscles = MuscleSerializer(many=True, read_only=True)
    muscles_secondary = MuscleSerializer(many=True, read_only=True)
    equipment = EquipmentSerializer(many=True, read_only=True)
    exercises = ExerciseTranslationSerializer(many=True, read_only=True)
    variations = serializers.PrimaryKeyRelatedField(read_only=True)

    class Meta:
        model = ExerciseBase
        depth = 1
        fields = [
            "id",
            "uuid",
            "category",
            "muscles",
            "muscles_secondary",
            "equipment",
            "license",
            "license_author",
            "images",
            "exercises",
            "variations",
            "images",
        ]
<commit_msg>Expose aliases and notes over the API<commit_after># -*- coding: utf-8 -*-

# This file is part of wger Workout Manager.
#
# wger Workout Manager is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# wger Workout Manager is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License

# Third Party
from rest_framework import serializers

# wger
from wger.exercises.models import (
    Alias,
    Equipment,
    Exercise,
    ExerciseBase,
    ExerciseCategory,
    ExerciseComment,
    ExerciseImage,
    ExerciseVideo,
    Muscle,
    Variation,
)

class ExerciseBaseSerializer(serializers.ModelSerializer):
    """
    Exercise base serializer
    """

    class Meta:
        model = ExerciseBase
        fields = [
            'id',
            'uuid',
            'creation_date',
            'update_date',
            'category',
            'muscles',
            'muscles_secondary',
            'equipment',
            'variations',
        ]

class EquipmentSerializer(serializers.ModelSerializer):
    """
    Equipment serializer
    """

    class Meta:
        model = Equipment
        fields = ['id', 'name']

class ExerciseImageSerializer(serializers.ModelSerializer):
    """
    ExerciseImage serializer
    """

    class Meta:
        model = ExerciseImage
        fields = [
            'id',
            'uuid',
            'exercise_base',
            'image',
            'is_main',
            'status',
            'style',
        ]

class ExerciseVideoSerializer(serializers.ModelSerializer):
    """
    ExerciseVideo serializer
    """
    exercise_base_uuid = serializers.ReadOnlyField(source='exercise_base.uuid')

    class Meta:
        model = ExerciseVideo
        fields = [
            'id',
            'uuid',
            'exercise_base',
            'exercise_base_uuid',
            'video',
            'is_main',
            'size',
            'duration',
            'width',
            'height',
            'codec',
            'codec_long',
            'license',
            'license_author',
        ]

class ExerciseCommentSerializer(serializers.ModelSerializer):
    """
    ExerciseComment serializer
    """

    id = serializers.IntegerField(required=False, read_only=True)

    class Meta:
        model = ExerciseComment
        fields = [
            'id',
            'exercise',
            'comment',
        ]

class ExerciseAliasSerializer(serializers.ModelSerializer):
    """
    ExerciseAlias serializer
    """

    class Meta:
        model = Alias
        fields = ['id', 'exercise', 'alias']

class ExerciseVariationSerializer(serializers.ModelSerializer):
    """
    Exercise variation serializer
    """

    class Meta:
        model = Variation
        fields = [
            'id',
        ]

class ExerciseInfoAliasSerializer(serializers.ModelSerializer):
    """
    Exercise alias serializer for info endpoint
    """

    class Meta:
        model = Alias
        fields = ['id', 'alias']

class ExerciseCategorySerializer(serializers.ModelSerializer):
    """
    ExerciseCategory serializer
    """

    class Meta:
        model = ExerciseCategory
        fields = ['id', 'name']

class MuscleSerializer(serializers.ModelSerializer):
    """
    Muscle serializer
    """

    class Meta:
        model = Muscle
        fields = [
            'id',
            'name',
            'is_front',
            'image_url_main',
            'image_url_secondary',
        ]

class ExerciseSerializer(serializers.ModelSerializer):
    """
    Exercise serializer

    The fields from the new ExerciseBase are retrieved here as to retain
    compatibility with the old model where all the fields where in Exercise.
    """
    category = serializers.PrimaryKeyRelatedField(queryset=ExerciseCategory.objects.all())
    muscles = serializers.PrimaryKeyRelatedField(many=True, queryset=Muscle.objects.all())
    muscles_secondary = serializers.PrimaryKeyRelatedField(many=True, queryset=Muscle.objects.all())
    equipment = serializers.PrimaryKeyRelatedField(many=True, queryset=Equipment.objects.all())
    variations = serializers.PrimaryKeyRelatedField(many=True, queryset=Variation.objects.all())

    class Meta:
        model = Exercise
        fields = (
            "id",
            "uuid",
            "name",
            "exercise_base",
            "description",
            "creation_date",
            "category",
            "muscles",
            "muscles_secondary",
            "equipment",
            "language",
            "license",
            "license_author",
            "variations",
        )

class ExerciseTranslationSerializer(serializers.ModelSerializer):
    """
    Exercise translation serializer
    """

    id = serializers.IntegerField(required=False, read_only=True)
    uuid = serializers.UUIDField(required=False, read_only=True)
    aliases = ExerciseInfoAliasSerializer(source='alias_set', many=True, read_only=True)
    notes = ExerciseCommentSerializer(source='exercisecomment_set', many=True, read_only=True)

    class Meta:
        model = Exercise
        fields = (
            "id",
            "uuid",
            "aliases",
            "name",
            "exercise_base",
            "description",
            "notes",
            "creation_date",
            "language",
            "license",
            "license_author",
        )

class ExerciseInfoSerializer(serializers.ModelSerializer):
    """
    Exercise info serializer
    """

    images = ExerciseImageSerializer(many=True, read_only=True)
    videos = ExerciseVideoSerializer(many=True, read_only=True)
    comments = ExerciseCommentSerializer(source='exercisecomment_set', many=True, read_only=True)
    category = ExerciseCategorySerializer(read_only=True)
    muscles = MuscleSerializer(many=True, read_only=True)
    muscles_secondary = MuscleSerializer(many=True, read_only=True)
    equipment = EquipmentSerializer(many=True, read_only=True)
    variations = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    aliases = ExerciseInfoAliasSerializer(source='alias_set', many=True, read_only=True)

    class Meta:
        model = Exercise
        depth = 1
        fields = [
            "id",
            "name",
            "aliases",
            "uuid",
            "exercise_base_id",
            "description",
            "creation_date",
            "category",
            "muscles",
            "muscles_secondary",
            "equipment",
            "language",
            "license",
            "license_author",
            "images",
            "videos",
            "comments",
            "variations",
        ]

class ExerciseBaseInfoSerializer(serializers.ModelSerializer):
    """
    Exercise base info serializer
    """

    images = ExerciseImageSerializer(source='exerciseimage_set', many=True, read_only=True)
    category = ExerciseCategorySerializer(read_only=True)
    muscles = MuscleSerializer(many=True, read_only=True)
    muscles_secondary = MuscleSerializer(many=True, read_only=True)
    equipment = EquipmentSerializer(many=True, read_only=True)
    exercises = ExerciseTranslationSerializer(many=True, read_only=True)
    variations = serializers.PrimaryKeyRelatedField(read_only=True)

    class Meta:
        model = ExerciseBase
        depth = 1
        fields = [
            "id",
            "uuid",
            "category",
            "muscles",
            "muscles_secondary",
            "equipment",
            "license",
            "license_author",
            "images",
            "exercises",
            "variations",
            "images",
        ]
<|endoftext|>