Skip to content

Commit e2902f1

Browse files
committed
project source files added
1 parent 8af3bf8 commit e2902f1

13 files changed

+722
-0
lines changed

devmateclient/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .client import Client
2+
from .version import version, version_info
3+
4+
__version__ = version
5+
__title__ = 'devmateclient'

devmateclient/api/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .customers import CustomersApiMixin
2+
from .licenses import LicensesApiMixin

devmateclient/api/customers.py

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import logging
2+
3+
from ..errors import IllegalArgumentError
4+
5+
PATH = '/v2/customers'
6+
LICENSES_PATH = '/licenses'
7+
8+
log = logging.getLogger(__name__)
9+
10+
11+
class CustomersApiMixin(object):
12+
def get_customer_by_id(self, customer_id, with_meta=None):
13+
"""
14+
Get existing DevMate customer by given id, or error if input params are incorrect, or customer doesn't exist
15+
:param customer_id: `int` target customer id in DevMate
16+
:param with_meta: `bool` set True to return object with meta info in tuple
17+
:return: customer object in `dict`, or `tuple` with customer and meta info
18+
"""
19+
log.debug('Get customer by id %s, with meta : %s', customer_id, with_meta)
20+
21+
if customer_id is None or customer_id <= 0:
22+
raise IllegalArgumentError('Id should not be negative or 0. Given : {}'.format(customer_id))
23+
24+
return self._dm_get(path='{}/{}'.format(PATH, customer_id), with_meta=with_meta)
25+
26+
def get_customers(self,
27+
with_email=None,
28+
with_first_name=None,
29+
with_last_name=None,
30+
with_company=None,
31+
with_phone=None,
32+
with_address=None,
33+
with_key=None,
34+
with_identifier=None,
35+
with_invoice=None,
36+
with_order_id=None,
37+
with_activation_id=None,
38+
with_limit=None,
39+
with_offset=None,
40+
with_licenses=None,
41+
with_meta=None):
42+
"""
43+
Get existing DevMate customers by given filter params, or error if input params are incorrect
44+
:param with_email: `str` filter customer list by given email (contains)
45+
:param with_first_name: `str` filter customer list by given first name (contains)
46+
:param with_last_name: `str` filter customer list by given last name (contains)
47+
:param with_company: `str` filter customer list by given company name (contains)
48+
:param with_phone: `str` or `int` filter customer list by given phone number (contains)
49+
:param with_address: `str` filter customer list by given address (contains)
50+
:param with_key: `str` or `int` filter customer list by given activation key (equals)
51+
:param with_identifier: `str` or `int` filter customer list by given identifier, e.g. MAC address (contains)
52+
:param with_invoice: `str` or `int` filter customer list by given invoice (contains)
53+
:param with_order_id: `int` filter customer list by given order id (equals)
54+
:param with_activation_id: `int` filter customer list by given activation id (equals)
55+
:param with_limit: `int` max count of customers per page
56+
:param with_offset: `int` offset
57+
:param with_licenses: `bool` set True to include licenses to customer object
58+
:param with_meta: `bool` set True to return object with meta info in tuple
59+
:return: `list` of customer objects in `dict`, or `tuple` with customers and meta info
60+
"""
61+
params = {
62+
'filter[email]': with_email,
63+
'filter[first_name]': with_first_name,
64+
'filter[last_name]': with_last_name,
65+
'filter[company]': with_company,
66+
'filter[phone]': with_phone,
67+
'filter[address]': with_address,
68+
'filter[key]': with_key,
69+
'filter[identifier]': with_identifier,
70+
'filter[order_id]': with_order_id,
71+
'filter[activation_id]': with_activation_id,
72+
'filter[invoice]': with_invoice,
73+
'offset': with_offset,
74+
'limit': with_limit,
75+
'with': 'licenses' if with_licenses else None
76+
}
77+
78+
log.debug('Get customers with params %s, with meta : %s', params, with_meta)
79+
80+
not_none_params = dict((k, v) for k, v in params.items() if v is not None)
81+
82+
return self._dm_get(path=PATH, params=not_none_params, with_meta=with_meta)
83+
84+
def create_customer(self, customer, with_meta=None):
85+
"""
86+
Create new customer in DevMate, or error if input params are incorrect
87+
:param customer: customer data in `dict`, 'email' field should be set in `dict`
88+
:param with_meta: `bool` set True to return object with meta info in tuple
89+
:return: created customer object in `dict`, or `tuple` with customer and meta info
90+
"""
91+
log.debug('Create customer with details %s, with meta : %s', customer, with_meta)
92+
93+
if 'email' not in customer or customer['email'] is None:
94+
raise IllegalArgumentError('"email" field should be set for the customer')
95+
96+
return self._dm_post(path=PATH, json={'data': customer}, with_meta=with_meta)
97+
98+
def update_customer(self, customer, with_meta=None):
99+
"""
100+
Update existing customer in DevMate, or error if input params are incorrect, or customer doesn't exist
101+
:param customer: updated customer data in `dict`, 'id' field should be set in `dict`
102+
:param with_meta: `bool` set True to return object with meta info in tuple
103+
:return: created customer object in `dict`, or `tuple` with customer and meta info
104+
"""
105+
log.debug('Update customer with details %s, with meta : %s', customer, with_meta)
106+
107+
if 'id' not in customer or customer['id'] is None:
108+
raise IllegalArgumentError('Current customer "id" field should be set for the customer')
109+
110+
return self._dm_put(path='{}/{}'.format(PATH, customer['id']), json={'data': customer}, with_meta=with_meta)
111+
112+
def create_license_for_customer(self, customer_id, _license, with_meta=None):
113+
"""
114+
Create new license for existing customer in DevMate by license type id,
115+
or error if input params are incorrect, or customer doesn't exist
116+
:param customer_id: `int` id of customer in DevMate
117+
:param _license: license object in `dict`, 'license_type_id' field should be set in `dict`
118+
:param with_meta: `bool` set True to return object with meta info in tuple
119+
:return: created license object in `dict`, or `tuple` with license and meta info
120+
"""
121+
log.debug('Create license for customer %s with details %s, with meta : %s', customer_id, _license, with_meta)
122+
123+
if customer_id is None or customer_id <= 0:
124+
raise IllegalArgumentError('Id should not be negative or 0. Given : {}'.format(customer_id))
125+
126+
if 'license_type_id' not in _license or _license['license_type_id'] is None:
127+
raise IllegalArgumentError('Current customer "license_type_id" field should be set for the customer')
128+
129+
return self._dm_post(
130+
path='{}/{}{}'.format(PATH, customer_id, LICENSES_PATH),
131+
json={'data': _license},
132+
with_meta=with_meta
133+
)

