Skip to content

Commit

Permalink
Documentation, bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Louis Sobel committed Apr 29, 2012
1 parent 3696f19 commit ca14e5e
Show file tree
Hide file tree
Showing 14 changed files with 231 additions and 31 deletions.
4 changes: 2 additions & 2 deletions README.markdown
Expand Up @@ -6,7 +6,7 @@ About
----------
A python server that uses the dropbox API to serve files that are hosted on dropbox. It will not be particularly useful
to a developer who is comfortable with git, heroku, ftp, or another method of hosting a website. It will be useful, however,
to people who are not proficient with these tools. It is extremely simple because whatever is in the Drapache folder in the users
to people who don't know how to use these tools. It's very simple - whatever is in the Drapache folder in the users
dropbox /Apps folder will be immediately available on the internet.
Get it set up at [get.drapache.com](http://get.drapache.com)

Expand All @@ -22,7 +22,7 @@ runs on drapache and demonstrates what can be done with dbpy.

Misc. Features
----------------
It will create an index for a folder if one doesn't exist, using a template found in Drapache/\_templates/
It will create an index for a folder if one doesn't exist, using a template found in Drapache/\_templates/.
Files or folders that begin with '\_' will not be served, returning instead a 403-Forbidden HTTP response.
Right now they _do_ show up in an auto-generated index, but that is for debugging purposes and could be easily changed

Expand Down
9 changes: 5 additions & 4 deletions TODO.markdown
Expand Up @@ -3,6 +3,7 @@ Things that can be done from here:

- TESTS.
- enough said. first priority; it has become way too big to test comprehensively by hand.
- dbpy tests are important because if they are working, the whole system is working

- Make more subdomain managers / improve the Mysql one with cacheing.
- this is important because the subdomain lookup occurs for every request
Expand All @@ -22,12 +23,12 @@ Things that can be done from here:
from a dropbox.client.DropboxClient and a drapache.util.http.Request, and call serve on it to get a
drapache.util.http.Response

- Also, I think there is room for a lot of cacheing... maybe of dropbox clients?
- Also, I think there is room for a lot of cacheing... maybe of dropbox clients themselves?

- Some kind of global configuration mechanism for the server itself... like debug, how to handle index files, dbpy timeout, etc
- Some kind of global configuration mechanism for the server... like debug, how to handle index files, dbpy timeout, etc

- I haven't really thought about how much memory could be used, because all files as well as the output of .dbpy scripts
is saved in memory. Limit this somehow/use tempory files?
- I haven't really thought about just how much memory could be used, because all files as well as the output of .dbpy scripts
is saved in memory. Limit this somehow/use temporary files?

- Writing blog.drapache was a pain because, for example, when I made a small change is CSS I had to wait a while before
seeing what it looked like in the browser (because of the slow page load times + the dropbox sync delay).
Expand Down
99 changes: 99 additions & 0 deletions dbpy_doc_parser.py
@@ -0,0 +1,99 @@
import drapache.dbpy.builtins.dbpy as dbpy


import re
import inspect
import pprint

import json

def get_help_hash(module):

children = []
doc = {'type':'module','name':module.name,'children':children,'doc':module.__doc__}

if hasattr(module,'submodules'):
for child in module.submodules:

if hasattr(child,'get_doc'):
child_hash = child.get_doc()
else:
child_hash = get_help_hash(child)

children.append(child_hash)

module_code = inspect.getsource(module)

#go through, finding attributes and functions

mode = 'looking'
cur_hash = {}
cur_doc = []


target = None

attributes = []
functions = []

attribute_pattern = re.compile('#DOC:(.+)')
function_pattern = re.compile('@env.register')

def_pattern = re.compile('def (.+?):')

for line in module_code.split('\n'):
line = line.strip()

#print "%s === %s" % (mode,line)

if mode == 'looking':

attr_match = attribute_pattern.match(line)
if attr_match:
cur_hash['type'] = 'attribute'
cur_hash['name'] = attr_match.group(1)
cur_hash['children'] = []
target = attributes
mode = 'doc_expecting'

else:
function_match = function_pattern.match(line)
if function_match:
cur_hash['type'] = 'function'
cur_hash['children'] = []
target = functions
mode = 'def_expecting'

elif mode == 'def_expecting':
def_match = def_pattern.match(line)
if def_match:
mode = 'doc_expecting'
cur_hash['name'] = def_match.group(1)

elif mode == 'doc_expecting':
if '"""' in line:
mode = 'doc_sucking'
function_match = function_pattern.match(line)
if function_match:
cur_hash['type'] = 'function'
cur_hash['children'] = []
target = functions
mode = 'def_expecting'

elif mode == 'doc_sucking':
if '"""' in line:
cur_hash['doc'] = ' '.join(cur_doc)
target.append(cur_hash)
cur_hash = {}
cur_doc = []
mode = 'looking'
else:
cur_doc.append(line)

children.extend(attributes)
children.extend(functions)

return doc

if __name__ == "__main__":
print json.dumps(get_help_hash(dbpy))
1 change: 0 additions & 1 deletion drapache/dbpy/builtins/__init__.py
@@ -1,6 +1,5 @@
#import all packages or modules that are sub-modules of this one

from environment import DBPYEnvironment



Expand Down
32 changes: 24 additions & 8 deletions drapache/dbpy/builtins/dbpy/__init__.py
Expand Up @@ -4,12 +4,15 @@
import http
import text

submodules = [templates,io,session,http,text]
submodules = [http,io,templates,session,text]

import os
import imp

name = 'dbpy'

__doc__ = """The root module. All other modules are under this namespace"""

def build(env,path):

from drapache import dbpy
Expand All @@ -19,14 +22,23 @@ def build(env,path):

for module in submodules:
setattr(self,module.name,module.build(env,name))



#DOC:get_params
"""
The parsed parameters from the request url in a multidict
"""
self.get_params = env.get_params

#DOC:post_params
"""
If the request was a post request, the parsed parameters from the body of the post
"""
self.post_params = env.post_params

#get a version of any builtins we use in this module
io = self.io


#### write the builtins!
def dropbox_import_callback(imports):
#hm... unfortunately, if any of the imports mutate the built_in_hash, they can
Expand All @@ -39,16 +51,20 @@ def dropbox_import_callback(imports):
@env.register(self)
@env.privileged_with_callback(dropbox_import_callback)
def dropbox_import(*module_paths):
"""
Accepts multiple path arguments, and will download each one
and create a module in the global namespace, like the python import statement
"""
#look first in the path given by folder search
#then look in a '/_scripts' folder? or similarly named?
#not right now

#NO PACKAGE SUPPORT... SIMPLE FILES ONLY FOR NOW
imports = []
for module_path in module_paths:
if not module_name in env.globals:
filestring = io.file.read(module_path)
module_name = os.path.basename(module_path).split('.',1)[0]
for module_path in module_paths:
filestring = io.file.read(module_path)
module_name = os.path.basename(module_path).split('.',1)[0]
if not module_name in env.globals:
out_module = imp.new_module(module_name)
env.globals[module_name] = out_module
imports.append( (filestring,out_module) )
Expand All @@ -58,7 +74,7 @@ def dropbox_import(*module_paths):
@env.register(self)
def die(message="",report=True):
"""
Raises an Exception
Terminates the script at the point when it is called, printing the error message if report is True
"""
if report:
print message
Expand Down
29 changes: 23 additions & 6 deletions drapache/dbpy/builtins/dbpy/http.py
Expand Up @@ -2,29 +2,46 @@

name = 'http'

__doc__ = "The http module provides access to methods concerning the http request and response"

def build(env,path):

self = env.get_new_module(path+'.'+name)

dbpy = env.get_module('dbpy')

@env.register(self)
def set_response_header(key,value):
env.response.set_header(key,value)
def set_response_header(header,value):
"""
Sets the header `header` to the value given by `value`
"""
env.response.set_header(header,value)

@env.register(self)
def get_request_header(key):
return env.request.headers.get(key)
def get_request_header(header):
"""
Returns the header specified by `header`
"""
return env.request.headers.get(header)

@env.register(self)
def set_response_status(status):
"""
Sets the HTTP Status code of the response to `status`
"""
env.response.status = status

@env.register(self)
def redirect(where,immediately=True):
def redirect(where,immediately=True,status=302):
"""
Redirects the HTTP request to another location.
The target location is given by `where`.
If immediately is true, the script will exit immediately once this function is executed.
The status is 302 by default, but could be set to whatever.
"""

set_response_status(302)
set_response_header('Location',where)

if immediately:
dbpy.die("redirecting")

Expand Down
2 changes: 2 additions & 0 deletions drapache/dbpy/builtins/dbpy/io/__init__.py
Expand Up @@ -5,6 +5,8 @@

name = 'io'

__doc__ = "Module for file and json dropbox read/write operations"

def build(env,path):


Expand Down
34 changes: 32 additions & 2 deletions drapache/dbpy/builtins/dbpy/io/file.py
Expand Up @@ -5,6 +5,8 @@

name = 'file'

__doc__ = "Functions for reading/writing with files that live on dropbox"

def build(env,path):

self = env.get_new_module(path+'.'+name)
Expand All @@ -13,6 +15,9 @@ def build(env,path):
@env.register(self)
@env.privileged
def _get_lock(path,timeout):
"""
Internal function, public because, why hide it?
"""
try:
file_exists = env.locker.lock(path,timeout)
except IOError as e:
Expand All @@ -24,14 +29,27 @@ def _get_lock(path,timeout):
@env.register(self)
@env.privileged
def _release_lock(path):
"""
Internal function, public because, why hide it?
"""
#throws an IOError if it doesn't work
env.locker.release(path)

@env.register(self)
@env.privileged
def open(path,to='read',timeout=None,allow_download=True):
"""
loads a file from the users dropbox and returns a string with the contents
Opens a file on your dropbox.
There are three modes: read, write, append, and json. If the mode is read, the file is simply
downloaded and a file-like (StringIO) object is returned. If the mode is write, append or json
the function will try for `timeout` seconds to obtain a lock for the given path. If it fails,
it will raise an `IOError`. Otherwise, it will then download the file and return either
a file-like filehandle (in the case of write mode) or a json dictionary handle that will update
back to the dropbox file (in the case of json mode).
If the mode is append, all writes will start at the end. If the mode is write, all writes will overwrite
the data starting at the start of the file.
"""
#if path starts with /, it is absolute.
#otherwise, it is relative to the request path
Expand Down Expand Up @@ -75,24 +93,36 @@ def close_file_cleanup():
@env.register(self)
@env.privileged
def close(file_handle):
"""
Closes the given file handle. This will happen automatically,
but do this to release resources (it releases the lock too)
"""
file_handle._close(env.locker)


@env.register(self)
def write(path,string,timeout=None):
"""
Writes the given string to the path given by `path`
"""
text_file = open(path,to='write',timeout=timeout,allow_download=False)
text_file.write(string)
close(text_file)

@env.register(self)
def read(path):
"""
reads the file given by path and returns a string of its contents
"""
return open(path).read()


@env.register(self)
@env.privileged
def delete(path):

"""
deletes the given path.
"""
if not path.startswith('/'):
path = env.request_folder + path

Expand Down

0 comments on commit ca14e5e

Please sign in to comment.