Commit 0aeeecba by Piotr Mitros

Relative to absolute import bug fixed. Untested code for merging analytics object. Better docs

parent bbf7494e
...@@ -101,6 +101,26 @@ To build on top of the framework, you will need several things: ...@@ -101,6 +101,26 @@ To build on top of the framework, you will need several things:
A very minimal example of how to build applications on top of this A very minimal example of how to build applications on top of this
framework is in the example directory. framework is in the example directory.
Using Analytics Externally (djobject)
-------------------------------------
djobject.py has an abstraction for accessing analytics both locally
(function calls) and remotely (RPC). This is a standard Python
object. All of the standard Python introspection of the methods in
this object works.
When using analytics remotely, there are issues of security and
permissions. Specifically, a sysadmin might want to see an analytic
per-course/per-student. An instructor of that course might want to
have that fixed to the course (so it transforms into a per-student
analytic). djobject's transform_embed defines a DSL for restricting
permissions to analytics, as well as for fixing specific commandline
parameters.
There is an issue of network reliability and timeouts when access
remotely. This is planned to be handled by being able to set timeouts
on djembed objects.
Shortcuts/invariants Shortcuts/invariants
-------------------- --------------------
......
...@@ -3,9 +3,9 @@ etc. as Python objects. ...@@ -3,9 +3,9 @@ etc. as Python objects.
This is prototype-grade code. This is prototype-grade code.
It will remain ugly into production, however. I'm not sure if there is It is very ugly. It will be cleaned up, but still slightly ugly,
a good way to make this clean, but the ugliness here will save a lot however. I'm not sure if there is a good way to make this clean, but
of ugliness for the API caller. the ugliness here will save a lot of ugliness for the API caller.
''' '''
import decorator import decorator
...@@ -29,7 +29,7 @@ def find_in_schema(cls = None, name = None): ...@@ -29,7 +29,7 @@ def find_in_schema(cls = None, name = None):
items.append(item) items.append(item)
return items return items
def http_rpc_helper(baseurl, view_or_query, function, headers = {}): def http_rpc_helper(baseurl, view_or_query, function, headers = {}, timeout = None):
''' Make an RPC call to a remote djanalytics instance ''' Make an RPC call to a remote djanalytics instance
''' '''
if baseurl: if baseurl:
...@@ -39,7 +39,12 @@ def http_rpc_helper(baseurl, view_or_query, function, headers = {}): ...@@ -39,7 +39,12 @@ def http_rpc_helper(baseurl, view_or_query, function, headers = {}):
url = urllib.basejoin(baseembedurl, function) url = urllib.basejoin(baseembedurl, function)
if kwargs: if kwargs:
url = url+"?"+urllib.urlencode(kwargs) url = url+"?"+urllib.urlencode(kwargs)
response = requests.get(url, headers = headers) kwargs = {}
if headers:
kwargs['headers'] = headers
if timeout:
kwargs['timeout'] = float(timeout)
response = requests.get(url, **kwargs)
if response.status_code == 200: if response.status_code == 200:
return response.content return response.content
if response.status_code == 404: if response.status_code == 404:
...@@ -56,12 +61,62 @@ def local_call_helper(view_or_query, function): ...@@ -56,12 +61,62 @@ def local_call_helper(view_or_query, function):
return djanalytics.core.registry.handle_request(view_or_query, function, **kwargs) return djanalytics.core.registry.handle_request(view_or_query, function, **kwargs)
return rpc_call return rpc_call
class multi_embed():
''' Lets you merge multiple embeds into one. E.g. talk to 5
analytics servers.
This code is scaffolding meant to define the interface. It is
untested.
'''
def __init__(self, embeds):
self._embeds = embeds
def __getattr__(self, attr):
for x in self._embeds:
try:
attr = x.__getattr(attr)
except AttributeError:
pass
if attr:
return attr
raise AttributeError(attr)
def _refresh_schema(self):
for x in self._embeds:
x._refresh_schema
def __dir__(self):
return list(sum(map(lambda x:set(x.__dir__()), self._embeds)))
def __repr__(self):
return "/".join(map(lambda x:repr(x), self._embeds))
class embed(): class embed():
def __init__(self, view_or_query, baseurl = None, headers = {}): def __init__(self, view_or_query, baseurl = None, headers = {}):
self._baseurl = baseurl self._baseurl = baseurl
self._view_or_query = view_or_query self._view_or_query = view_or_query
self._headers = headers self._headers = headers
self._refresh_schema() self._refresh_schema()
self._timeout = [ None ]
class MetaEmbed(object):
''' Allows operations on the view or query through view.meta.
E.g. view.meta.set_timeout(t)
This code is scaffolding meant to define the interface. It
is untested.
'''
def __init__(self, embed):
self._embed = embed
def set_timout(self, t):
''' Set the timeout for object if it is going over a
network. Timeouts are kept on a stack; when you're
done, call unset_timeout, to return the timeout to
where it was. '''
self._embed._timeout.append(t)
def unset_timeout(self):
''' Unset the timeout for object. See set_timeout for
details. '''
self._embed._timeout.pop()
self.meta = MetaEmbed(self)
def _refresh_schema(self): def _refresh_schema(self):
global schema global schema
...@@ -82,7 +137,8 @@ class embed(): ...@@ -82,7 +137,8 @@ class embed():
# Return a caller to the function # Return a caller to the function
if self._baseurl: if self._baseurl:
helper = http_rpc_helper(self._baseurl, self._view_or_query, attr) helper = http_rpc_helper(self._baseurl, self._view_or_query, attr,
headers = self._headers, timeout = self._timeout[-1])
else: else:
helper = local_call_helper(self._view_or_query, attr) helper = local_call_helper(self._view_or_query, attr)
...@@ -120,6 +176,12 @@ class embed(): ...@@ -120,6 +176,12 @@ class embed():
return self._view_or_query+" object host: ["+self._baseurl+"]" return self._view_or_query+" object host: ["+self._baseurl+"]"
class transform_embed: class transform_embed:
'''
Defines a DSL for restricting permissions to analytics and for
locking down specific parameters.
TODO: This is prototype-grade code. It needs a cleanup pass.
'''
def __init__(self, transform_policy, context, embed): def __init__(self, transform_policy, context, embed):
self._embed = embed self._embed = embed
self._context = context self._context = context
......
...@@ -6,8 +6,8 @@ log=logging.getLogger(__name__) ...@@ -6,8 +6,8 @@ log=logging.getLogger(__name__)
event_handlers = [] event_handlers = []
request_handlers = {'view':{}, 'query':{}} request_handlers = {'view':{}, 'query':{}}
import views import djanalytics.core.views
funcskips = views.default_optional_kwargs.keys()+['params'] # params are additional GET/POST parameters funcskips = djanalytics.core.views.default_optional_kwargs.keys()+['params'] # params are additional GET/POST parameters
def register_handler(cls, category, name, description, f, args): def register_handler(cls, category, name, description, f, args):
''' Helper function for @view and @query decorators. ''' Helper function for @view and @query decorators.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment