Skip to content

Commit 8a9b112

Browse files
authored
Merge pull request #2 from researchnow/feature/add-authentication
Adds Authentication methods.
2 parents b3a1d05 + 2c9a36d commit 8a9b112

File tree

14 files changed

+292
-46
lines changed

14 files changed

+292
-46
lines changed

.circleci/config.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Python CircleCI 2.0 configuration file
2+
#
3+
# Check https://circleci.com/docs/2.0/language-python/ for more details
4+
#
5+
version: 2
6+
jobs:
7+
build:
8+
docker:
9+
# specify the version you desire here
10+
# use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers`
11+
- image: circleci/python:2.7.17
12+
13+
# Specify service dependencies here if necessary
14+
# CircleCI maintains a library of pre-built images
15+
# documented at https://circleci.com/docs/2.0/circleci-images/
16+
# - image: circleci/postgres:9.4
17+
18+
working_directory: ~/repo
19+
20+
steps:
21+
- checkout
22+
23+
# Download and cache dependencies
24+
- restore_cache:
25+
keys:
26+
- v1-dependencies-{{ checksum "requirements.txt" }}
27+
# fallback to using the latest cache if no exact match is found
28+
- v1-dependencies-
29+
30+
- run:
31+
name: install dependencies
32+
command: |
33+
virtualenv venv
34+
. venv/bin/activate
35+
pip install -r requirements.txt
36+
37+
- save_cache:
38+
paths:
39+
- ./venv
40+
key: v1-dependencies-{{ checksum "requirements.txt" }}
41+
42+
# run tests!
43+
# this example uses Django's built-in test-runner
44+
# other common Python testing frameworks include pytest and nose
45+
# https://pytest.org
46+
# https://nose.readthedocs.io
47+
- run:
48+
name: run tests
49+
command: |
50+
. venv/bin/activate
51+
pytest tests
52+
53+
- store_artifacts:
54+
path: test-reports
55+
destination: test-reports
56+
57+
- store_test_results:
58+
path: test-reports

.env-example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
DYNATA_DEMAND_CLIENT_ID=
2+
DYNATA_DEMAND_USERNAME=
3+
DYNATA_DEMAND_PASSWORD=

