Skip to content

Commit

Permalink
- light refactoring
Browse files Browse the repository at this point in the history
- new API
- more tests
  • Loading branch information
thefab committed Mar 2, 2013
1 parent 1ff0cde commit 0a43e79
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 21 deletions.
9 changes: 9 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# CHANGES

## Release 0.1a2 (alpha)

- `lock_acquire()` returns an URL as string (and not as an `RDLMLock` object)
- `lock_release()` takes a lock URL as string (and not as an `RDLMLocki` object)
- new methods :
- `lock_get()`
- `lock_resource_delete()`
- `lock_resource_delete_all()`

## Release 0.1a1 (alpha)

- first release
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@
client = RDLMClient(server="localhost", port=8888, default_title="classic example", default_lifetime=300, default_wait=10)

# Acquire a lock on resource : foo
lock = client.lock_acquire("foo")
lock_url = client.lock_acquire("foo")

# We have the lock on resource : foo
# [...]

# Try to acquire the same lock another time with a overrided wait timeout of 3 seconds
# => RDLMException
try:
lock2 = client.lock_acquire("foo", wait=3)
lock_url2 = client.lock_acquire("foo", wait=3)
except RDLMException:
print "Can't acquire the lock"

# Release the lock
result = client.lock_release(lock)
result = client.lock_release(lock_url)
if not(result):
print "Can't release the lock"

Expand Down
88 changes: 75 additions & 13 deletions rdlmpy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@
# See the LICENSE file for more information.

import requests
import socket
import getpass
import os
import sys
from requests.auth import HTTPBasicAuth
import json
from rdlmpy.exceptions import RDLMLockWaitExceededException, RDLMLockDeletedException, RDLMLockServerException
from rdlmpy.lock import RDLMLock
from rdlmpy.exceptions import RDLMLockWaitExceededException
from rdlmpy.exceptions import RDLMLockDeletedException
from rdlmpy.exceptions import RDLMLockServerException
from rdlmpy import __version__ as VERSION


class RDLMClient(object):
'''
Expand All @@ -19,7 +28,8 @@ class RDLMClient(object):
_default_lifetime = None
_default_wait = None

def __init__(self, server="localhost", port=8888, default_title=None, default_lifetime=300, default_wait=10):
def __init__(self, server="localhost", port=8888, default_title=None,
default_lifetime=300, default_wait=10):
'''
@summary: constructor
@param server: rdlm server hostname
Expand All @@ -30,26 +40,35 @@ def __init__(self, server="localhost", port=8888, default_title=None, default_li
@result: client object
'''
self._base_url = "http://%s:%i" % (server, port)
self._default_title = default_title
if default_title:
self._default_title = default_title
else:
self._default_title = "%s#%i(%s) with RDLMClient(%s) @%s" % (
getpass.getuser(), os.getpid(), sys.argv[0],
VERSION, socket.gethostname())
self._default_lifetime = default_lifetime
self._default_wait = default_wait

def lock_acquire(self, resource_name, lifetime=None, wait=None, title=None):
def lock_acquire(self, resource_name, lifetime=None, wait=None,
title=None):
'''
@summary: acquires a lock
@param resource_name: name of the resource to lock
@param lifetime: lifetime for the lock (in seconds)
@param wait: wait time for the lock (in seconds)
@param title: title
@result: lock object (if the lock is acquired)
@result: lock url (if the lock is acquired)
If the lock is not acquired, this function can raise :
- a RDLMLockWaitExceededException: can't acquire the lock after "wait" seconds
- a RDLMLockDeletedException: the request has been deleted by an admin request
- a RDLMLockWaitExceededException: can't acquire the lock after
"wait" seconds
- a RDLMLockDeletedException: the request has been deleted by
an admin request
- a RDLMLockServerException: unknown error from the RDLM server
'''
lock_dict = {}
lock_dict['lifetime'] = self._default_lifetime if lifetime is None else lifetime
lock_dict['lifetime'] = self._default_lifetime \
if lifetime is None else lifetime
lock_dict['wait'] = self._default_wait if wait is None else wait
lock_dict['title'] = self._default_title if title is None else title
lock_raw = json.dumps(lock_dict)
Expand All @@ -60,15 +79,58 @@ def lock_acquire(self, resource_name, lifetime=None, wait=None, title=None):
raise RDLMLockDeletedException()
elif r.status_code != 201 or not(r.headers['Location'].startswith('http://')):
raise RDLMLockServerException()
return RDLMLock(url=r.headers['Location'])
return r.headers['Location']

def lock_release(self, lock):
def lock_release(self, lock_url):
'''
@summary: releases a lock
@param lock: the lock object to release
@param lock_url: the lock url to release
@result: True (if ok), False (else)
The lock object is the return value of lock_acquire() method
The lock url is the return value of lock_acquire() method
'''
r = requests.delete(lock_url)
return (r.status_code == 204)

def lock_get(self, lock_url):
'''
@summary: gets informations about a lock
@param lock_url: the lock url
@result: informations dict (or None)
'''
r = requests.get(lock_url)
try:
if r.status_code == 200:
return RDLMLock.factory(lock_url, r.content)
except:
pass
return None

def resource_delete(self, resource_name, username=None, password=None):
'''
@summary: delete all locks on a resource
@param resource_name: name of the resource
@param username: admin http username
@param password: admin http password
@result: True if there were some locks on the resource, False else
'''
if username and password:
auth = HTTPBasicAuth(username, password)
r = requests.delete("%s/resources/%s" % (self._base_url, resource_name), auth=auth)
else:
r = requests.delete("%s/resources/%s" % (self._base_url, resource_name))
return (r.status_code == 204)

def resource_delete_all(self, username=None, password=None):
'''
@summary: delete all locks on all resources
@param username: admin http username
@param password: admin http password
@result: True if ok, False else
'''
r = requests.delete(lock.url)
if username and password:
auth = HTTPBasicAuth(username, password)
r = requests.delete("%s/resources" % self._base_url, auth=auth)
else:
r = requests.delete("%s/resources" % self._base_url)
return (r.status_code == 204)
17 changes: 17 additions & 0 deletions tests/lock1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"uid": "94e4458bad8248828213275ae0b17eae",
"title": "test title",
"active_since": "2013-03-02T22:00:05.555051",
"_links": {
"self": {
"href": "/locks/foo/94e4458bad8248828213275ae0b17eae"
},
"resource": {
"href": "/resources/foo"
}
},
"active_expires": "2013-03-02T22:05:05.555051",
"active": true,
"lifetime": 300,
"wait": 10
}
60 changes: 55 additions & 5 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from rdlmpy import RDLMClient
from rdlmpy import RDLMLockWaitExceededException, RDLMLockDeletedException, RDLMLockServerException
from httpretty import HTTPretty, httprettified
import base64
import os

class TestClient(unittest.TestCase):

Expand Down Expand Up @@ -29,8 +31,8 @@ def _make_lock_object(self):

@httprettified
def test_acquire(self):
(lock, lock_url) = self._make_lock_object()
self.assertEqual(lock.url, lock_url)
(lock_u, lock_url) = self._make_lock_object()
self.assertEqual(lock_u, lock_url)

@httprettified
def _test_acquire_exception(self, status_code, exception):
Expand Down Expand Up @@ -60,11 +62,59 @@ def test_acquire_409(self):

@httprettified
def test_release(self):
(lock, lock_url) = self._make_lock_object()
self.assertEqual(lock.url, lock_url)
(lock_u, lock_url) = self._make_lock_object()
self.assertEqual(lock_u, lock_url)
HTTPretty.register_uri(HTTPretty.DELETE, lock_url, status=204)
f = self.client.lock_release(lock)
f = self.client.lock_release(lock_url)
self.assertTrue(f)

@httprettified
def test_delete_all_resources(self):
HTTPretty.register_uri(HTTPretty.DELETE, "%s/resources" % self.baseurl, status=401)
f = self.client.resource_delete_all()
self.assertFalse(f)
self.assertFalse('Authorization' in HTTPretty.last_request.headers)
HTTPretty.register_uri(HTTPretty.DELETE, "%s/resources" % self.baseurl, status=204)
f = self.client.resource_delete_all("foo", "bar")
self.assertTrue(f)
self.assertTrue('Authorization' in HTTPretty.last_request.headers)
b64 = base64.standard_b64encode(b"foo:bar")
expected = b"Basic " + b64
self.assertEqual(HTTPretty.last_request.headers['Authorization'], expected)

@httprettified
def test_delete_resource(self):
HTTPretty.register_uri(HTTPretty.DELETE, "%s/resources/foo" % self.baseurl, status=401)
f = self.client.resource_delete("foo")
self.assertFalse(f)
self.assertFalse('Authorization' in HTTPretty.last_request.headers)
HTTPretty.register_uri(HTTPretty.DELETE, "%s/resources/foo" % self.baseurl, status=204)
f = self.client.resource_delete("foo", username="foo", password="bar")
self.assertTrue(f)
self.assertTrue('Authorization' in HTTPretty.last_request.headers)
b64 = base64.standard_b64encode(b"foo:bar")
expected = b"Basic " + b64
self.assertEqual(HTTPretty.last_request.headers['Authorization'], expected)

@httprettified
def test_get(self):
lock_url = "%s/locks/foo/94e4458bad8248828213275ae0b17eae" % self.baseurl
HTTPretty.register_uri(HTTPretty.GET, lock_url, status=404)
f = self.client.lock_get(lock_url)
self.assertTrue(f is None)
json_file = os.path.join(os.path.dirname(__file__), "lock1.json")
with open(json_file, "r") as f:
body = f.read()
HTTPretty.register_uri(HTTPretty.GET, lock_url, status=200, body=body)
f = self.client.lock_get(lock_url)
self.assertFalse(f is None)
self.assertTrue(f.active)
self.assertEqual(f.title, "test title")
self.assertEqual(f.uid, "94e4458bad8248828213275ae0b17eae")
self.assertEqual(f.lifetime, 300)
self.assertEqual(f.wait, 10)
self.assertEqual(f.active_since.isoformat(), "2013-03-02T22:00:05")
self.assertEqual(f.active_expires.isoformat(), "2013-03-02T22:05:05")

if __name__ == '__main__':
unittest.main()

0 comments on commit 0a43e79

Please sign in to comment.