<repo_name>thompsonson/beddit-python<file_sep>/README.rst

beddit-python
============================
.. image:: https://travis-ci.org/giginet/beddit-python.svg?branch=master
    :target: https://travis-ci.org/giginet/beddit-python
.. image:: https://coveralls.io/repos/github/giginet/beddit-python/badge.svg?branch=master 
    :target: https://coveralls.io/github/giginet/beddit-python?branch=master

API Client for Beddit_ in Python.

.. _Beddit: http://www.beddit.com

Read `API Documentation`_ for detail.

.. _API Documentation: https://github.com/beddit/beddit-api

Installation
---------------------

.. code:: sh

  pip install beddit-python


Usage
--------------

List sleep scores per day
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python

  from datetime import datetime
  from beddit.client import BedditClient

  client = BedditClient('<EMAIL>', password)

  start_date = datetime(2016, 7, 1)
  end_date = datetime(2016, 7, 31)

  sleeps = client.get_sleeps(start=start_date, end=end_date)
  for sleep in sleeps:
      print(sleep.date.strftime('%Y-%m-%d'), sleep.property.total_sleep_score)


.. code:: txt

  2016-07-01 75
  2016-07-02 92
  ....

Get user information
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python

  import os
  from beddit.client import BedditClient

  client = BedditClient(os.environ.get('BEDDIT_USERNAME'), os.environ.get('BEDDIT_PASSWORD'))

  user = client.get_user()
  print(user.name)
  print(user.height)
  print(user.weight)



Supported Python
------------------------

Python 2.7, 3.3, 3.4, 3.5

LICENSE
----------------

MIT License

<file_sep>/beddit/group.py
from datetime import datetime
from .user import User


class PendingInvite(object):
    def __init__(self, response_object):
        self.created = datetime.utcfromtimestamp(response_object['created'])
        self.created_by = response_object['created_by']
        self.email = response_object['email']


class Group(object):
    def __init__(self, response_object):
        self.id = int(response_object['id'])
        self.created = datetime.utcfromtimestamp(response_object['created'])
        self.members = [User(user) for user in response_object['members']]
        self.pending_invites = [PendingInvite(invitation) for invitation in response_object['pending_invites']]
<file_sep>/.coveragerc
[report]
include =
    beddit/*.py
    tests/*.py
omit =
    beddit/__init__.py
    tests/__init__.py
    */compatibility.py
exclude_lines =
    pragma: no cover
    if __name__ == .__main__.:
<file_sep>/tests/test_group.py
import unittest
import json
import os
from datetime import datetime
from beddit.group import Group


BASE_DIR = os.path.dirname(__file__)


class GroupTest(unittest.TestCase):
    @property
    def group_response(self):
        return json.load(open(os.path.join(BASE_DIR, 'fixtures/group.json')))

    def test_group(self):
        group = Group(self.group_response)
        self.assertEqual(group.id, 1234)
        self.assertEqual(group.created, datetime.utcfromtimestamp(1371472503.646541))
        self.assertEqual(len(group.members), 1)
        self.assertEqual(len(group.pending_invites), 1)

        pending_invite = group.pending_invites[0]
        self.assertEqual(pending_invite.created, datetime.utcfromtimestamp(1371472503.646541))
        self.assertEqual(pending_invite.created_by, 132)
        self.assertEqual(pending_invite.email, '<EMAIL>')
<file_sep>/tests/test_session.py
import unittest
import json
import os
from datetime import datetime
from pytz import timezone
from beddit.session import Session


BASE_DIR = os.path.dirname(__file__)