devmateclient/api/licenses.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import logging
2+
3+
PATH = '/v2/licenses'
4+
RESET_ACTIVATION_PATH = '/reset_first_activation'
5+
6+
log = logging.getLogger(__name__)
7+
8+
9+
class LicensesApiMixin(object):
10+
def reset_first_activation(self, activation_key):
11+
"""
12+
Reset first license activation by given activation key. Raise error if license with given key doesn't exist
13+
:param activation_key: `str` or `int` license activation key
14+
:return: `void`
15+
"""
16+
log.debug('Reset first activation by activation key %s', activation_key)
17+
18+
self._dm_post(path='{}/{}{}'.format(PATH, activation_key, RESET_ACTIVATION_PATH), with_meta=False)

devmateclient/client.py

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import logging
2+
3+
import requests
4+
5+
from . import api
6+
from . import errors
7+
8+
BASE_URL = 'https://public-api.devmate.com'
9+
10+
log = logging.getLogger(__name__)
11+
12+
13+
class Client(requests.Session, api.CustomersApiMixin, api.LicensesApiMixin):
14+
"""
15+
DevMate Public API client.
16+
To initialize this you have to set auth_token, which can be generated in Settings -> API Integration.
17+
You can change auth token in runtime with auth_token property.
18+
"""
19+
def __init__(self, auth_token):
20+
super(Client, self).__init__()
21+
self._auth_token = auth_token
22+
self._base_url = BASE_URL
23+
24+
@property
25+
def auth_token(self):
26+
return self._auth_token
27+
28+
@auth_token.setter
29+
def auth_token(self, auth_token):
30+
self._auth_token = auth_token
31+
32+
def _url(self, path):
33+
return '{base_url}{path}'.format(base_url=self._base_url, path=path)
34+
35+
def _add_auth_header(self, req_kwargs):
36+
if self._auth_token is not None:
37+
log.debug('Add auth token %s to header with request params %s', self._auth_token, req_kwargs)
38+
39+
token_header = {'Authorization': 'Token {}'.format(self._auth_token)}
40+
41+
if not req_kwargs.get('headers'):
42+
req_kwargs['headers'] = token_header
43+
else:
44+
req_kwargs['headers'].update(token_header)
45+
46+
@staticmethod
47+
def _check_dm_errors(response):
48+
log.debug('Check errors for response %s', response)
49+
50+
if 200 <= response.status_code < 300:
51+
return
52+
elif response.status_code == 400:
53+
error = errors.IncorrectParamsError(response)
54+
elif response.status_code == 404:
55+
error = errors.NotFoundError(response)
56+
elif response.status_code == 409:
57+
error = errors.ConflictError(response)
58+
elif 400 <= response.status_code < 500:
59+
error = errors.DevMateClientError(response)
60+
elif 500 <= response.status_code < 600:
61+
error = errors.DevMateServerError(response)
62+
else:
63+
error = errors.DevMateRequestError(response)
64+
65+
try:
66+
json = response.json()
67+
error.dm_errors = json['errors']
68+
except Exception:
69+
error.dm_errors = []
70+
71+
raise error
72+
73+
@staticmethod
74+
def _is_application_json(headers):
75+
return 'Content-Type' in headers and headers['Content-Type'] == 'application/json'
76+
77+
def _extract_data(self, response, with_meta):
78+
log.debug('Extract data from response %s, with meta : %s', response, with_meta)
79+
80+
if not self._is_application_json(response.headers):
81+
log.debug('Response doesn\'t contain JSON, return response %s', response)
82+
return response
83+
84+
try:
85+
json = response.json()
86+
87+
if 'data' not in json:
88+
log.debug('Return whole JSON %s', json)
89+
return json
90+
91+
if with_meta:
92+
data = json['data']
93+
meta = json['meta'] if 'meta' in json else None
94+
log.debug('Return JSON data %s and meta %s', data, meta)
95+
return data, meta
96+
else:
97+
data = json['data']
98+
log.debug('Return JSON data %s', data)
99+
return data
100+
except Exception as e:
101+
log.debug('Error on parsing json %s, return response', e, response)
102+
return response
103+
104+
def _dm_request(self, method, path, with_meta, **kwargs):
105+
url = self._url(path)
106+
self._add_auth_header(kwargs)
107+
log.debug('Send %s request to %s with args %s', method, url, kwargs)
108+
response = self.request(method=method, url=url, **kwargs)
109+
self._check_dm_errors(response)
110+
return self._extract_data(response, with_meta=with_meta)
111+
112+
def _dm_get(self, path, with_meta, **kwargs):
113+
return self._dm_request('GET', path, with_meta, **kwargs)
114+
115+
def _dm_post(self, path, with_meta, **kwargs):
116+
return self._dm_request('POST', path, with_meta, **kwargs)
117+
118+
def _dm_put(self, path, with_meta, **kwargs):
119+
return self._dm_request('PUT', path, with_meta, **kwargs)

