You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
7.3 KiB
Python
199 lines
7.3 KiB
Python
import requests
|
|
import requests.exceptions
|
|
import json
|
|
import time
|
|
import re
|
|
|
|
path_to_pk = re.compile( r".*/([0-9]+)/(\?.*)?(#.*)?" )
|
|
|
|
from . import urlqueryparse
|
|
|
|
class objdict( dict ):
|
|
@classmethod
|
|
def response_to_objdicts( cls, i ):
|
|
if isinstance( i, dict ):
|
|
return cls( i )
|
|
elif isinstance( i, ( list, tuple ) ):
|
|
return [ cls.response_to_objdicts( v ) for v in i ]
|
|
return i
|
|
|
|
def __getattr__( self, name ):
|
|
if name in self:
|
|
return self.__class__.response_to_objdicts( self[ name ] )
|
|
else:
|
|
raise AttributeError( "No such attribute: " + name )
|
|
|
|
def __setattr__( self, name, value ):
|
|
self[ name ] = value
|
|
|
|
def __delattr__( self, name ):
|
|
if name in self:
|
|
del self[ name ]
|
|
else:
|
|
raise AttributeError( "No such attribute: " + name )
|
|
|
|
response_to_objdicts = objdict.response_to_objdicts
|
|
|
|
class ApiKeyAuth( requests.auth.AuthBase ):
|
|
def __init__( self, api_username, api_key ):
|
|
self.api_username = api_username
|
|
self.api_key = api_key
|
|
|
|
def __call__( self, r ):
|
|
r.headers[ 'Authorization' ] = "ApiKey %s:%s" % ( self.api_username, self.api_key )
|
|
r.headers[ 'Content-Type' ] = "application/json"
|
|
r.headers[ 'Accept-Encoding' ] = "application/json"
|
|
return r
|
|
|
|
class APIv2( object ):
|
|
def __init__( self, api_username, api_key, base = "https://fulcrm.org", api_path = "/api/v2", cache = None, cache_timeout = None ):
|
|
self.api_username = api_username
|
|
self.api_key = api_key
|
|
self.api_path = api_path
|
|
self.base = base
|
|
self.cache = cache
|
|
self.cache_timeout = cache_timeout
|
|
self.auth = ApiKeyAuth( self.api_username, self.api_key )
|
|
|
|
def normalize_path( self, path ):
|
|
if not path.startswith( self.api_path ):
|
|
if not path.startswith( "/" ):
|
|
path = "/" + path
|
|
path = self.api_path + path
|
|
if not path.endswith( "/" ):
|
|
path = path + "/"
|
|
return path
|
|
|
|
def path_to_pk( self, path ):
|
|
if not isinstance( path, str ):
|
|
return path
|
|
if path.startswith( self.base ) or path.startswith( self.api_path ):
|
|
try:
|
|
match = path_to_pk.split( path )
|
|
return match[ 1 ]
|
|
except:
|
|
return path
|
|
return path
|
|
|
|
def normalize_query( self, query ):
|
|
for ( k, v ) in query.items():
|
|
if isinstance( v, tuple ):
|
|
query[ k ] = list( v )
|
|
elif isinstance( v, list ):
|
|
pass
|
|
else:
|
|
query[ k ] = [ v ]
|
|
query[ k ] = [ self.path_to_pk( x ) for x in v ]
|
|
|
|
if 'expand' in query:
|
|
if query[ 'expand' ]:
|
|
query[ 'expand' ] = [ ",".join( query[ 'expand' ] ) ]
|
|
else:
|
|
del query[ 'expand' ]
|
|
return query
|
|
|
|
def normalize_url( self, url, no_d = False, query = None ):
|
|
url = urlqueryparse.set_url_components( url,
|
|
scheme = 'https',
|
|
netloc = 'fulcrm.org',
|
|
path = self.normalize_path )
|
|
new_query = urlqueryparse.get_query_fields( url )
|
|
if no_d:
|
|
if 'expand' in new_query:
|
|
new_query[ 'expand' ] = [ x for x in new_query[ 'expand' ] if not ( ( x == 'd' ) or ( x.endswith( '.d' ) ) ) ]
|
|
else:
|
|
if 'expand' in new_query:
|
|
new_query[ 'expand' ].append( 'd' )
|
|
else:
|
|
new_query[ 'expand' ] = [ 'd' ]
|
|
|
|
if query:
|
|
new_query.update( query )
|
|
|
|
new_query = self.normalize_query( new_query )
|
|
|
|
new_url = urlqueryparse.set_query_fields( url, new_query )
|
|
return new_url
|
|
|
|
def get( self, url, query = None, no_d = False, _countdown = 5 ):
|
|
url = self.normalize_url( url, query = query, no_d = no_d )
|
|
return self.make_request( url, data = None, method = 'GET', _countdown = _countdown )
|
|
|
|
def post( self, url, data, query = None, no_d = False, _countdown = 5 ):
|
|
url = self.normalize_url( url, query = query, no_d = no_d )
|
|
return self.make_request( url, data = data, method = 'POST', _countdown = _countdown )
|
|
|
|
def patch( self, url, data, query = None, no_d = False, _countdown = 5 ):
|
|
url = self.normalize_url( url, query = query, no_d = no_d )
|
|
return self.make_request( url, data = data, method = 'PATCH', _countdown = _countdown )
|
|
|
|
def put( self, url, data, query = None, no_d = False, _countdown = 5 ):
|
|
url = self.normalize_url( url, query = query, no_d = no_d )
|
|
return self.make_request( url, data = data, method = 'PUT', _countdown = _countdown )
|
|
|
|
def delete( self, url, query = None, no_d = False, _countdown = 5 ):
|
|
url = self.normalize_url( url, query = query, no_d = no_d )
|
|
return self.make_request( url, data = None, method = 'DELETE', _countdown = _countdown )
|
|
|
|
def make_request( self, url, data, method, _countdown = 5 ):
|
|
method = { 'POST': requests.post,
|
|
'PATCH': requests.patch,
|
|
'GET': requests.get,
|
|
'DELETE': requests.delete
|
|
}.get( method, requests.get )
|
|
|
|
if self.cache and method is requests.get and not data:
|
|
rval = self.cache.get( url )
|
|
if rval is not None:
|
|
print( "CACHED", url )
|
|
return response_to_objdicts( rval )
|
|
else:
|
|
print( "NOCACHE", url )
|
|
|
|
try:
|
|
if data:
|
|
request = method( url, data = json.dumps( data ), auth = self.auth, timeout = 30.0 )
|
|
else:
|
|
request = method( url, auth = self.auth, timeout = 30.0 )
|
|
except requests.exceptions.ConnectionError:
|
|
if _countdown > 0:
|
|
time.sleep( 2 ** ( 5 - _countdown ) )
|
|
return self.make_request( url, data, method, _countdown - 1 )
|
|
else:
|
|
raise
|
|
if request.ok:
|
|
try:
|
|
if self.cache and self.cache_timeout and method is requests.get and not data:
|
|
rval = request.json()
|
|
self.cache.set( url, rval, timeout = self.cache_timeout )
|
|
return response_to_objdicts( rval )
|
|
else:
|
|
return response_to_objdicts( request.json() )
|
|
except:
|
|
return None
|
|
else:
|
|
request.raise_for_status()
|
|
|
|
def get_one( self, url, no_d = False, query = None ):
|
|
url = self.normalize_url( url, no_d = no_d, query = query )
|
|
return self.make_request( url, None, 'GET' )
|
|
|
|
def get_many( self, url, count = 10, no_d = False, query = None ):
|
|
if query is None:
|
|
query = {}
|
|
query[ 'page_size' ] = [ count ]
|
|
url = self.normalize_url( url, no_d = no_d, query = query )
|
|
|
|
next = None
|
|
while True:
|
|
result = self.make_request( url, None, 'GET' )
|
|
if 'results' in result:
|
|
for object in result[ 'results' ]:
|
|
yield response_to_objdicts( object )
|
|
if 'next' in result:
|
|
next = result.get( 'next', None )
|
|
if next:
|
|
url = next
|
|
else:
|
|
return
|