class SessionTest(unittest.TestCase):
    @property
    def session_response(self):
        return json.load(open(os.path.join(BASE_DIR, 'fixtures/session.json')))

    def test_session(self):
        raw = self.session_response[0]
        session = Session(raw)
        jst = timezone('Asia/Tokyo')
        self.assertEqual(session.timezone, jst)
        self.assertEqual(session.id, raw['id'])
        self.assertEqual(session.start, datetime.fromtimestamp(raw['start_timestamp'], tz=jst))
        self.assertEqual(session.end, datetime.fromtimestamp(raw['end_timestamp'], tz=jst))
        self.assertEqual(session.updated, datetime.fromtimestamp(raw['updated'], tz=jst))
        self.assertEqual(session.hardware, raw['hardware'])
        self.assertEqual(session.frame_length, raw['frame_length'])
        self.assertEqual(session.error_code, raw['error_code'])
        self.assertEqual(session.sampled_tracks, {})

        self.assertEqual(len(session.respiration_cycle_amplitudes), 7976)
        self.assertEqual(len(session.heartbeat), 5963)
        self.assertEqual(len(session.heart_rate), 460)
        self.assertEqual(len(session.signal_high_percentile), 544)
        self.assertEqual(len(session.repiration_cycles), 0)
        self.assertEqual(len(session.events), 2)
        self.assertEqual(len(session.actigram), 744)
        self.assertEqual(len(session.sensor_status), 1)
        self.assertEqual(len(session.snoring_events), 10)
        self.assertEqual(len(session.activity_segment_length), 303)
        self.assertEqual(len(session.high_activity_intervals), 113)
        self.assertEqual(len(session.activity_segment_variation), 303)

    def test_sampled_tracks(self):
        response = json.load(open(os.path.join(BASE_DIR, 'fixtures/session_sample.json')))
        session = Session(response)
        tracks = session.sampled_tracks
        self.assertEqual(tracks['normal'].samples_per_frame, 28)
        self.assertEqual(tracks['normal'].data_url, 'https://bedditcloud-sleepdata...')
        self.assertEqual(tracks['normal'].data_type, 'uint16')

        self.assertEqual(tracks['noise'].samples_per_frame, 2)
        self.assertEqual(tracks['noise'].data_url, 'https://bedditcloud-sleepdata...')
        self.assertEqual(tracks['noise'].data_type, 'float32')

<file_sep>/beddit/sleep.py
from datetime import datetime
from enum import Enum
from pytz import timezone


class SleepStage(Enum):
    Away = 65
    Sleep = 83
    RestlessSleep = 82
    Awake = 87
    NoSignal = 78
    GapInMeasurement = 71


class Presence(Enum):
    Away = 65
    Present = 80
    End = 78


class Sleep(object):
    class Property(object):
        def __init__(self, properties):
            for p in properties:
                score = int(properties[p])
                setattr(self, p, score)

    def __init__(self, response_object):
        self.timezone = timezone(response_object['timezone'])
        self.date = datetime.strptime(response_object['date'], '%Y-%m-%d').replace(tzinfo=self.timezone)
        self.start = datetime.fromtimestamp(response_object['start_timestamp'], tz=self.timezone)
        self.end = datetime.fromtimestamp(response_object['end_timestamp'], tz=self.timezone)
        self.session_range_start = datetime.fromtimestamp(response_object['session_range_start'], tz=self.timezone)
        self.session_range_end = datetime.fromtimestamp(response_object['session_range_end'], tz=self.timezone)
        self.updated = datetime.fromtimestamp(response_object['updated'], tz=self.timezone)

        # properties
        self.property = self.Property(response_object['properties'])

        time_value_tracks = response_object['time_value_tracks']
        self.actigram = {datetime.fromtimestamp(float(timestamp), tz=self.timezone): float(value)
                         for timestamp, value in time_value_tracks['actigram_epochwise']['items']}
        self.sleep_event = {datetime.fromtimestamp(float(timestamp), tz=self.timezone): SleepStage(value)
                            for timestamp, value in time_value_tracks['sleep_stages']['items']}
        self.presence = {datetime.fromtimestamp(float(timestamp), tz=self.timezone): Presence(value)
                         for timestamp, value in time_value_tracks['presence']['items']}
        self.snoring_episodes = {datetime.fromtimestamp(float(timestamp), tz=self.timezone): value
                                 for timestamp, value in time_value_tracks['presence']['items']}

        self.nap_periods = {datetime.fromtimestamp(float(timestamp), tz=self.timezone): float(value)
                            for timestamp, value in time_value_tracks['nap_periods']['items']}
        self.heart_rate_curve = {datetime.fromtimestamp(float(timestamp), tz=self.timezone): float(value)
                                 for timestamp, value in time_value_tracks['heart_rate_curve']['items']}
        self.sleep_cycles = {datetime.fromtimestamp(float(timestamp), tz=self.timezone): float(value)
                             for timestamp, value in time_value_tracks['sleep_cycles']['items']}