devmateclient/consts.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class LicenseStatus(object):
2+
NOT_USED = 1
3+
ACTIVE = 2
4+
EXPIRED = 3
5+
BLOCKED = 4
6+
RETURNED = 5
7+
8+
9+
class HistoryRecordType(object):
10+
ACTIVATION = 1
11+
CREATING = 2
12+
EXPIRING = 3
13+
BLOCKING = 4
14+
RETURNING = 5
15+
RESETTING = 6

devmateclient/errors.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
class DevMateError(Exception):
2+
"""
3+
Base DevMate error.
4+
"""
5+
pass
6+
7+
8+
class IllegalArgumentError(DevMateError):
9+
"""
10+
Raised if Illegal Argument passed to the function before request executed.
11+
"""
12+
pass
13+
14+
15+
class DevMateRequestError(DevMateError):
16+
"""
17+
Base DevMate request error. Raised if request has been failed.
18+
Has dm_errors property with request error explanation.
19+
"""
20+
dm_errors = []
21+
22+
23+
class DevMateClientError(DevMateRequestError):
24+
"""
25+
DevMate request error for Client errors (status code 400 - 499).
26+
Has dm_errors property with request error explanation.
27+
"""
28+
pass
29+
30+
31+
class DevMateServerError(DevMateRequestError):
32+
"""
33+
DevMate request error for Server errors (status code 500 - 599).
34+
Has dm_errors property with request error explanation.
35+
"""
36+
pass
37+
38+
39+
class IncorrectParamsError(DevMateClientError):
40+
"""
41+
DevMate request error for status code 400. Raised if incorrect params have been given.
42+
Has dm_errors property with request error explanation.
43+
"""
44+
pass
45+
46+
47+
class NotFoundError(DevMateClientError):
48+
"""
49+
DevMate request error for status code 404. Raised if resource hasn't been found.
50+
Has dm_errors property with request error explanation.
51+
"""
52+
pass
53+
54+
55+
class ConflictError(DevMateClientError):
56+
"""
57+
DevMate request error for status code 409. Raised if already existed unique param has been given.
58+
Has dm_errors property with request error explanation.
59+
"""
60+
pass

devmateclient/version.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
version = "1.0.0"
2+
version_info = tuple([int(d) for d in version.split("-")[0].split(".")])

tests/__init__.py

Whitespace-only changes.

tests/base.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import json
2+
import unittest
3+
4+
import devmateclient
5+
6+
APPLICATION_JSON = 'application/json'
7+
8+
9+
def request_as_json(pretty_request):
10+
return json.loads(pretty_request.body.decode("utf-8"))
11+
12+
13+
class BaseApiTest(unittest.TestCase):
14+
def setUp(self):
15+
self.token = 'TEST_TOKEN'
16+
self.client = devmateclient.Client(self.token)
17+
18+
def tearDown(self):
19+
self.client.close()

0 commit comments

Comments
 (0)