Initial commit
Benjamin Renard

Benjamin Renard commited on 2014-01-12 00:33:07
Showing 19 changed files, with 772 additions and 0 deletions.

... ...
@@ -0,0 +1,7 @@
1
+*.pyc
2
+*.pyo
3
+*~
4
+.*.swp
5
+/*.egg-info
6
+/cache/*
7
+/data/*
... ...
@@ -0,0 +1,10 @@
1
+.PHONY: clean flake8
2
+
3
+all: clean flake8
4
+
5
+clean:
6
+	find -name "*.pyc" | xargs rm -f
7
+	rm -rf cache/*
8
+
9
+flake8:
10
+	flake8 --ignore=E123,E128 --max-line-length=120 mycoserver
... ...
@@ -0,0 +1,24 @@
1
+Install
2
+=======
3
+
4
+Debian dependencies:
5
+
6
+$ aptitude install python python-mako python-markupsafe python-paste python-pastedeploy python-pastescript \
7
+    python-weberror python-webhelpers python-webob
8
+
9
+Non-Debian dependencies:
10
+
11
+$ git clone git://gitorious.org/biryani/biryani.git biryani1
12
+$ cd biryani1
13
+$ git checkout biryani1
14
+$ python setup.py develop --no-deps --user
15
+
16
+Install mycoserver Python egg (from mycoserver root directory):
17
+
18
+$ python setup.py develop --no-deps --user
19
+
20
+
21
+Start server
22
+============
23
+
24
+$ paster serve --reload development.ini
... ...
@@ -0,0 +1,53 @@
1
+# EesyVPN Web - Development environment configuration
2
+#
3
+# The %(here)s variable will be replaced with the parent directory of this file.
4
+
5
+[DEFAULT]
6
+debug = true
7
+# Uncomment and replace with the address which should receive any error reports
8
+#email_to = you@yourdomain.com
9
+smtp_server = localhost
10
+from_address = myco-server@localhost
11
+dbpass = myP@ssw0rd
12
+
13
+[server:main]
14
+use = egg:Paste#http
15
+host = 0.0.0.0
16
+port = 8765
17
+
18
+[app:main]
19
+use = egg:MyCoServer
20
+
21
+
22
+# Logging configuration
23
+[loggers]
24
+keys = root, mycoserver, mycoserver_router
25
+
26
+[handlers]
27
+keys = console
28
+
29
+[formatters]
30
+keys = generic
31
+
32
+[logger_root]
33
+level = DEBUG
34
+handlers = console
35
+
36
+[logger_mycoserver]
37
+level = DEBUG
38
+handlers =
39
+qualname = mycoserver
40
+
41
+[logger_mycoserver_router]
42
+level = DEBUG
43
+handlers =
44
+qualname = mycoserver.router
45
+
46
+[handler_console]
47
+class = StreamHandler
48
+args = (sys.stderr,)
49
+formatter = generic
50
+
51
+[formatter_generic]
52
+format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s:%(funcName)s line %(lineno)d] %(message)s
53
+datefmt = %H:%M:%S
... ...
@@ -0,0 +1,57 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+"""Middleware initialization"""
5
+
6
+
7
+import logging.config
8
+import os
9
+
10
+from paste.cascade import Cascade
11
+from paste.urlparser import StaticURLParser
12
+from weberror.errormiddleware import ErrorMiddleware
13
+
14
+from . import configuration, context, controllers, templates
15
+
16
+import db
17
+
18
+
19
+def make_app(global_conf, **app_conf):
20
+    """Create a WSGI application and return it
21
+
22
+    ``global_conf``
23
+        The inherited configuration for this application. Normally from
24
+        the [DEFAULT] section of the Paste ini file.
25
+
26
+    ``app_conf``
27
+        The application's local configuration. Normally specified in
28
+        the [app:<name>] section of the Paste ini file (where <name>
29
+        defaults to main).
30
+    """
31
+    logging.config.fileConfig(global_conf['__file__'])
32
+    app_ctx = context.Context()
33
+    app_ctx.conf = configuration.load_configuration(global_conf, app_conf)
34
+    app_ctx.templates = templates.load_templates(app_ctx)
35
+    app_ctx.db = db.DB(
36
+      app_ctx.conf.get('dbhost','localhost'),
37
+      app_ctx.conf.get('dbuser','myco'),
38
+      app_ctx.conf.get('dbpass','password'),
39
+      app_ctx.conf.get('dbname','myco'),
40
+    )
41
+    if not app_ctx.db.connect():
42
+      logging.error('Failed to connect DB')
43
+    app = controllers.make_router()
44
+    app = context.make_add_context_to_request(app, app_ctx)
45
+    if not app_ctx.conf['debug']:
46
+        app = ErrorMiddleware(
47
+            app,
48
+            error_email=app_ctx.conf['email_to'],
49
+            error_log=app_ctx.conf.get('error_log', None),
50
+            error_message=app_ctx.conf.get('error_message', 'An internal server error occurred'),
51
+            error_subject_prefix=app_ctx.conf.get('error_subject_prefix', 'Web application error: '),
52
+            from_address=app_ctx.conf['from_address'],
53
+            smtp_server=app_ctx.conf.get('smtp_server', 'localhost'),
54
+            )
55
+    app = Cascade([StaticURLParser(os.path.join(app_ctx.conf['app_dir'], 'static')), app])
56
+    app.ctx = app_ctx
57
+    return app
... ...
@@ -0,0 +1,30 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+"""Paste INI configuration"""
5
+
6
+
7
+import os
8
+
9
+from biryani1 import strings
10
+from biryani1.baseconv import (check, default, guess_bool, pipe, struct)
11
+
12
+
13
+def load_configuration(global_conf, app_conf):
14
+    """Build the application configuration dict."""
15
+    app_dir = os.path.dirname(os.path.abspath(__file__))
16
+    conf = {}
17
+    conf.update(strings.deep_decode(global_conf))
18
+    conf.update(strings.deep_decode(app_conf))
19
+    conf.update(check(struct(
20
+        {
21
+            'app_conf': default(app_conf),
22
+            'app_dir': default(app_dir),
23
+            'cache_dir': default(os.path.join(os.path.dirname(app_dir), 'cache')),
24
+            'debug': pipe(guess_bool, default(False)),
25
+            'global_conf': default(global_conf),
26
+            },
27
+        default='drop',
28
+        drop_none_values=False,
29
+        ))(conf))
30
+    return conf
... ...
@@ -0,0 +1,24 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+"""Context loaded and saved in WSGI requests"""
5
+
6
+
7
+from webob.dec import wsgify
8
+
9
+
10
+def make_add_context_to_request(app, app_ctx):
11
+    """Return a WSGI middleware that adds context to requests."""
12
+    @wsgify
13
+    def add_context_to_request(req):
14
+        req.ctx = app_ctx
15
+        req.ctx.req = req
16
+        return req.get_response(app)
17
+    return add_context_to_request
18
+
19
+
20
+class Context(object):
21
+    _ = lambda self, message: message
22
+    conf = None
23
+    templates = None
24
+    db = None
... ...
@@ -0,0 +1,70 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+import logging
5
+
6
+from webob.dec import wsgify
7
+
8
+from . import conv, router, templates, wsgi_helpers
9
+
10
+import json
11
+
12
+log = logging.getLogger(__name__)
13
+
14
+@wsgify
15
+def home(req):
16
+	return templates.render(req.ctx, '/home.mako', data={})
17
+
18
+@wsgify
19
+def login(req):
20
+	params = req.params
21
+	log.debug(u'params = {}'.format(params))
22
+	inputs = {
23
+		'email': params.get('email'),
24
+		'password': params.get('password'),
25
+		}
26
+	log.debug(u'inputs = {}'.format(inputs))
27
+	data, errors = conv.inputs_to_login_data(inputs)
28
+	if errors is not None:
29
+		return wsgi_helpers.bad_request(req.ctx, comment=errors)
30
+		
31
+	log.debug(u'data = {}'.format(data))
32
+
33
+	login_data=req.ctx.db.login(data['email'],data['password'])
34
+	return wsgi_helpers.respond_json(req.ctx,login_data,headers=[('Access-Control-Allow-Origin','*')])
35
+
36
+@wsgify
37
+def sync(req):
38
+	params = req.params
39
+	log.debug(u'params = {}'.format(params))
40
+	inputs = {
41
+		'email': params.get('email'),
42
+		'password': params.get('password'),
43
+		'groups': params.get('groups')
44
+		}
45
+	log.debug(u'inputs = {}'.format(inputs))
46
+	data, errors = conv.inputs_to_sync_data(inputs)
47
+	if errors is not None or data['groups'] is None:
48
+		return wsgi_helpers.bad_request(req.ctx, comment=errors)
49
+
50
+	data['groups']=json.loads(data['groups'])
51
+		
52
+	log.debug(u'data = {}'.format(data))
53
+
54
+	login_data=req.ctx.db.login(data['email'],data['password'])
55
+	if 'email' in login_data:
56
+		ret=req.ctx.db.sync_group(data['email'],data['groups'])
57
+		return wsgi_helpers.respond_json(req.ctx,ret,headers=[('Access-Control-Allow-Origin','*')])
58
+	else:
59
+		return wsgi_helpers.respond_json(
60
+                  req.ctx,
61
+                  login_data,
62
+                  headers=[('Access-Control-Allow-Origin','*')]
63
+                )
64
+
65
+def make_router():
66
+	return router.make_router(
67
+		('GET', '^/$', home),
68
+		('GET', '^/login$', login),
69
+		('GET', '^/sync$', sync),
70
+		)
... ...
@@ -0,0 +1,23 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+from biryani1.baseconv import cleanup_line, empty_to_none, not_none, pipe, struct
5
+
6
+inputs_to_login_data = struct(
7
+    {
8
+        'email': pipe(cleanup_line, empty_to_none),
9
+        'password': pipe(cleanup_line, empty_to_none),
10
+    },
11
+    default='drop',
12
+    drop_none_values=False,
13
+    )
14
+
15
+inputs_to_sync_data = struct(
16
+    {
17
+        'email': pipe(cleanup_line, empty_to_none),
18
+        'password': pipe(cleanup_line, empty_to_none),
19
+        'groups': pipe(empty_to_none),
20
+    },
21
+    default='drop',
22
+    drop_none_values=False,
23
+    )
... ...
@@ -0,0 +1,84 @@
1
+#!/usr/bin/python
2
+# -*- coding: utf-8 -*-
3
+
4
+import json
5
+import logging
6
+log = logging.getLogger(__name__)
7
+import MySQLdb
8
+
9
+class DB(object):
10
+
11
+	def __init__(self,host,user,pwd,db):
12
+		self.host = host
13
+		self.user = user
14
+		self.pwd  = pwd
15
+		self.db   = db
16
+		self.con  = 0
17
+
18
+	def connect(self):
19
+		if self.con == 0:
20
+			try:
21
+				con = MySQLdb.connect(self.host,self.user,self.pwd,self.db)
22
+				self.con = con
23
+				return True
24
+			except Exception, e:
25
+				log.fatal('Error connecting to database : %s' % e)
26
+				return
27
+
28
+	def do_sql(self,sql):
29
+		try:
30
+			c=self.con.cursor()
31
+			c.execute(sql)
32
+			self.con.commit()
33
+			return c
34
+		except Exception,e:
35
+			log.error('Error executing request %s : %s' % (sql,e))
36
+			return False
37
+
38
+	def select(self,sql):
39
+		ret=self.do_sql(sql)
40
+		if ret!=False:
41
+			return ret.fetchall()
42
+		return ret
43
+
44
+	def login(self,email,password):
45
+		ret=self.select("SELECT email,name,password FROM users WHERE email='%s' AND password='%s'" % (email,password))
46
+		log.debug(ret)
47
+		if ret:
48
+			if len(ret)==1:
49
+				return {
50
+					'email': ret[0][0],
51
+					'name': ret[0][1]
52
+				}
53
+			elif len(ret)>=1:
54
+				log.warning('Duplicate user %s in database' % email)
55
+		elif ret==():
56
+			return { 'loginerror': 'Utilisateur inconnu' }
57
+		return { 'loginerror': 'Erreur inconnu' }
58
+
59
+	def sync_group(self,email,groups):
60
+		db_groups=self.get_group(email)
61
+		json_group=json.dumps(groups)
62
+		if db_groups!=False:
63
+			if db_groups=={}:
64
+				if groups=={}:
65
+					return {'groups': {}}
66
+				else:
67
+					if self.do_sql("INSERT INTO groups (email,groups) VALUES ('%s','%s')" % (email,json_group)):
68
+						return {'groups': groups}
69
+			elif groups=={}:
70
+				return {'groups': db_groups}
71
+			else:
72
+				if self.do_sql("UPDATE groups SET groups='%s' WHERE email='%s'" % (json_group,email)):
73
+					return {'groups': groups}
74
+		return {'syncerror': 'Erreur inconnu'}
75
+
76
+	def get_group(self,email):
77
+		ret=self.select("SELECT groups FROM groups WHERE email='%s'" % email)
78
+		if ret!=False:
79
+			if len(ret)==1:
80
+				return json.loads(ret[0][0])
81
+			else:
82
+				return {}
83
+		else:
84
+			return False
... ...
@@ -0,0 +1,50 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+"""Helpers for URLs"""
5
+
6
+
7
+import logging
8
+import re
9
+
10
+from webob.dec import wsgify
11
+
12
+from . import wsgi_helpers
13
+
14
+
15
+log = logging.getLogger(__name__)
16
+
17
+
18
+def make_router(*routings):
19
+    """Return a WSGI application that dispatches requests to controllers."""
20
+    routes = []
21
+    for routing in routings:
22
+        methods, regex, app = routing[:3]
23
+        if isinstance(methods, basestring):
24
+            methods = (methods,)
25
+        vars = routing[3] if len(routing) >= 4 else {}
26
+        routes.append((methods, re.compile(regex), app, vars))
27
+
28
+    @wsgify
29
+    def router(req):
30
+        """Dispatch request to controllers."""
31
+        split_path_info = req.path_info.split('/')
32
+        assert not split_path_info[0], split_path_info
33
+        for methods, regex, app, vars in routes:
34
+            if methods is None or req.method in methods:
35
+                match = regex.match(req.path_info)
36
+                if match is not None:
37
+                    log.debug(u'URL path = {path} matched controller {controller}'.format(
38
+                        controller=app, path=req.path_info))
39
+                    if getattr(req, 'urlvars', None) is None:
40
+                        req.urlvars = {}
41
+                    req.urlvars.update(dict(
42
+                        (name, value.decode('utf-8') if value is not None else None)
43
+                        for name, value in match.groupdict().iteritems()
44
+                        ))
45
+                    req.urlvars.update(vars)
46
+                    req.script_name += req.path_info[:match.end()]
47
+                    req.path_info = req.path_info[match.end():]
48
+                    return req.get_response(app)
49
+        return wsgi_helpers.not_found(req.ctx)
50
+    return router
... ...
@@ -0,0 +1,44 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+"""Mako templates rendering"""
5
+
6
+
7
+import json
8
+import mako.lookup
9
+import os
10
+
11
+from . import helpers
12
+
13
+
14
+js = lambda x: json.dumps(x, encoding='utf-8', ensure_ascii=False)
15
+
16
+
17
+def load_templates(ctx):
18
+    # Create the Mako TemplateLookup, with the default auto-escaping.
19
+    return mako.lookup.TemplateLookup(
20
+        default_filters=['h'],
21
+        directories=[os.path.join(ctx.conf['app_dir'], 'templates')],
22
+        input_encoding='utf-8',
23
+        module_directory=os.path.join(ctx.conf['cache_dir'], 'templates'),
24
+        )
25
+
26
+
27
+def render(ctx, template_path, **kw):
28
+    return ctx.templates.get_template(template_path).render_unicode(
29
+        ctx=ctx,
30
+        helpers=helpers,
31
+        js=js,
32
+        N_=lambda message: message,
33
+        req=ctx.req,
34
+        **kw).strip()
35
+
36
+
37
+def render_def(ctx, template_path, def_name, **kw):
38
+    return ctx.templates.get_template(template_path).get_def(def_name).render_unicode(
39
+        _=ctx.translator.ugettext,
40
+        ctx=ctx,
41
+        js=js,
42
+        N_=lambda message: message,
43
+        req=ctx.req,
44
+        **kw).strip()
... ...
@@ -0,0 +1,14 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+import os
5
+import random
6
+import datetime
7
+
8
+
9
+def random_sequence(length):
10
+    return [random.random() for idx in xrange(0, length)]
11
+
12
+
13
+def relative_path(ctx, abs_path):
14
+    return os.path.relpath(abs_path, ctx.req.path)
... ...
@@ -0,0 +1,13 @@
1
+## -*- coding: utf-8 -*-
2
+
3
+
4
+<%inherit file="/site.mako"/>
5
+
6
+<%block name="title_content">MyCo</%block>
7
+
8
+<%block name="body_content"> 
9
+<div class="hero-unit">
10
+  <h1>MyCo <small>Gérer vos déponses communes</small></h1>
11
+  <p class="muted">Application mobile de gestion de vos dépenses communes.</p>
12
+</div>
13
+</%block>
... ...
@@ -0,0 +1,23 @@
1
+## -*- coding: utf-8 -*-
2
+
3
+
4
+<%inherit file="/site.mako"/>
5
+
6
+
7
+<%block name="body_content">
8
+<div class="alert alert-block alert-error">
9
+  <h4 class="alert-heading">Error « ${title} »</h4>
10
+  <p>${explanation}</p>
11
+% if comment:
12
+  <p>${comment}</p>
13
+% endif
14
+% if message:
15
+  <p>${message}</p>
16
+% endif
17
+</div>
18
+</%block>
19
+
20
+
21
+<%block name="title_content">
22
+${title} - ${parent.title_content()}
23
+</%block>
... ...
@@ -0,0 +1,36 @@
1
+<!DOCTYPE html>
2
+<html lang="fr">
3
+  <head>
4
+    <meta charset="utf-8">
5
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+    <title><%block name="title_content">MyCo</%block></title>
7
+    <link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
8
+    <link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap-theme.min.css">
9
+    <link rel="stylesheet" href="${helpers.relative_path(ctx, '/css/style.css')}">
10
+    <!--[if lt IE 9]>
11
+      <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
12
+      <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
13
+    <![endif]-->
14
+  </head>
15
+  <body>
16
+    
17
+
18
+<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
19
+  <div class="container">
20
+    <div class="navbar-header">
21
+      <a class="navbar-brand" href="index.html">MyCo</a>
22
+    </div>
23
+  </div>
24
+</div>
25
+
26
+<div class="container">
27
+      
28
+<%block name="body_content"/>
29
+
30
+</div>
31
+
32
+<script src="https://code.jquery.com/jquery.js"></script>
33
+<script src="https://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
34
+    
35
+  </body>
36
+</html>
... ...
@@ -0,0 +1,170 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+import collections
5
+import json
6
+
7
+from markupsafe import Markup
8
+from webhelpers.html import tags
9
+import webob.dec
10
+import webob.exc
11
+
12
+from . import templates
13
+
14
+
15
+N_ = lambda message: message
16
+
17
+
18
+errors_explanation = {
19
+    400: N_("Request is faulty"),
20
+    401: N_("Access is restricted to authorized persons."),
21
+    403: N_("Access is forbidden."),
22
+    404: N_("The requested page was not found."),
23
+    }
24
+errors_message = {
25
+    401: N_("You must login to access this page."),
26
+    }
27
+errors_title = {
28
+    400: N_("Unable to Access"),
29
+    401: N_("Access Denied"),
30
+    403: N_("Access Denied"),
31
+    404: N_("Unable to Access"),
32
+    }
33
+
34
+
35
+def bad_request(ctx, **kw):
36
+    return error(ctx, 400, **kw)
37
+
38
+
39
+def discard_empty_items(data):
40
+    if isinstance(data, collections.Mapping):
41
+        # Use type(data) to keep OrderedDicts.
42
+        data = type(data)(
43
+            (name, discard_empty_items(value))
44
+            for name, value in data.iteritems()
45
+            if value is not None
46
+            )
47
+    return data
48
+
49
+
50
+def error(ctx, code, **kw):
51
+    response = webob.exc.status_map[code](headers=kw.pop('headers', None))
52
+    if code != 204:  # No content
53
+        body = kw.pop('body', None)
54
+        if body is None:
55
+            template_path = kw.pop('template_path', '/http-error.mako')
56
+            explanation = kw.pop('explanation', None)
57
+            if explanation is None:
58
+                explanation = errors_explanation.get(code)
59
+                explanation = ctx._(explanation) if explanation is not None else response.explanation
60
+            message = kw.pop('message', None)
61
+            if message is None:
62
+                message = errors_message.get(code)
63
+                if message is not None:
64
+                    message = ctx._(message)
65
+            comment = kw.pop('comment', None)
66
+            if isinstance(comment, dict):
67
+                comment = tags.ul(u'{0} : {1}'.format(key, value) for key, value in comment.iteritems())
68
+            elif isinstance(comment, list):
69
+                comment = tags.ul(comment)
70
+            title = kw.pop('title', None)
71
+            if title is None:
72
+                title = errors_title.get(code)
73
+                title = ctx._(title) if title is not None else response.status
74
+            body = templates.render(ctx, template_path,
75
+                comment=comment,
76
+                explanation=explanation,
77
+                message=message,
78
+                response=response,
79
+                title=title,
80
+                **kw)
81
+        response.body = body.encode('utf-8') if isinstance(body, unicode) else body
82
+    return response
83
+
84
+
85
+def forbidden(ctx, **kw):
86
+    return error(ctx, 403, **kw)
87
+
88
+
89
+def method_not_allowed(ctx, **kw):
90
+    return error(ctx, 405, **kw)
91
+
92
+
93
+def no_content(ctx, headers=None):
94
+    return error(ctx, 204, headers=headers)
95
+
96
+
97
+def not_found(ctx, **kw):
98
+    return error(ctx, 404, **kw)
99
+
100
+
101
+def redirect(ctx, code=302, location=None, **kw):
102
+    assert location is not None
103
+    location_str = location.encode('utf-8') if isinstance(location, unicode) else location
104
+    response = webob.exc.status_map[code](headers=kw.pop('headers', None), location=location_str)
105
+    body = kw.pop('body', None)
106
+    if body is None:
107
+        template_path = kw.pop('template_path', '/http-error.mako')
108
+        explanation = kw.pop('explanation', None)
109
+        if explanation is None:
110
+            explanation = Markup(u'{0} <a href="{1}">{1}</a>.').format(ctx._(u"You'll be redirected to page"), location)
111
+        message = kw.pop('message', None)
112
+        if message is None:
113
+            message = errors_message.get(code)
114
+            if message is not None:
115
+                message = ctx._(message)
116
+        title = kw.pop('title', None)
117
+        if title is None:
118
+            title = ctx._("Redirection in progress...")
119
+        body = templates.render(ctx, template_path,
120
+            comment=kw.pop('comment', None),
121
+            explanation=explanation,
122
+            message=message,
123
+            response=response,
124
+            title=title,
125
+            **kw)
126
+    response.body = body.encode('utf-8') if isinstance(body, unicode) else body
127
+    return response
128
+
129
+
130
+def respond_json(ctx, data, code=None, headers=None, jsonp=None):
131
+    """Return a JSON response.
132
+
133
+    This function is optimized for JSON following
134
+    `Google JSON Style Guide <http://google-styleguide.googlecode.com/svn/trunk/jsoncstyleguide.xml>`_, but will handle
135
+    any JSON except for HTTP errors.
136
+    """
137
+    if isinstance(data, collections.Mapping):
138
+        # Remove null properties as recommended by Google JSON Style Guide.
139
+        data = discard_empty_items(data)
140
+        error = data.get('error')
141
+    else:
142
+        error = None
143
+    if headers is None:
144
+        headers = []
145
+    if jsonp:
146
+        headers.append(('Content-Type', 'application/javascript; charset=utf-8'))
147
+    else:
148
+        headers.append(('Content-Type', 'application/json; charset=utf-8'))
149
+    if error:
150
+        code = code or error['code']
151
+        assert isinstance(code, int)
152
+        response = webob.exc.status_map[code](headers=headers)
153
+        if error.get('code') is None:
154
+            error['code'] = code
155
+        if error.get('message') is None:
156
+            error['message'] = response.title
157
+    else:
158
+        response = ctx.req.response
159
+        if code is not None:
160
+            response.status = code
161
+        response.headers.update(headers)
162
+    text = unicode(json.dumps(data, encoding='utf-8', ensure_ascii=False, indent=2, sort_keys=True))
163
+    if jsonp:
164
+        text = u'{0}({1})'.format(jsonp, text)
165
+    response.text = text
166
+    return response
167
+
168
+
169
+def unauthorized(ctx, **kw):
170
+    return error(ctx, 401, **kw)
... ...
@@ -0,0 +1,40 @@
1
+#!/usr/bin/env python
2
+# -*- coding: utf-8 -*-
3
+
4
+
5
+"""MyCO Server web application."""
6
+
7
+
8
+from setuptools import setup, find_packages
9
+
10
+
11
+doc_lines = __doc__.split('\n')
12
+
13
+
14
+setup(
15
+    author=u'Benjamin Renard',
16
+    author_email=u'brenard@zionetrix.net',
17
+    description=doc_lines[0],
18
+    entry_points="""
19
+        [paste.app_factory]
20
+        main = mycoserver.application:make_app
21
+        """,
22
+    include_package_data=True,
23
+    install_requires=[
24
+        'Biryani1 >= 0.9dev',
25
+        'MarkupSafe >= 0.15',
26
+        'WebError >= 0.10',
27
+        'WebHelpers >= 1.3',
28
+        'WebOb >= 1.1',
29
+        ],
30
+#    keywords='',
31
+#    license=u'http://www.fsf.org/licensing/licenses/agpl-3.0.html',
32
+    long_description='\n'.join(doc_lines[2:]),
33
+    name=u'MyCoServer',
34
+    packages=find_packages(),
35
+    paster_plugins=['PasteScript'],
36
+    setup_requires=['PasteScript >= 1.6.3'],
37
+#    url=u'',
38
+    version='0.1',
39
+    zip_safe=False,
40
+    )
0 41