<file_sep>/beddit/user.py
from datetime import datetime, timedelta
from enum import Enum


class Sex(Enum):
    Unknown = None
    Male = 'male'
    Female = 'female'


class User(object):
    def __init__(self, response_object):
        self.id = response_object['id']
        self.email = response_object['email']
        self.name = response_object['name']
        self.date_of_birth = datetime.strptime(response_object['date_of_birth'], '%Y-%m-%d')
        self.sex = Sex(response_object['sex'])
        self.weight = float(response_object['weight'])
        self.height = float(response_object['height'])
        self.sleep_time_goal = timedelta(seconds=response_object['sleep_time_goal'])
        self.tip_audiences = response_object['tip_audiences']
        self.created = datetime.utcfromtimestamp(response_object['created'])
        self.updated = datetime.utcfromtimestamp(response_object['updated'])
<file_sep>/tox.ini
[tox]
envlist =
  py{27,33,34,35}

[testenv]
basepython =
    py27: python2.7
    py33: python3.3
    py34: python3.4
    py35: python3.5
deps=
    py27: enum34
    py27: mock
    py33: enum34
    -rrequirements.txt
    coverage
commands =
    {envbindir}/coverage run --source=beddit runtests.py []
    coverage report

<file_sep>/tests/__init__.py
from tests import test_client
from tests import test_group
from tests import test_session
from tests import test_sleep
from tests import test_user
<file_sep>/tests/test_sleep.py
import unittest
import json
import os
from datetime import datetime
from pytz import timezone
from beddit.sleep import Sleep


BASE_DIR = os.path.dirname(__file__)


class SleepTest(unittest.TestCase):
    @property
    def sleep_response(self):
        return json.load(open(os.path.join(BASE_DIR, 'fixtures/sleep.json')))

    def test_sleep(self):
        raw = self.sleep_response[0]
        sleep = Sleep(raw)
        jst = timezone('Asia/Tokyo')
        self.assertEqual(sleep.timezone, jst)
        self.assertEqual(sleep.date, datetime(2016, 8, 16).replace(tzinfo=jst))
        self.assertEqual(sleep.start, datetime.fromtimestamp(raw['start_timestamp'], tz=jst))
        self.assertEqual(sleep.end, datetime.fromtimestamp(raw['end_timestamp'], tz=jst))
        self.assertEqual(sleep.session_range_start, datetime.fromtimestamp(raw['session_range_start'], tz=jst))
        self.assertEqual(sleep.session_range_end, datetime.fromtimestamp(raw['session_range_end'], tz=jst))
        self.assertEqual(sleep.updated, datetime.fromtimestamp(raw['updated'], tz=jst))

        self.assertEqual(len(sleep.actigram), 497)
        self.assertEqual(len(sleep.sleep_event), 21)
        self.assertEqual(len(sleep.presence), 20)
        self.assertEqual(len(sleep.snoring_episodes), 20)
        self.assertEqual(len(sleep.nap_periods), 0)
        self.assertEqual(len(sleep.heart_rate_curve), 89)
        self.assertEqual(len(sleep.sleep_cycles), 146)

        properties = [
            "sleep_latency",
            "sleep_time_target",
            "total_sleep_score",
            "sensor_status",
            "sleep_score_version",
            "short_term_average_respiration_rate",
            "primary_sleep_period_away_episode_duration",
            "primary_sleep_period_away_episode_count",
            "total_nap_duration",
            "average_respiration_rate",
            "total_snoring_episode_duration",
            "activity_index",
            "signal_amplitude",
            "short_term_resting_heart_rate",
            "evening_HRV_index",
            "stage_duration_W",
            "clipping_duration",
            "single_person_setup",
            "resting_heart_rate",
            "all_night_HRV_index",
            "stage_duration_N",
            "stage_duration_A",
            "stage_duration_G",
            "morning_HRV_index",
            "stage_duration_R",
            "stage_duration_S",
            "sleep_efficiency"
        ]
        for p in properties:
            self.assertTrue(hasattr(sleep.property, p))