CONTRIBUTING.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
### Contributing to python-demandapi-client
2+
3+
This is an InnerSource python project. It is the work of someone who thought it might benefit someone else in the company as well.
4+
5+
### Maintainers
6+
7+
This repository is maintained by
8+
9+
1. [Ridley Larsen](@RidleyLarsen)
10+
11+
## Contributing Code
12+
13+
The best place to start is by looking at the [Samplify Integration board](https://app.asana.com/0/1143497512179452/board), which is for the initial build. If you decide to take on a task make sure to assign it to yourself so we don't duplicate work. A good PR completely resolves the associated Asana issue, passes or python linting, and includes test coverage for your new code. This Github repository is integrated with CircleCI, so a PR cannot be accepted that has merge conflicts, fails to pass linting or tests, or lowers the repository's test coverage. Additionally your PR should include a high level description of your or reviewers will be peppering you with questions, if this is something someone might have a question about in the future then go ahead and add this information to the [docs](docs). Approval of the maintainer is required merge a PR into `dev`, which is where all PRs go.
14+
15+
## Linting
16+
17+
Linting software is strongly recommended to improve code quality and maintain readability in Python projects. Python's official linting package is called pycodestyle, but another useful linting package is called flake8. Flake8 runs three different linters on your code, including pycodestyle, and a package called PyFlakes that checks for things like unused imports.
18+
19+
Read more [here](http://flake8.pycqa.org/en/latest/)
20+
21+
## Testing
22+
23+
## Filing Issues
24+
25+
Please use the [GitHub Issues](https://github.com/researchnow/python-demandapi-client/issues/new) to file an issue.
26+
27+
Thats it.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 Research Now
3+
Copyright (c) 2019 Dynata
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,27 @@
11
# python-demandapi-client
2-
A Python client library for the Dynata Demand API
2+
A Python client library for the [Dynata Demand API](https://developers.dynata.com/)
3+
## Setup
4+
5+
The client requires environment variables to be set for the Dynata Demand API credentials. These can be found in `.env-example`.
6+
7+
## Example Usage
8+
9+
demandapi = DemandAPIClient()
10+
demandapi.authenticate()
11+
demandapi.logout()
12+
13+
## Contributing
14+
15+
Information on [contributing](CONTRIBUTING.md).
16+
17+
## Testing
18+
19+
To run the tests,
20+
21+
virtualenv venv
22+
. venv/bin/activate
23+
pip install -r requirements.txt
24+
pytest tests
25+
deactivate
26+
27+
to run the tests for this project.

demandapi/api.py

Lines changed: 0 additions & 44 deletions
This file was deleted.

dynatademand/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .api import DemandAPIClient # noqa
2+
from .errors import DemandAPIError # noqa

dynatademand/api.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import os
2+
import requests
3+
from .errors import DemandAPIError
4+
5+
6+
class DemandAPIClient(object):
7+
def __init__(self):
8+
self.client_id = os.getenv('DYNATA_DEMAND_CLIENT_ID', None)
9+
self.username = os.getenv('DYNATA_DEMAND_USERNAME', None)
10+
self.password = os.getenv('DYNATA_DEMAND_PASSWORD', None)
11+
if None in [self.client_id, self.username, self.password]:
12+
raise DemandAPIError("All authentication data is required.")
13+
self._access_token = None
14+
self._refresh_token = None
15+
self.base_host = os.getenv('DYNATA_DEMAND_BASE_URL', default='https://api.researchnow.com')
16+
self.auth_base_url = '{}/auth/v1'.format(self.base_host)
17+
self.base_url = '{}/sample/v1'.format(self.base_host)
18+
19+
def _check_authentication(self):
20+
if self._access_token is None:
21+
raise DemandAPIError('The API instance must be authenticated before calling this method.')
22+
23+
def _api_post(self, uri, payload):
24+
# Send an authenticated POST request to an API endpoint.
25+
self._check_authentication()
26+
url = '{}{}'.format(self.base_url, uri)
27+
request_headers = {
28+
'oauth_access_token': self._access_token,
29+
'Content-Type': "application/json",
30+
}
31+
response = requests.post(url=url, json=payload, headers=request_headers)
32+
if response.status_code > 399:
33+
raise DemandAPIError('Demand API request to {} failed with status {}. Response: {}'.format(
34+
url, response.status_code, response.content
35+
))
36+
return response.json()
37+
38+
def authenticate(self):
39+
url = '{}/token/password'.format(self.auth_base_url)
40+
auth_response = requests.post(url, json={
41+
'clientId': self.client_id,
42+
'password': self.password,
43+
'username': self.username,
44+
})
45+
if auth_response.status_code > 399:
46+
raise DemandAPIError('Authentication failed with status {} and error: {}'.format(
47+
auth_response.status_code,
48+
auth_response.json())
49+
)
50+
response_data = auth_response.json()
51+
self._access_token = response_data.get('accessToken')
52+
self._refresh_token = response_data.get('refreshToken')
53+
return response_data
54+
55+
def refresh_access_token(self):
56+
url = '{}/token/refresh'.format(self.auth_base_url)
57+
refresh_response = requests.post(url, json={
58+
'clientId': self.client_id,
59+
'refreshToken': self._refresh_token
60+
})
61+
if refresh_response.status_code != 200:
62+
raise DemandAPIError("Refreshing Access Token failed with status {} and error: {}".format(
63+
refresh_response.status_code, refresh_response.content
64+
))
65+
response_data = refresh_response.json()
66+
self._access_token = response_data.get('accessToken')
67+
self._refresh_token = response_data.get('refreshToken')
68+
return response_data
69+
70+
def logout(self):
71+
url = '{}/logout'.format(self.auth_base_url)
72+
logout_response = requests.post(url, json={
73+
'clientId': self.client_id,
74+
'refreshToken': self._refresh_token,
75+
'accessToken': self._access_token
76+
})
77+
if logout_response.status_code != 204:
78+
raise DemandAPIError("Log out failed with status {} and error: {}".format(
79+
logout_response.status_code, logout_response.content
80+
))
81+
return logout_response.json()
File renamed without changes.

pycodestyle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[pycodestyle]
2+
ignore = E501,E722
3+
statistics = True

0 commit comments

Comments
 (0)