Skip to content
Browse files

small tweaks

  • Loading branch information...
matthewcornell committed Sep 10, 2019
1 parent 31f2d2e commit e348423606af22f99c7074ba7ae7576dc375b000
Showing with 43 additions and 34 deletions.
  1. +23 −19 tests/
  2. +20 −15 zoltpy/
@@ -1,34 +1,38 @@

import unittest
from unittest.mock import patch
from unittest.mock import patch, MagicMock

from zoltpy.connection import ZoltarConnection, ZoltarSession

# MOCK_TOKEN is an expired token as returned by zoltar. decoded contents:
# - header: {"typ": "JWT", "alg": "HS256"}
# - payload: {"user_id": 3, "username": "model_owner1", "exp": 1558442805, "email": ""}
# - expiration:
# 05/21/2019 @ 12:46pm (UTC)
# 2019-05-21T12:46:45+00:00 (ISO 8601)
# Tuesday, May 21, 2019 12:46:45 PM (GMT)
# datetime(2019, 5, 21, 12, 46, 45) (python): datetime.utcfromtimestamp(1558442805)
MOCK_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjozLCJ1c2VybmFtZSI6Im1vZGVsX293bmVyMSIsImV4cCI6MTU1ODQ0MjgwNSwiZW1haWwiOiIifQ.o03V2RxkFpA5ThhRAidwDWCdcQNeJzr1wwFkOFKUI74"

class TestConnection(unittest.TestCase):

def test_authenticate(self):
host = ''
conn = ZoltarConnection(host)

def test_authenticate(self):
conn = ZoltarConnection('')
u = 'username'
p = 'password'
token = '''eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM
with patch.object(ZoltarSession, '_get_token', return_value=token) as mock_method:
# with patch('zoltpy.connection.ZoltarSession._get_token', return_value=token) as mock_method:
with patch('', return_value=MagicMock()) as post_mock:
post_mock.return_value.status_code = 200
post_mock.return_value.json = MagicMock(return_value={'token': MOCK_TOKEN})
conn.authenticate(u, p)
print('XX', conn.session.token)
self.assertEqual(u, conn.username)
self.assertEqual(p, conn.password)
self.assertIsInstance(conn.session, ZoltarSession)

def test_is_token_expired(self): # todo xx
self.assertEqual(MOCK_TOKEN, conn.session.token)
post_mock.assert_called_once_with('/api-token-auth/', {'username': 'username', 'password': 'password'})

if __name__ == '__main__':
@@ -9,14 +9,14 @@

class ZoltarConnection:
Represents a connection to a Zoltar server. This is an object-oriented interface that may be best suited to zoltpy
developers. See the `util` module for a name-based non-OOP interface.
# notes:
# - implement is_token_expired()
# - get, upload, delete should call re_authenticate_if_necessary()
# - needs tests! should be straightforward to mock internal ZoltarResource json responses
# - incomplete - ZoltarResource children only have a few properties implemented
# - caches resource json, but doesn't automatically handle becoming stale, refreshing, etc.
# - no back-pointers are stored, e.g., Model -> owning Project
- This implementation uses the simple approach of caching the JSON response for resource URLs, but doesn't
automatically handle their becoming stale, hence the need to call ZoltarResource.refresh().

def __init__(self, host=''):
@@ -37,14 +37,16 @@ def re_authenticate_if_necessary(self):

def projects(self): # entry point into ZoltarResources. NB: hits API
def projects(self):
The entry point into ZoltarResources. Returns a list of Projects. NB: A property, but hits the API.
# NB: here we are throwing away each project's json, which is here because the API returns json objects for
# projects rather than just URIs
return [Project(self, project_json['url']) for project_json in
self._json_for_uri( + '/api/projects/')]
return [Project(self, project_json['url']) for project_json in self.json_for_uri( + '/api/projects/')]

def _json_for_uri(self, uri):
def json_for_uri(self, uri):
if not self.session:
raise RuntimeError('_validate_authentication(): no session')

@@ -86,16 +88,19 @@ def is_token_expired(self):

class ZoltarResource(ABC):
An abstract proxy for a Zoltar object at a particular URI including its JSON. NB: subclasses not meant to be
directly instantiated by users.
An abstract proxy for a Zoltar object at a particular URI including its JSON. All it does is cache JSON from a
URI. Notes:
- This class and its subclasses are not meant to be directly instantiated by users. Instead the user enters through
ZoltarConnection.projects and then drills down.
- Because the JSON is cached, it will become stale after the source object in the server changes, such as when a
model's forecasts change. Thus, use refresh() as needed.

def __init__(self, zoltar_connection, uri): # NB: hits API
self.zoltar_connection = zoltar_connection
self.uri = uri
self.json = None # cached -> can become stale!


@@ -105,7 +110,7 @@ def id(self):

def refresh(self):
self.json = self.zoltar_connection._json_for_uri(self.uri)
self.json = self.zoltar_connection.json_for_uri(self.uri)

def delete(self):

0 comments on commit e348423

Please sign in to comment.
You can’t perform that action at this time.