<file_sep>/beddit/client.py
import requests
import time
import json
from .user import User
from .group import Group
from .sleep import Sleep
from .session import Session
from .compatibility import urljoin


def auth_required(func):
    def _is_logged_in(self, *args, **kwargs):
        if not self.access_token or not self.user_id:
            raise BedditClient.AuthError('authentication is required')
        return func(self, *args, **kwargs)
    return _is_logged_in


def datetime_to_timestamp(datetime):
    return time.mktime(datetime.timetuple())


class BedditClient(object):
    class ArgumentError(BaseException):
        pass

    class AuthError(BaseException):
        pass

    class APIError(BaseException):
        pass

    BASE_URL = 'https://cloudapi.beddit.com'
    access_token = None
    user_id = None

    @classmethod
    def build_full_path(cls, path):
        return urljoin(cls.BASE_URL, path)

    def __init__(self, username=None, password=<PASSWORD>, access_token=None, user_id=None):
        if access_token and user_id:
            self.user_id = user_id
            self.access_token = access_token
        elif username and password:
            payload = {
                'grant_type': 'password',
                'username': username,
                'password': <PASSWORD>
            }
            endpoint = BedditClient.build_full_path('/api/v1/auth/authorize')
            r = requests.post(endpoint, data=payload)
            if r.status_code == 200:
                response_object = r.json()
                self.access_token = response_object['access_token']
                self.user_id = response_object['user']
            else:
                response = r.json()
                if response['description']:
                    raise BedditClient.AuthError(response['description'])
                else:
                    raise BedditClient.AuthError('Authentication is failed')
        else:
            raise BedditClient.ArgumentError('you must either use both access_token and user_id or both username and password')

    @property
    def _headers(self):
        return {'Authorization': "UserToken {}".format(self.access_token)}

    def _get_with_auth(self, path, params={}):
        endpoint = BedditClient.build_full_path(path)
        r = requests.get(endpoint,
                         params=params,
                         headers=self._headers)
        return r

    def _post_with_auth(self, path, params={}):
        endpoint = BedditClient.build_full_path(path)
        r = requests.post(endpoint,
                          data=params,
                          headers=self._headers)
        return r

    def _put_with_auth(self, path, params={}):
        endpoint = BedditClient.build_full_path(path)
        r = requests.put(endpoint,
                         data=params,
                         headers=self._headers)
        return r

    @auth_required
    def get_sleeps(self, user_id=None, start=None, end=None, updated_after=None, limit=None, reverse=False):
        params = {}
        if start:
            params['start_date'] = start.strftime('%Y-%m-%d')
        if end:
            params['end_date'] = end.strftime('%Y-%m-%d')
        if updated_after:
            params['updated_after'] = updated_after.timestamp()
        if limit:
            params['limit'] = limit
        if reverse:
            params['reverse'] = 'yes'

        if not user_id:
            user_id = self.user_id
        path = "/api/v1/user/{}/sleep".format(user_id)
        r = self._get_with_auth(path, params=params)
        if r.status_code == 200:
            return [Sleep(sleep) for sleep in r.json()]
        else:
            raise BedditClient.APIError(r.json()['description'])

    @auth_required
    def get_sessions(self, start=None, end=None, updated_after=None):
        params = {}

        if updated_after and not start and not end:
            params['updated_after'] = datetime_to_timestamp(updated_after)
        elif not updated_after and start and end:
            params['start_timestamp'] = datetime_to_timestamp(start)
            params['end_timestamp'] = datetime_to_timestamp(end)
        else:
            raise BedditClient.ArgumentError('you must either use the updated_after or both start and end.')

        path = "/api/v1/user/{}/session".format(self.user_id)
        r = self._get_with_auth(path, params=params)
        if r.status_code == 200:
            return [Session(session) for session in r.json()]
        else:
            raise BedditClient.APIError(r.json()['description'])

    @classmethod
    def reset_password(cls, email):
        path = "/api/v1/user/password_reset"
        endpoint = BedditClient.build_full_path(path)
        r = requests.post(endpoint, data={'email': email})
        if r.status_code == 200:
            return True
        else:
            raise BedditClient.APIError(r.status_code)

    @auth_required
    def get_user(self, user_id=None):
        if not user_id:
            user_id = self.user_id

        path = "/api/v1/user/{}".format(user_id)
        r = self._get_with_auth(path)
        if r.status_code == 200:
            return User(r.json())
        else:
            raise BedditClient.APIError(r.json()['description'])

    @auth_required
    def update_user(self, user_id=None, **params):
        if not user_id:
            user_id = self.user_id

        path = "/api/v1/user/{}".format(user_id)
        r = self._put_with_auth(path, params=json.dumps(params))
        if r.status_code == 200:
            return User(r.json())
        else:
            raise BedditClient.APIError(r.json()['description'])

    @auth_required
    def get_groups(self, user_id=None):
        if not user_id:
            user_id = self.user_id

        path = "/api/v1/user/{}/group".format(user_id)
        r = self._get_with_auth(path)
        if r.status_code == 200:
            return [Group(group) for group in r.json()]
        else:
            raise BedditClient.APIError(r.json()['description'])

    @auth_required
    def invite_to_group(self, email, group_id=None):
        if group_id:
            path = "/api/v1/group/{}/invite".format(group_id)
        else:
            path = "/api/v1/group/new/invite"

        r = self._post_with_auth(path, params={'email': email})
        if r.status_code == 200:
            return [Group(group) for group in r.json()]
        else:
            raise BedditClient.APIError(r.json()['description'])

    @auth_required
    def remove_group_invite(self, group_id, user_id):
        path = '/api/v1/group/{}/member/{}/remove'.format(group_id, user_id)
        r = self._post_with_auth(path)
        if r.status_code == 200:
            return [Group(group) for group in r.json()]
        else:
            raise BedditClient.APIError(r.json()['description'])
