Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added files

  • Loading branch information...
commit 471e3d64325d585bf26bd8432f30bc42ada640e5 1 parent 3047e95
@Lispython Lispython authored
View
3  AUTHORS
@@ -1,4 +1,4 @@
-gottwall is written and maintained by Alexandr Lispython and
+gottwall is written by GottWall Team and maintained by Alexandr Lispython and
various contributors:
Development Lead
@@ -9,4 +9,3 @@ Development Lead
Patches and Suggestions
~~~~~~~~~~~~~~~~~~~~~~~
-- Nicolas D (github@ndevilla)
View
6 Makefile
@@ -26,4 +26,8 @@ clean-pyc:
find . -name '*~' -exec rm -f {} +
find-print:
- grep -r --include=*.py --exclude-dir=venv --exclude=fabfile* --exclude=tests.py --exclude-dir=tests --exclude-dir=commands 'print' ./
+ grep -r --include=*.py --exclude-dir=venv --exclude=fabfile* --exclude=tests.py --exclude-dir=tests --exclude-dir=commands 'print' ./
+
+
+debug:
+ python gottwall/app.py --config=examples/config.py
View
18 examples/config.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+STORAGE = 'gottwall.storages.RedisStorage'
+
+
+REDIS_HOST = 'localhost'
+REDIS_PORT = 6379
+REDIS_PASSWORD = None
+REDIS_DB = 2
+
+
+USERS = []
+
+SECRET_KEY = "dwefwefwefwecwef"
+
+
+PROJECTS = ["test_project"]
View
18 examples/tester.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import json
+import datetime
+import human_curl as hurl
+
+metric_data = {"name": "my_metric_name",
+ "timestamp": datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S"),
+ "filters": {"views": "registered",
+ "clicks": "anonymouse"},
+ "action": "incr",
+ "value": 2}
+
+headers = (("content-type", "application/json"),)
+
+r = hurl.post("http://127.0.0.1:8889/test_project/api/store", data=json.dumps(metric_data), headers=headers)
+
View
93 gottwall/app.py
@@ -11,7 +11,10 @@
:license: BSD, see LICENSE for more details.
:github: http://github.com/Lispython/gottwall
"""
+import imp
+import os
+import importlib
import os.path
import tornado.ioloop
from tornado.web import Application
@@ -19,52 +22,90 @@
from tornado import httpserver
from tornado import autoreload
from utils import rel
+from config import Config, default_settings
+
+from handlers import BaseHandler, DashboardHandler, LoginHandler, HomeHandler,\
+ StatsHandler, MetricsHandler
+from backends import HTTPBackend as HTTPBackendHandler
define("port", default=8889, help="run HTTP on the given port", type=int)
define("ssl_port", default=8890, help="run HTTPS on the given port", type=int)
+define("config", help="Configuration file", type=str)
+
+class HTTPApplication(Application):
+ """Base application
+ """
-class LoginHandler():
- def get(self):
- self.render("login.html")
+ def __init__(self, config):
+ self.dirty_handlers = [
+ (r"/login", LoginHandler),
+ (r"/dashboard", DashboardHandler),
+ (r"/(?P<project>.+)/api/stats", StatsHandler),
+ (r"/(?P<project>.+)/api/metrics", MetricsHandler),
+ (r"/", HomeHandler)]
-class HomeHandler():
+ self.config = config
- def get(self):
- self.render("index.html")
+ tornado.web.Application.__init__(self, self.dirty_handlers, **config)
-class Dashboard():
- def get(self):
- self.render("dashboard.html")
+ def configure_app(self):
+ """Configure application backends and storages
+ """
+ self.configure_backends(self.config['BACKENDS'])
+ self.configure_storage(self.config['STORAGE'])
+ def configure_storage(self, storage_path):
+ """Configure data storage by path
-class HTTPApplication(Application):
- """Base application
- """
+ :param storage: storage path
+ """
+ module_path, name = storage_path.rsplit('.', 1)
+ try:
+ module = importlib.import_module(module_path, name)
+ storage = getattr(module, name)
+ except (ImportError, AttributeError), e:
+ raise Exception("Invalid storage: {0}".format(e))
+ storage.setup(self)
- def __init__(self):
- self.dirty_handlers = [
- (r"/login", LoginHandler),
- (r"/", HomeHandler),
- ]
+ def configure_backends(self, backends):
+ """Configture data receive backends
- settings = dict(
+ :param backends: list of backends
+ """
+
+ for backend_path in backends:
+ module_path, name = backend_path.rsplit('.', 1)
+ try:
+ backend_module = importlib.import_module(module_path, name)
+
+ backend = getattr(backend_module, name)
+ except (ImportError, AttributeError), e:
+ raise Exception("Invalid backend: {0}".format(e))
+ backend.setup_backend(self)
+
+ return backend
+
+
+if __name__ == "__main__":
+ tornado.options.parse_command_line()
+
+ default_settings.update(
+ dict(
site_title=u"GottWall - statistics aggregator",
+ login_url="/login",
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
xsrf_cookies=False,
- cookie_secret="0fkewrlhfwhrfweiurbweuybfrweoubfrowebfioubweoiufbwbeofbowebfbwup2XdTP1o/Vo=",
- autoescape=None,
- )
- tornado.web.Application.__init__(self, self.dirty_handlers, **settings)
-
+ cookie_secret="fkewrlhfwhrfweiurbweuybfrweoubfrowebfioubweoiufbwbeofbowebfbwup2XdTP1o/Vo=",
+ autoescape=None))
-application = HTTPApplication()
+ default_settings.from_file(options.config)
+ application = HTTPApplication(default_settings)
+ application.configure_app()
-if __name__ == "__main__":
- tornado.options.parse_command_line()
http_server = httpserver.HTTPServer(application)
http_server.listen(options.port)
ioloop = tornado.ioloop.IOLoop.instance()
View
5 gottwall/backends/__init__.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from base import BaseBackend
+from http import HTTPBackend
View
34 gottwall/backends/base.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+gottwall.backends.base
+~~~~~~~~~~~~~~~~~~~~~~
+
+Base backends for metric calculation
+
+:copyright: (c) 2012 by Alexandr Lispython (alex@obout.ru).
+:license: BSD, see LICENSE for more details.
+:github: http://github.com/Lispython/gottwall
+"""
+
+class BaseBackend(object):
+ application = None
+
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def process_data(self, project, data):
+ """Process `data`
+ """
+ if data.get('action', 'incr') == 'incr':
+ data.pop('action', None)
+ self.application.storage.incr(project, **data)
+ return True
+
+ def setup_backend(self, app):
+ """Setup backend for application
+
+ :param app: :class:`tornado.web.Application` instance
+ """
+ raise NotImplementedError("You need reimplement setup_backend method")
View
81 gottwall/backends/http.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+gottwall.backends.base
+~~~~~~~~~~~~~~~~~~~~~~
+
+Base backends for metric calculation
+
+:copyright: (c) 2012 by Alexandr Lispython (alex@obout.ru).
+:license: BSD, see LICENSE for more details.
+:github: http://github.com/Lispython/gottwall
+"""
+
+import simplejson as json
+from tornado.web import HTTPError
+
+from gottwall.backends.base import BaseBackend
+from gottwall.handlers import BaseHandler
+
+
+class HTTPBackend(BaseHandler, BaseBackend):
+
+
+ @staticmethod
+ def merge_handlers(app):
+ """Merge hosts handlers in application handler
+ """
+ hosts = {}
+ handlers = app.handlers
+ for host, patterns in handlers:
+ if host not in hosts.keys():
+ hosts[host] = []
+ hosts[host] += patterns
+ app.handlers = [(host, patterns) for host, patterns in hosts.items()]
+
+ @classmethod
+ def setup_backend(cls, app):
+ """Setup data handler to `app`
+
+ :param cls: :class:`BaseBackend` childrem class
+ :param app: :class:`tornado.web.Application` instance
+ """
+ handlers = [
+ (r"/(?P<project>.+)/api/store", cls), ]
+ app.add_handlers(r".*$", handlers)
+
+ cls.merge_handlers(app)
+
+ cls.application = app
+
+ def post(self, project, *args, **kwargs):
+ if not self.validate_project(project):
+ raise HTTPError(404, "Invalid project")
+
+ if not self.validate_content_type():
+ raise HTTPError(400, "Invalid content type")
+
+ if not self.check_auth():
+ raise HTTPError(403, "Forbidden")
+
+ data = json.loads(self.request.body)
+ self.process_data(project, data)
+
+ self.write("OK")
+ self.finish()
+
+ def validate_project(self, project):
+ """Validate projects name
+ """
+ return True
+
+ def validate_content_type(self):
+ if self.request.headers['content-type'] == 'application/json':
+ return True
+ return False
+
+ def check_auth(self):
+ """Check authorization headers
+ """
+ return True
View
49 gottwall/config.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+gottwall.config
+~~~~~~~~~~~~~~~
+
+Gottwall configs wrapper
+
+:copyright: (c) 2012 by Alexandr Lispython (alex@obout.ru).
+:license: BSD, see LICENSE for more details.
+:github: http://github.com/Lispython/gottwall
+"""
+import imp
+import os.path
+from settings import PERIODS
+
+
+class Config(dict):
+ """Settings store
+ """
+ def __init__(self, **kwargs):
+ dict.__init__(self, kwargs or {})
+
+ def from_file(self, filename):
+ """Load settings from pyfile with `filename`
+ """
+ filename = os.path.abspath(filename)
+ d = imp.new_module('config')
+ d.__file__ = filename
+ try:
+ execfile(filename, d.__dict__)
+ except IOError, e:
+ raise Exception("Unable to load configuration file {0}: ".format(filename, e))
+
+ for key in dir(d):
+ if not key.startswith('__'):
+ self[key] = getattr(d, key)
+ return d
+
+ def __repr__(self):
+ return '<{0} {1}>'.format(self.__class__.__name__, dict.__repr__(self))
+
+
+
+default_settings = Config(**{
+ "BACKENDS": ['gottwall.backends.HTTPBackend'],
+ "STORAGE": "gottwall.storages.MemoryStorage",
+ "PERIODS": PERIODS})
View
16 gottwall/exceptions.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+gottwall.exceptions
+~~~~~~~~~~~~~~~~~~~
+
+GottWall core exceptions
+
+:copyright: (c) 2012 by Alexandr Lispython (alex@obout.ru).
+:license: BSD, see LICENSE for more details.
+:github: http://github.com/Lispython/gottwall
+"""
+
+class InvalidData(Exception):
+ pass
View
131 gottwall/handlers.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+handlers
+~~~~~~~~
+
+module description
+
+:copyright: (c) 2012 by Alexandr Lispython (alex@obout.ru).
+:license: BSD, see LICENSE for more details.
+:github: http://github.com/Lispython/gottwall
+"""
+import logging
+import tornado.escape
+from tornado.auth import GoogleMixin
+from tornado.web import RequestHandler, HTTPError, asynchronous, authenticated
+from tornado.escape import json_decode, json_encode
+
+from gottwall import get_version
+
+logger = logging.getLogger('gottwall')
+
+
+class BaseHandler(RequestHandler):
+ def __init__(self, *args, **kwargs):
+ super(BaseHandler, self).__init__(*args, **kwargs)
+ self.set_header("Server", "GottWall/{0}".format(get_version()))
+
+ def get_current_user(self):
+ """ Return current logged user or None.
+ """
+ user_json = self.get_secure_cookie("user")
+ if not user_json:
+ return None
+ return json_decode(user_json)
+
+ def check_auth(self):
+ """Check authorization
+ """
+ key = self.request.headers.get('Authorization')
+ if key != self.application.config['SECRET_KEY']:
+ logger.error("Invalid authorixation key: {0}".format(key))
+ raise HTTPError(401, "Authorization required")
+ return True
+
+
+
+class DashboardHandler(BaseHandler):
+ @authenticated
+ def get(self, *args, **kwargs):
+ self.render("dashboard.html", config=self.application.config)
+
+
+class HomeHandler(BaseHandler):
+
+ def get(self, *args, **kwargs):
+ storage = self.application.storage
+ config = self.application.config
+ self.render("index.html", storage=storage.__class__.__name__,
+ backends=self.application.config['BACKENDS'],
+ projects=config['PROJECTS'])
+
+
+class JSONHandler(BaseHandler):
+ """Make json from response body
+ """
+
+ def json_response(self, data, finish=True):
+ output_json = tornado.escape.json_encode(data)
+ self.set_header("Content-Type", "application/json")
+ if finish is True:
+ self.finish(output_json)
+ else:
+ return output_json
+
+
+class StatsHandler(JSONHandler):
+ """Load periods statistics
+ """
+ def get(self, *args, **kwargs):
+
+ from_date = self.get_argument('from_date', None)
+ to_date = self.get_argument('to_date', None)
+ period = self.get_argument('period', 'week')
+ filter_name = self.get_argument('filter_name', None)
+ filter_value = self.get_argument('filter_value', None)
+
+ data = self.application.storage.slice_data(
+ period, from_date, to_date, filter_name, filter_value)
+
+ self.json_response(data)
+
+
+class MetricsHandler(JSONHandler):
+ """Load metrics structure
+ """
+
+ def get(self, *args, **kwargs):
+ from random import choice
+ data = {"name": []}
+
+ self.json_response(data)
+
+
+class LogoutHandelr(BaseHandler):
+
+ def get(self):
+ self.clear_cookie('user')
+ self.redirect("/")
+
+
+class LoginHandler(BaseHandler, GoogleMixin):
+
+ @asynchronous
+ def get(self):
+ if self.get_argument("openid.mode", None):
+ self.get_authenticated_user(self.async_callback(self._on_auth))
+ return
+ self.authenticate_redirect()
+
+ def _on_auth(self, user):
+ if not user:
+ raise HTTPError(500, "Google auth failed")
+
+ if self.application.config.get('USERS') and \
+ not user.get('email') in self.application.config['USERS']:
+ raise HTTPError(403, "%s access forbiden." % user.get('name'))
+
+ self.set_secure_cookie("user", json_encode(user))
+ self.redirect("/")
View
18 gottwall/settings.py
@@ -6,7 +6,7 @@
GottWall settings
-:copyright: (c) 2012 by Alexandr Lispython (alex@obout.ru).
+:copyright: (c) 2012 by GottWall Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
:github: http://github.com/Lispython/gottwall
"""
@@ -16,3 +16,19 @@
__all__ = 'PROJECT_ROOT',
PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
+
+
+BACKENDS = ['gottwall.backends.HTTPBackend']
+
+
+PERIODS = [
+ "week",
+ "day",
+ "year",
+ "month",
+ "hour",
+ "all",
+ "minute"
+ ]
+
+TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S"
View
178 gottwall/storages.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+gottwall.storages
+~~~~~~~~~~~~~~~~~
+
+GottWall storages backends
+
+:copyright: (c) 2012 by GottWall Team, see AUTHORS for more details.
+:license: BSD, see LICENSE for more details.
+:github: http://github.com/Lispython/gottwall
+"""
+
+
+import tornadoredis
+
+from utils import get_by_period
+
+
+class BaseStorage(object):
+ """Base interface for calculation
+ """
+
+ def __init__(self, application):
+ self._application = application
+
+ @classmethod
+ def setup(cls, application):
+ """Setup storage to application
+ """
+ storage = cls(application)
+ application.storage = storage
+ return storage
+
+
+ def incr(self, project, name, timestamp, value=1, filters=None, **kwargs):
+ """Add count for metric `name` and `filters`
+
+ :param project: project name
+ :param name: metric name
+ :param filters: list of names
+ :param value: increment value
+ """
+ raise NotImplementedError
+
+ def decr(self, project, name, timestamp, value=-1, filters=None, **kwargs):
+ """Sub value from metric `name` in `project`
+
+ :param project: project name
+ :param name: metric name
+ :param filters: list of names
+ :param value: increment value
+ """
+ raise NotImplementedError
+
+ def slice_data(self, period, from_date, to_date, filter_name, filter_value):
+ """Get data by range and filters
+
+ :param period: range periodic
+ :param from_date: slice from
+ :param to_date: slice to
+ :param filter_name: slice for filter
+ :param filter_value: slice for filter value
+ """
+ raise NotImplementedError
+
+
+
+class MemoryStorage(BaseStorage):
+ """Store keys in dict in memory
+
+ Example:
+ {"project_name":
+ "metric_name:week:2012-7-11:f_name_value": 70}
+ {"project_name":
+ "metric_name:year:2012:f_name_value": 70}
+ """
+ def __init__(self, application):
+ super(MemoryStorage, self).__init__(application)
+ self._store = {}
+
+ def save_value(self, project, key, value):
+ """Setup metric value
+
+ :param project: project name
+ :param key: project key
+ :param value: counter value
+ """
+ if project not in self._store:
+ self._store[project] = {}
+ self._store[project][key] = self._store[project].get(key, 0) + value
+
+ def incr(self, project, name, timestamp, value=1, filters=None, **kwargs):
+ """Add value to metric counter
+
+ :param project: project name
+ :param name: metric name
+ :param timestamp: timestamp name
+ :param value: increment value
+ :param filters: dict of filters
+ :param \*\*kwargs: additional kwargs
+ """
+ for period in self._application.config['PERIODS']:
+ if filters:
+ for fname, fvalue in filters.iteritems():
+ self.save_value(project,
+ self.make_key(project, name, period, timestamp, fname, fvalue),
+ value)
+ self.save_value(project, self.make_key(project, name, period, timestamp), value)
+ return True
+
+
+ def decr(self, project, name, timestamp, value=1, filters=None, **kwargs):
+ return self.incr(project, name, timestamp, 0 - abs(value), filters, **kwargs)
+
+ def make_key(self, project, name, period, timestamp,
+ filtername=None, filtervalue=None):
+ """Make key from parameters
+
+ :param project: project name
+ :param name: mertric name
+ :param period: period prefix
+ :param timestamp: timestamp
+ :param filter: filtername
+ """
+
+ parts = [project, name, period, get_by_period(timestamp, period)]
+ if filtername and filtervalue:
+ parts.append("{0}|{1}".format(filtername, filtervalue))
+
+ return ';'.join(parts)
+
+ def get_metric_value(self, project, name, period, timestamp,
+ filtername=None, filtervalue=None):
+ """Get value from metric
+
+ :param project: project name
+ :param name: metric name
+ :param timestamp:
+ :param filtername: filtername
+ :param filtervalue: filtervalue
+ """
+ key = self.make_key(project, name, period, timestamp, filtername, filtervalue)
+ return self._store[project].get(key, 0)
+
+ def slice_data(self, period, from_date, to_date, filter_name, filter_value):
+ """Calculate stored data
+ """
+ from datetime import datetime
+ from random import choice
+ data = [[datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S"),
+ choice(range(20))] for x in xrange(40)]
+ return data
+
+
+class RedisStorage(MemoryStorage):
+ """Redis backend to store statistics
+ """
+
+ def __init__(self, application):
+ super(RedisStorage, self).__init__(application)
+ config = self._application.config
+
+ self.client = tornadoredis.Client(host=config['REDIS_HOST'],
+ port=config['REDIS_PORT'],
+ password=config['REDIS_PASSWORD'],
+ selected_db=config['REDIS_DB'])
+ self.client.connect()
+
+ def save_value(self, project, key, value):
+ """Increment key value
+
+ :param project: project name
+ :param key: project key
+ :param value: counter value
+ """
+
+ self.client.incr(key, value)
View
19 gottwall/templates/dashboard.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>{{ handler.settings['site_title'] }} | Dashboard</title>
+ <meta name="description" content="{{ handler.settings['site_title'] }}" />
+ <style type="text/css" media="all"></style>
+ </head>
+ <body>
+ <h1>{{ handler.settings['site_title'] }} | Dashboard</h1>
+ <p>Simple statistics aggregation platform</a>.
+
+ <h1>Projects:</h1>
+ <ul>
+ {% for project in config['PROJECTS'] %}
+ <li class="">{{ project }} </li>
+ {% end %}
+ </ul>
+ </body>
+</html>
View
13 gottwall/templates/index.html
@@ -3,10 +3,21 @@
<head>
<title>{{ handler.settings['site_title'] }}</title>
<meta name="description" content="{{ handler.settings['site_title'] }}" />
- <style type="text/css" media="all"></style>
+ <style type="text/css" media="all" src="static/css/main.css"></style>
</head>
<body>
<h1>{{ handler.settings['site_title'] }}</h1>
<p>Simple statistics aggregation platform</a>.
+ <h1>Store backend: {{ storage }}</h2>
+ {% for backend in backends %}
+ <h1>Data backend: {{ backend }}</h1>
+ {% end %}
+
+ <h1>Projects:</h1>
+ <ul>
+ {% for project in projects %}
+ <li class="">{{ project }} </li>
+ {% end %}
+ </ul>
</body>
</html>
View
38 gottwall/utils.py
@@ -12,8 +12,9 @@
"""
import os.path
+from datetime import datetime, timedelta, date
-from settings import PROJECT_ROOT
+from settings import PROJECT_ROOT, TIMESTAMP_FORMAT
__all__ = 'rel',
@@ -21,3 +22,38 @@
def rel(*args):
return os.path.join(PROJECT_ROOT, *args)
+
+
+def timestamp_to_datetime(timestamp):
+ """Convert `timestamp` to `datetime`
+
+ :param timestamp: str
+ """
+ if isinstance(timestamp, (str, unicode)):
+ return datetime.strptime(timestamp, TIMESTAMP_FORMAT)
+ return timestamp
+
+
+def get_by_period(timestamp, period):
+ """Get period value by timestamp
+
+ :param timestamp: `datetime.datetime` instance
+ :param period: period name
+ """
+ timestamp = timestamp_to_datetime(timestamp)
+
+ if period == "week":
+ return "{0}-{1}".format(timestamp.year, timestamp.isocalendar()[1])
+ elif period == "day":
+ return timestamp.strftime("%Y-%m-%d")
+ elif period == "year":
+ return timestamp.strftime("%Y")
+ elif period == "month":
+ return timestamp.strftime("%Y-%m")
+ elif period == "hour":
+ return timestamp.strftime("%Y-%m-%dT%H")
+ elif period == "minute":
+ return timestamp.strftime("%Y-%m-%dT%H:%M")
+ elif period == "all":
+ return "all"
+ return None
View
20 setup.py
@@ -72,15 +72,27 @@ def run_tests():
from tests import suite
return suite()
+py_ver = sys.version_info
+
+#: Python 2.x?
+is_py2 = (py_ver[0] == 2)
+
+#: Python 3.x?
+is_py3 = (py_ver[0] == 3)
+
tests_require = [
'nose',
- 'unittest2',
-]
+ 'unittest2']
install_requires = [
"tornado==2.4",
- "oredis"
- ]
+ "oredis",
+ "simplejson",
+ "python-dateutil==2.1",
+ "tornado-redis"]
+
+if not (is_py3 or (is_py2 and py_ver[1] >= 7)):
+ install_requires.append("importlib==1.0.2")
setup(
View
65 tests.py
@@ -1,65 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-gottwall.tests
-~~~~~~~~~~~~~~~~
-
-Unittests for gottwall
-
-:copyright: (c) 2011 - 2012 by Alexandr Lispython (alex@obout.ru).
-:license: BSD, see LICENSE for more details.
-"""
-from __future__ import with_statement
-
-import os
-import time
-import pycurl2 as pycurl
-import cookielib
-from Cookie import Morsel
-import json
-import uuid
-from random import randint, choice
-from string import ascii_letters, digits
-import logging
-from urlparse import urljoin
-import unittest
-import urllib
-from types import TupleType, ListType, FunctionType, DictType
-from urllib import urlencode
-
-import human_curl as requests
-from human_curl import Request, Response
-from human_curl import AsyncClient
-from human_curl.auth import *
-from human_curl.utils import *
-
-from human_curl.exceptions import (CurlError, InterfaceError)
-
-logger = logging.getLogger("gottwall.test")
-
-## async_logger = logging.getLogger("human_curl.async")
-## async_logger.setLevel(logging.DEBUG)
-
-## # Add the log message handler to the logger
-## # LOG_FILENAME = os.path.join(os.path.dirname(__file__), "debug.log")
-## # handler = logging.handlers.FileHandler(LOG_FILENAME)
-## handler = logging.StreamHandler()
-
-## formatter = logging.Formatter("%(levelname)s %(asctime)s %(module)s [%(lineno)d] %(process)d %(thread)d | %(message)s ")
-
-## handler.setFormatter(formatter)
-
-## async_logger.addHandler(handler)
-
-class BaseTestCase(unittes.TestCase):
- pass
-
-
-def suite():
- suite = unittest.TestSuite()
- suite.addTest(unittest.makeSuite(BaseTestCase))
- return suite
-
-
-if __name__ == '__main__':
- unittest.main(defaultTest="suite")
View
35 tests/__init__.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+gottwall.tests
+~~~~~~~~~~~~~~~~
+
+Unittests for gottwall
+
+:copyright: (c) 2011 - 2012 by Alexandr Lispython (alex@obout.ru).
+:license: BSD, see LICENSE for more details.
+"""
+
+import unittest
+
+import tornado.options
+
+from config import ConfigTestCase
+from backends import BaseBackendTestCase, HTTPBackendTestCase
+from app import ApplicationTestCase
+from api import APITestCase
+from storages import StorageTestCase
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ConfigTestCase))
+ suite.addTest(unittest.makeSuite(BaseBackendTestCase))
+ suite.addTest(unittest.makeSuite(HTTPBackendTestCase))
+ suite.addTest(unittest.makeSuite(ApplicationTestCase))
+ suite.addTest(unittest.makeSuite(APITestCase))
+ suite.addTest(unittest.makeSuite(StorageTestCase))
+ return suite
+
+if __name__ == '__main__':
+ unittest.main(defaultTest="suite")
View
52 tests/backends.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+gottwall.tests
+~~~~~~~~~~~~~~~~
+
+Unittests for gottwall
+
+:copyright: (c) 2011 - 2012 by Alexandr Lispython (alex@obout.ru).
+:license: BSD, see LICENSE for more details.
+"""
+
+import datetime
+import simplejson as json
+
+from tornado.web import Application
+
+from gottwall.app import HTTPApplication
+from gottwall.config import Config, default_settings
+
+from base import BaseTestCase, AsyncBaseTestCase
+
+
+class BaseBackendTestCase(BaseTestCase):
+
+ def test_1(self):
+ print("test wotk")
+
+
+class HTTPBackendTestCase(AsyncBaseTestCase):
+
+ def get_app(self):
+ default_settings.update({"BACKENDS": ["gottwall.backends.HTTPBackend"]})
+ self.app = HTTPApplication(default_settings)
+ self.app.configure_app()
+ return self.app
+
+ def test_handler(self):
+ metric_data = {"name": "my_metric_name",
+ "timestamp": datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S"),
+ "filters": {"views": "registered",
+ "clicks": "anonymouse"},
+ "action": "incr",
+ "value": 2}
+ response = self.fetch("/test_project/api/store", method="POST",
+ body=json.dumps(metric_data),
+ headers={"content-type": "application/json"})
+
+ self.assertEquals(response.body, "OK")
+ self.assertEquals(response.code, 200)
+
+
View
24 tests/config.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+gottwall.tests
+~~~~~~~~~~~~~~~~
+
+Unittests for gottwall
+
+:copyright: (c) 2012 by GottWall team, see AUTHORS for more details.
+:license: BSD, see LICENSE for more details.
+"""
+import os.path
+from base import BaseTestCase
+
+from gottwall.config import Config
+
+class ConfigTestCase(BaseTestCase):
+ def test_load(self):
+ config = Config()
+ filename = os.path.join(os.path.abspath(os.path.dirname(__file__)), "config_file.py")
+
+ config.from_file(filename)
+
+ self.assertTrue(config['SOME_TEST_VAR'], 'some_test_value')
View
1  tests/config_file.py
@@ -0,0 +1 @@
+SOME_TEST_VAR = 'some_test_value'
View
65 tests/storages.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+gottwall.tests
+~~~~~~~~~~~~~~~~
+
+Unittests for gottwall
+
+:copyright: (c) 2011 - 2012 by Alexandr Lispython (alex@obout.ru).
+:license: BSD, see LICENSE for more details.
+"""
+
+import datetime
+import simplejson as json
+
+from tornado.web import Application
+
+from gottwall.app import HTTPApplication
+from gottwall.config import Config, default_settings
+
+from base import BaseTestCase, AsyncBaseTestCase
+
+from gottwall.storages import MemoryStorage, BaseStorage
+
+
+class StorageTestCase(BaseTestCase):
+
+ def get_app(self):
+ self.app = HTTPApplication(default_settings)
+ self.app.configure_app()
+ return self.app
+
+ def test_base_storage(self):
+ app = HTTPApplication(default_settings)
+ app.configure_storage("gottwall.storages.BaseStorage")
+
+ self.assertTrue(isinstance(app.storage, BaseStorage))
+
+ params = {"project": "project_name",
+ "name": "orders",
+ "timestamp": datetime.datetime.now(),
+ "filters": {"clearing": True,
+ "device": "web"}}
+
+ self.assertRaises(NotImplementedError,
+ app.storage.incr, **params)
+
+ def test_memory_storage(self):
+ print("test memory storage")
+ app = HTTPApplication(default_settings)
+ app.configure_storage("gottwall.storages.MemoryStorage")
+ storage = app.storage
+
+ self.assertTrue(isinstance(app.storage, MemoryStorage))
+
+ project_name = "Test_project"
+ timestamp = datetime.datetime.utcnow()
+ for x in xrange(10):
+ storage.incr(project_name, "orders",
+ timestamp, filters={"clearing": True,
+ "device": "web"})
+
+ for period in ["all", "year", "month", "day", "week", "hour", "minute"]:
+ self.assertTrue(storage.get_metric_value(
+ project_name, "orders", period, timestamp), 10)
Please sign in to comment.
Something went wrong with that request. Please try again.