<file_sep>/beddit/session.py
from datetime import datetime, timedelta
from enum import Enum
from pytz import timezone


class SensorStatus(Enum):
    Operational = 1
    Interference = 2
    Unclear = 3


class SampledTrack(object):
    def __init__(self, obj):
        self.samples_per_frame = obj['samples_per_frame']
        self.data_url = obj.get('data_url', None)
        self.data_type = obj['data_type']


class Session(object):
    def __init__(self, response_object):
        self.timezone = timezone(response_object['timezone'])
        self.id = response_object['id']
        self.start = datetime.fromtimestamp(response_object['start_timestamp'], tz=self.timezone)
        self.end = datetime.fromtimestamp(response_object['end_timestamp'], tz=self.timezone)

        self.hardware = response_object.get("hardware", None)

        self.software = response_object["software"]
        self.frame_length = response_object.get("frame_length", None)
        self.error_code = response_object.get("error_code", None)
        self.sampled_tracks = {key: SampledTrack(value) for key, value in response_object.get('sampled_tracks', {}).items()}

        time_value_tracks = response_object['time_value_tracks']

        def parse(name, initializer=float):
            if name in time_value_tracks:

                def datetime_from_start(timestamp):
                    td = timedelta(seconds=timestamp)
                    return self.start + td

                value = {datetime_from_start(float(timestamp)): initializer(v)
                         for timestamp, v in time_value_tracks[name]['items']}
                setattr(self, name, value)
            else:
                setattr(self, name, [])
        parse('respiration_cycle_amplitudes')
        parse('heartbeat')
        parse('heart_rate')
        parse('signal_high_percentile')
        parse('repiration_cycles')
        parse('events')
        parse('actigram')
        parse('sensor_status', initializer=SensorStatus)
        parse('snoring_events')
        parse('activity_segment_length')
        parse('high_activity_intervals')
        parse('activity_segment_variation')

        self.updated = datetime.fromtimestamp(response_object['updated'], tz=self.timezone)
<file_sep>/tests/test_client.py
import unittest
import os
import json
import datetime
from tests.compatibility import patch, Mock
from beddit.client import BedditClient
from beddit.sleep import Sleep
from beddit.session import Session
from beddit.user import User
from beddit.group import Group

BASE_DIR = os.path.dirname(__file__)


def authenticated(testcase):
    def _inner(self, *args, **kwargs):
        response = Mock()
        response.json.side_effect = lambda: {'user': 10000, 'access_token': 'dummytoken'}
        response.status_code = 200
        payload = {
            'grant_type': 'password',
            'username': 'username',
            'password': '<PASSWORD>'
        }
        with patch('requests.post', return_value=response) as post:
            retval = testcase(self, *args, **kwargs)
            post.assert_called_with(BedditClient.build_full_path('api/v1/auth/authorize'), data=payload)
            return retval
    return _inner


class BedditClientTest(unittest.TestCase):
    @property
    def _default_client(self):
        return BedditClient('username', 'password')

    @authenticated
    def test_authenticate(self):
        client = self._default_client

        self.assertEqual(client.user_id, 10000)
        self.assertEqual(client.access_token, 'dummytoken')

    def test_authentication_error(self):
        response = Mock()
        response.status_code = 400
        response.json.side_effect = lambda: {'description': 'auth_error'}
        with patch('requests.post', return_value=response):
            self.assertRaises(BedditClient.AuthError, BedditClient, 'username', 'password')

    def test_argument_error(self):
        self.assertRaises(BedditClient.ArgumentError, BedditClient, 'username')

    @authenticated
    def test_get_sleep(self):
        client = self._default_client
        timestamp = 1471761649

        endpoint = BedditClient.build_full_path('/api/v1/user/10000/sleep')
        response = Mock()
        response.status_code = 200
        sleep_object = json.load(open(os.path.join(BASE_DIR, 'fixtures/sleep.json')))
        response.json = lambda: sleep_object
        with patch('requests.get', return_value=response) as get:
            sleeps = client.get_sleeps(
                start=datetime.datetime.utcfromtimestamp(timestamp),
                end=datetime.datetime.utcfromtimestamp(timestamp),
                limit=10,
                reverse=True
            )
            self.assertEqual(len(sleeps), 1)
            self.assertEqual(type(sleeps[0]), Sleep)

            args, kwargs = get.call_args
            self.assertEqual(args[0], endpoint)
            self.assertDictEqual(kwargs['params'], {
                'start_date': '2016-08-21',
                'end_date': '2016-08-21',
                'limit': 10,
                'reverse': 'yes'
            })

    @authenticated
    def test_get_session(self):
        client = self._default_client
        timestamp = 1471761649

        endpoint = BedditClient.build_full_path('/api/v1/user/10000/session')
        response = Mock()
        response.status_code = 200
        session_object = json.load(open(os.path.join(BASE_DIR, 'fixtures/session.json')))
        response.json = lambda: session_object
        dt = datetime.datetime.fromtimestamp(timestamp)
        with patch('requests.get', return_value=response) as get:
            sessions = client.get_sessions(updated_after=dt)
            self.assertEqual(len(sessions), 1)
            self.assertEqual(type(sessions[0]), Session)

            args, kwargs = get.call_args
            self.assertEqual(args[0], endpoint)
            self.assertDictEqual(kwargs['params'], {
                'updated_after': timestamp
            })

    @authenticated
    def test_get_session_with_invalid_argument(self):
        client = self._default_client
        self.assertRaises(BedditClient.ArgumentError, client.get_sessions)
        self.assertRaises(BedditClient.ArgumentError, client.get_sessions, start=datetime.datetime.now)
        self.assertRaises(BedditClient.ArgumentError, client.get_sessions, end=datetime.datetime.now)

    def test_reset_password(self):
        response = Mock()
        response.status_code = 200
        with patch('requests.post', return_value=response) as post:
            result = BedditClient.reset_password('<EMAIL>')
            self.assertTrue(result)

            post.assert_called_once_with(BedditClient.build_full_path('/api/v1/user/password_reset'),
                                         data={'email': '<EMAIL>'})

    @authenticated
    def test_get_user(self):
        client = self._default_client

        endpoint = BedditClient.build_full_path('/api/v1/user/10000')
        response = Mock()
        response.status_code = 200
        user_object = json.load(open(os.path.join(BASE_DIR, 'fixtures/user.json')))
        response.json = lambda: user_object
        with patch('requests.get', return_value=response) as get:
            user = client.get_user()
            self.assertEqual(type(user), User)

            args = get.call_args[0]
            self.assertEqual(args[0], endpoint)

    @authenticated
    def test_update_user(self):
        client = self._default_client

        endpoint = BedditClient.build_full_path('/api/v1/user/10000')
        response = Mock()
        response.status_code = 200
        user_object = json.load(open(os.path.join(BASE_DIR, 'fixtures/user.json')))
        response.json = lambda: user_object
        with patch('requests.put', return_value=response) as put:
            user = client.update_user(name='foo')
            self.assertEqual(type(user), User)

            args = put.call_args[0]
            self.assertEqual(args[0], endpoint)

    @authenticated
    def test_get_group(self):
        client = self._default_client

        endpoint = BedditClient.build_full_path('/api/v1/user/10000/group')
        response = Mock()
        response.status_code = 200
        group_object = json.load(open(os.path.join(BASE_DIR, 'fixtures/group.json')))
        response.json = lambda: [group_object]
        with patch('requests.get', return_value=response) as get:
            groups = client.get_groups()
            self.assertEqual(type(groups[0]), Group)

            args = get.call_args[0]
            self.assertEqual(args[0], endpoint)

    @authenticated
    def test_invite_to_group(self):
        client = self._default_client

        endpoint = BedditClient.build_full_path('/api/v1/group/new/invite')
        response = Mock()
        response.status_code = 200
        group_object = json.load(open(os.path.join(BASE_DIR, 'fixtures/group.json')))
        response.json = lambda: [group_object]
        with patch('requests.post', return_value=response) as post:
            groups = client.invite_to_group(email="<EMAIL>")
            self.assertEqual(type(groups[0]), Group)

            args, kwargs = post.call_args
            self.assertEqual(args[0], endpoint)
            self.assertDictEqual(kwargs['data'], {
                'email': '<EMAIL>'
            })

    @authenticated
    def test_remove_group_invite(self):
        client = self._default_client

        endpoint = BedditClient.build_full_path('/api/v1/group/200/member/10000/remove')
        response = Mock()
        response.status_code = 200
        group_object = json.load(open(os.path.join(BASE_DIR, 'fixtures/group.json')))
        response.json = lambda: [group_object]
        with patch('requests.post', return_value=response) as post:
            groups = client.remove_group_invite(group_id=200, user_id=10000)
            self.assertEqual(type(groups[0]), Group)

            args = post.call_args[0]
            self.assertEqual(args[0], endpoint)
<file_sep>/tests/test_user.py
import unittest
import json
import os
from datetime import datetime, timedelta
from beddit.user import User, Sex


BASE_DIR = os.path.dirname(__file__)


class UserTest(unittest.TestCase):
    @property
    def user_response(self):
        return json.load(open(os.path.join(BASE_DIR, 'fixtures/user.json')))

    def test_user(self):
        user = User(self.user_response)
        self.assertEqual(user.id, 10000)
        self.assertEqual(user.updated, datetime.utcfromtimestamp(1471356194))
        self.assertEqual(user.weight, 60.0)
        self.assertEqual(user.height, 175.0)
        self.assertEqual(user.sleep_time_goal, timedelta(seconds=27000))
        self.assertIn('general', user.tip_audiences)
        self.assertEqual(user.sex, Sex.Unknown)
        self.assertEqual(user.date_of_birth, datetime(1990, 1, 1))
        self.assertEqual(user.email, '<EMAIL>')
        self.assertEqual(user.created, datetime.utcfromtimestamp(1468327763))
