├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── marlin ├── __init__.py ├── marlin-server ├── marlin-server.py ├── marlin.config ├── marlin.py ├── marlin_functions.py ├── static │ ├── fonts │ │ ├── copse-regular-webfont.eot │ │ ├── copse-regular-webfont.svg │ │ ├── copse-regular-webfont.ttf │ │ ├── copse-regular-webfont.woff │ │ ├── quattrocentosans-bold-webfont.eot │ │ ├── quattrocentosans-bold-webfont.svg │ │ ├── quattrocentosans-bold-webfont.ttf │ │ ├── quattrocentosans-bold-webfont.woff │ │ ├── quattrocentosans-bolditalic-webfont.eot │ │ ├── quattrocentosans-bolditalic-webfont.svg │ │ ├── quattrocentosans-bolditalic-webfont.ttf │ │ ├── quattrocentosans-bolditalic-webfont.woff │ │ ├── quattrocentosans-italic-webfont.eot │ │ ├── quattrocentosans-italic-webfont.svg │ │ ├── quattrocentosans-italic-webfont.ttf │ │ ├── quattrocentosans-italic-webfont.woff │ │ ├── quattrocentosans-regular-webfont.eot │ │ ├── quattrocentosans-regular-webfont.svg │ │ ├── quattrocentosans-regular-webfont.ttf │ │ └── quattrocentosans-regular-webfont.woff │ ├── images │ │ ├── background.png │ │ ├── body-background.png │ │ ├── bullet.png │ │ ├── hr.gif │ │ └── octocat-logo.png │ ├── javascripts │ │ └── main.js │ ├── marlin.jpg │ ├── marlin.jpg~ │ ├── stylesheets │ │ ├── normalize.css │ │ ├── pygment_trac.css │ │ └── styles.css │ └── toast.css ├── templates │ └── index.html └── tests.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea/* 3 | build/* 4 | dist/* 5 | Marlin.egg-info/* 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Anoop Thomas Mathew 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of this project nor the names of its contributors may 15 | be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include marlin/static/* 2 | include marlin/templates/* 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Marlin](https://github.com/atmb4u/marlin/blob/master/marlin/static/marlin.jpg?raw=true) 2 | 3 | Marlin 4 | ====== 5 | 6 | #### a fast, frill-free REST API with ZERO setup time is too interesting. 7 | 8 | Quick Start Guide 9 | ----------------- 10 | 11 | ```bash 12 | 13 | pip install marlin # install marlin to the python environment. 14 | 15 | marlin-server start # start marlin server - port: 5000 16 | 17 | ``` 18 | 19 | 20 | Detailed Installation in Ubuntu 21 | ------------------------------- 22 | 23 | * redis-server 24 | 25 | ```bash 26 | sudo apt-get install redis-server 27 | ``` 28 | * create virtualenv 29 | 30 | ```bash 31 | sudo apt-get install python-pip 32 | sudo pip install virtualenv 33 | virtualenv marlin-env 34 | source marlin-env/bin/activate 35 | ``` 36 | 37 | * requests, ujson, flask, python-daemon 38 | ```bash 39 | pip install flask requests ujson python-daemon redis 40 | ``` 41 | 42 | * install marlin 43 | 44 | ```bash 45 | pip install marlin # install marlin to the python environment. 46 | 47 | ``` 48 | 49 | 50 | Managing Server 51 | --------------- 52 | 53 | ```bash 54 | marlin-server start # starts server with default conf on port 5000 55 | 56 | marlin-server stop # stops the server 57 | 58 | marlin-server restart # restart the server 59 | 60 | marlin-server live # starts a server on DEBUG mode 61 | ``` 62 | 63 | Request Methods 64 | --------------- 65 | 66 | 67 | | METHOD | URL | RESPONSE | DESCRIPTION | 68 | | ------------- |:--------------------------------: | :----------:| :--------------------------------------:| 69 | | GET | /api/v1/[model]?start=1&end=10 |[data] 1-10 | returns the 1-10 elements in the [model]| 70 | | GET | /api/v1/[model]/1 | data item | returns the element with id 1 | 71 | | GET | /ping/ | 200/500 | check if service is up and connected | 72 | | POST | /api/v1/[model]/ | [data] | adds data to the model | 73 | | PUT | /api/v1/[model]/1/ | [data] | edit data | 74 | | DELETE | /api/v1/[model]/1 | 200 | delete the data item | 75 | | DELETE | /api/v1/[model]/ | - | delete complete data in model | 76 | | DELETE | /api/v1/[model]?force=1 | - | delete and reset model (starts with id=1| 77 | 78 | 79 | Server Configuration 80 | -------------------- 81 | 82 | __marlin.config__ 83 | 84 | For custom configuration, just create a __marlin.config__ on the directory from where you are starting marlin-server. 85 | 86 | ``` 87 | 88 | [SERVER] 89 | DEBUG = True 90 | PID_FILE = /tmp/marlin.pid 91 | LOG_FILE = /tmp/marlin.log 92 | SERVER_PORT = 5000 93 | 94 | [REDIS] 95 | REDIS_SERVER = localhost 96 | REDIS_PORT = 6379 97 | API_PREFIX = /api/ 98 | 99 | [APP] 100 | APP_NAME = marlin 101 | ``` 102 | 103 | Custom urls and functions 104 | ---------------- 105 | 106 | Always, a basic REST API is just a scaffolding for the application, and custom defined urls and functions make it beautiful. As marlin is more focused on performance, it is designed for flexibility as well. 107 | 108 | It is pretty simple to create custom functions in Marlin. 109 | 110 | Just place ```marlin_function.py``` in the present working directory (pwd), with custom routes and custom responses. 111 | 112 | 113 | ```python 114 | # marlin_functions.py 115 | from marlin import app 116 | 117 | 118 | @app.route("/example/"): 119 | return Response("Simple Custom Response") 120 | ``` 121 | 122 | or a more complex example. 123 | 124 | ### To get a custom element based on a user id 125 | 126 | ```python 127 | import json 128 | from marlin import app, RedisDatabaseManager 129 | from flask import Response, request 130 | 131 | 132 | @app.route("/simple_get/") 133 | def custom_get(model): 134 | rdm = RedisDatabaseManager(request, model, version='v1') 135 | user_id = 127 136 | if rdm: 137 | rdm.manipulate_data() 138 | rdm.get_from_redis(user_id) # get data for the specific user id 139 | else: 140 | return json.dumps({"status": "Something is not right"}) 141 | if rdm.status and rdm.data: 142 | return Response(rdm.string_data, content_type='application/json; charset=utf-8') 143 | elif rdm.status: 144 | return Response(json.dumps({'status': "No data Found"}), content_type='application/json; charset=utf-8', 145 | status=404) 146 | else: 147 | return json.dumps({"status": "Something is not right"}) 148 | ``` 149 | 150 | 151 | A little more complicated Example 152 | ### Following example filter all the objects with ```name=Apple``` 153 | 154 | ```python 155 | @app.route('//', methods=['GET']) 156 | def little_complicated(model): 157 | custom_range_start = 10 158 | custom_range_end = 70 159 | error_response = Response(json.dumps( 160 | {'status': "Some unknown error"}), 161 | content_type='application/json; charset=utf-8', status=500) 162 | rdm = RedisDatabaseManager(request, model=model) 163 | if rdm: 164 | rdm.manipulate_data() 165 | rdm.get_many_from_redis(custom_range_start, custom_range_end) 166 | else: 167 | return error_response 168 | if rdm.status: 169 | if rdm.data: 170 | custom_query_set = [] 171 | for datum in rdm.data: 172 | if datum.get("name") == "Apple": 173 | custom_query_set.append(datum) 174 | return Response(json.dumps(custom_query_set), content_type='application/json; charset=utf-8') 175 | else: 176 | return Response(json.dumps({'status': "No data Found"}), content_type='application/json; charset=utf-8', 177 | status=404) 178 | else: 179 | return error_response 180 | ``` 181 | -------------------------------------------------------------------------------- /marlin/__init__.py: -------------------------------------------------------------------------------- 1 | import marlin -------------------------------------------------------------------------------- /marlin/marlin-server: -------------------------------------------------------------------------------- 1 | marlin-server.py -------------------------------------------------------------------------------- /marlin/marlin-server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import ConfigParser 3 | import logging 4 | 5 | from daemon import runner 6 | import sys 7 | 8 | if len(sys.argv) == 2 and 'live' == sys.argv[1]: 9 | from marlin import app 10 | else: 11 | from marlin.marlin import app 12 | 13 | try: 14 | from marlin_functions import * 15 | import inspect 16 | import marlin_functions 17 | funcs = [func[0] for func in inspect.getmembers(marlin_functions, inspect.isfunction)] 18 | if funcs: 19 | print("Custom Functions defined:", funcs) 20 | except: 21 | print("No custom functions defined.\n Check atmb4u.github.io/marlin for more details on custom functions.") 22 | 23 | config = ConfigParser.ConfigParser() 24 | config.read("marlin.config") 25 | 26 | if config.has_option("SERVER", "LOG_FILE"): 27 | LOG_FILE = config.get("SERVER", "LOG_FILE") 28 | else: 29 | LOG_FILE = "/tmp/marlin.log" 30 | if config.has_option("SERVER", "PID_FILE"): 31 | PID_FILE = config.get("SERVER", "PID_FILE") 32 | else: 33 | PID_FILE = "/tmp/marlin.pid" 34 | if config.has_option("SERVER", "SERVER_PORT"): 35 | SERVER_PORT = int(config.get("SERVER", "SERVER_PORT")) 36 | else: 37 | SERVER_PORT = 5000 38 | 39 | # Configure logging 40 | logging.basicConfig(filename=LOG_FILE, level=logging.DEBUG) 41 | 42 | 43 | class MarlinServer(): 44 | 45 | def __init__(self, pidfile='/tmp/', stderr='/dev/null', stdout='/dev/null'): 46 | self.stdin_path = '/dev/null' 47 | self.stdout_path = stdout 48 | self.stderr_path = stderr 49 | self.pidfile_path = pidfile 50 | self.pidfile_timeout = 5 51 | 52 | @staticmethod 53 | def run(): 54 | 55 | app.run(port=SERVER_PORT) 56 | 57 | 58 | if __name__ == "__main__": 59 | marlin_server = MarlinServer(pidfile=PID_FILE) 60 | if len(sys.argv) == 2 and 'live' == sys.argv[1]: 61 | print "Starting Test Server ..." 62 | app.debug = True 63 | app.run(port=SERVER_PORT) 64 | sys.exit(0) 65 | runner = runner.DaemonRunner(marlin_server) 66 | runner.do_action() 67 | -------------------------------------------------------------------------------- /marlin/marlin.config: -------------------------------------------------------------------------------- 1 | [SERVER] 2 | DEBUG=False 3 | PID_FILE = /tmp/marlin.pid 4 | LOG_FILE = /tmp/marlin.log 5 | SERVER_PORT = 5000 6 | 7 | [REDIS] 8 | REDIS_SERVER = localhost 9 | REDIS_PORT = 6379 10 | API_PREFIX = /api/ 11 | REDIS_PASSWORD = 12 | 13 | [APP] 14 | APP_NAME = marlin 15 | 16 | -------------------------------------------------------------------------------- /marlin/marlin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #!/usr/bin/env python 3 | 4 | from functools import wraps, update_wrapper 5 | from datetime import timedelta 6 | import ConfigParser 7 | import os 8 | 9 | import ujson as json 10 | from redis import Redis, ConnectionError 11 | 12 | from flask import make_response, request, current_app, Response, Flask, render_template, url_for 13 | 14 | VERSION = "0.986" 15 | 16 | config = ConfigParser.ConfigParser() 17 | config.read("marlin.config") 18 | 19 | if config.has_option("SERVER", "DEBUG"): 20 | DEBUG = config.get("SERVER", "DEBUG") 21 | else: 22 | DEBUG = True 23 | if config.has_option("REDIS", "REDIS_SERVER"): 24 | REDIS_SERVER = config.get("REDIS", "REDIS_SERVER") 25 | else: 26 | REDIS_SERVER = "127.0.0.1" 27 | if config.has_option("REDIS", "REDIS_PORT"): 28 | REDIS_PORT = config.get("REDIS", "REDIS_PORT") 29 | else: 30 | REDIS_PORT = "6379" 31 | if config.has_option("REDIS", "API_PRIFIX"): 32 | API_PREFIX = config.get("REDIS", "API_PREFIX") 33 | else: 34 | API_PREFIX = '/api/' 35 | if config.has_option("APP", "APP_NAME"): 36 | APP_NAME = config.get("APP", "APP_NAME") 37 | else: 38 | APP_NAME = "marlin" 39 | if config.has_option("REDIS", "REDIS_PASSWORD"): 40 | REDIS_PASSWORD = config.get("REDIS", "REDIS_PASSWORD") 41 | else: 42 | REDIS_PASSWORD = "" 43 | 44 | 45 | app = Flask(__name__) 46 | app.debug = DEBUG 47 | app.template_folder = os.path.join(app.root_path, "templates") 48 | 49 | 50 | def cross_domain(origin=None, methods=None, headers=None, 51 | max_age=21600, attach_to_all=True, 52 | automatic_options=True): 53 | if methods is not None: 54 | methods = ', '.join(sorted(x.upper() for x in methods)) 55 | if headers is not None and not isinstance(headers, basestring): 56 | headers = ', '.join(x.upper() for x in headers) 57 | if not isinstance(origin, basestring): 58 | origin = ', '.join(origin) 59 | if isinstance(max_age, timedelta): 60 | max_age = max_age.total_seconds() 61 | 62 | def get_methods(): 63 | if methods is not None: 64 | return methods 65 | options_resp = current_app.make_default_options_response() 66 | return options_resp.headers['allow'] 67 | 68 | def decorator(f): 69 | def wrapped_function(*args, **kwargs): 70 | if automatic_options and request.method == 'OPTIONS': 71 | resp = current_app.make_default_options_response() 72 | else: 73 | resp = make_response(f(*args, **kwargs)) 74 | if not attach_to_all and request.method != 'OPTIONS': 75 | return resp 76 | h = resp.headers 77 | h['Access-Control-Allow-Origin'] = origin 78 | h['Access-Control-Allow-Methods'] = get_methods() 79 | h['Access-Control-Max-Age'] = str(max_age) 80 | if headers is not None: 81 | h['Access-Control-Allow-Headers'] = headers 82 | return resp 83 | 84 | f.provide_automatic_options = False 85 | return update_wrapper(wrapped_function, f) 86 | 87 | return decorator 88 | 89 | 90 | def returns_json(f): 91 | @wraps(f) 92 | def decorated_function(*args, **kwargs): 93 | r = f(*args, **kwargs) 94 | return Response(r, content_type='application/json; charset=utf-8') 95 | 96 | return decorated_function 97 | 98 | 99 | @app.route("/") 100 | def index(): 101 | url_for('static', filename='toast.css') 102 | url_for('static', filename='marlin.jpg') 103 | return render_template('index.html') 104 | 105 | 106 | def unified_router(rdm): 107 | """ 108 | used by uni_api_router and multi_api_router. 109 | input: a RedisDatabaseManager instance 110 | output: http response with/without data with responses accordingly. 111 | with data : 200 112 | Data not found: 404 113 | """ 114 | error_response = Response(json.dumps( 115 | {'status': "Make sure redis-server is installed and running on http://%s:%s" % (REDIS_SERVER, REDIS_PORT)}), 116 | content_type='application/json; charset=utf-8', status=500) 117 | if rdm: 118 | rdm.manipulate_data() 119 | else: 120 | return error_response 121 | if rdm.status: 122 | if rdm.data: 123 | #TODO: add list and item views for data in html 124 | return Response(rdm.string_data, content_type='application/json; charset=utf-8') 125 | elif rdm.method == "DELETE": 126 | return Response("", status=200, content_type='application/json; charset=utf-8') 127 | else: 128 | return Response(json.dumps({'status': "No data Found"}), content_type='application/json; charset=utf-8', 129 | status=404) 130 | else: 131 | return error_response 132 | 133 | 134 | @returns_json 135 | @app.route(API_PREFIX + '//', methods=['GET', 'OPTIONS', 'POST', 'PUT', 'DELETE']) 136 | @app.route(API_PREFIX + '///', methods=['GET', 'OPTIONS', 'POST', 'PUT', 'DELETE']) 137 | @cross_domain(origin='*') 138 | def uni_api_router(version, model, id): 139 | rdm = RedisDatabaseManager(request, version, model, id) 140 | return unified_router(rdm) 141 | 142 | 143 | @returns_json 144 | @app.route(API_PREFIX + '/', methods=['GET', 'OPTIONS', 'POST', 'PUT', 'DELETE']) 145 | @app.route(API_PREFIX + '//', methods=['GET', 'OPTIONS', 'POST', 'PUT', 'DELETE']) 146 | @cross_domain(origin='*') 147 | def multi_api_router(version, model): 148 | rdm = RedisDatabaseManager(request, version, model, None) 149 | return unified_router(rdm) 150 | 151 | 152 | @app.route("/ping/") 153 | def ping(): 154 | """ 155 | Service used to check if the connection to redis server is intact and redis server is working well. 156 | """ 157 | r = Redis(host=REDIS_SERVER, port=REDIS_PORT, password=REDIS_PASSWORD) 158 | try: 159 | r.ping() 160 | return Response("", status=200) 161 | except ConnectionError: 162 | return Response("", status=500) 163 | 164 | 165 | class RedisDatabaseManager(object): 166 | """ 167 | manages all the http requests and routes in the backend for GET, DELETE or PUT operation 168 | accordingly. 169 | 170 | """ 171 | 172 | r = Redis(host=REDIS_SERVER, port=REDIS_PORT, password=REDIS_PASSWORD) 173 | app_name = APP_NAME 174 | 175 | def __init__(self, request, version="v1", model="default", id=None): 176 | self.request = request 177 | self.version = version 178 | self.model = model 179 | self.id = id 180 | self.method = request.method 181 | self.key = "%s.%s.%s" % (self.app_name, self.version, self.model) 182 | self.status = False 183 | self.data = None 184 | self.string_data = "" 185 | self.length = 0 186 | 187 | def init_db(self): 188 | try: 189 | length = self.r.get(self.key + "_counter") 190 | if length: 191 | self.length = int(length) 192 | except ConnectionError: 193 | return False 194 | 195 | def manipulate_data(self): 196 | self.init_db() 197 | if self.method == "GET" and self.id: 198 | return self.get_from_redis() 199 | elif self.method == "GET" and not self.id: 200 | start = int(self.request.args.get("start", 1)) 201 | end = int(self.request.args.get("end", self.length)) 202 | return self.get_many_from_redis(start, end) 203 | elif (self.method == "POST" or self.method == "OPTIONS") and (not self.id or self.id == "add"): 204 | return self.set_to_redis() 205 | elif self.method == "DELETE" and self.id: 206 | self.delete_from_redis() 207 | elif self.method == "DELETE" and not self.id: 208 | if self.request.form.get("force") == "1": 209 | return self.flush_model() 210 | else: 211 | return self.delete_all_from_redis() 212 | elif (self.method == "PUT" or self.method == "POST") and self.id: 213 | return self.update_to_redis() 214 | 215 | def set_to_redis(self): 216 | try: 217 | kv_dict = {} 218 | for key in self.request.form.keys(): 219 | try: 220 | kv_dict[key] = float(self.request.form.get(key)) 221 | if int(kv_dict[key]) == kv_dict[key]: 222 | kv_dict[key] = int(self.request.form.get(key)) 223 | except ValueError: 224 | kv_dict[key] = self.request.form.get(key) 225 | kv_dict['id'] = self.length + 1 226 | self.string_data = json.dumps(kv_dict) 227 | self.r.hset(self.key, self.length + 1, self.string_data) 228 | self.r.incr(self.key + "_counter") 229 | self.data = kv_dict 230 | self.status = True 231 | except ConnectionError: 232 | self.status = False 233 | 234 | def get_from_redis(self): 235 | try: 236 | self.data = self.r.hget(self.key, self.id) 237 | self.string_data = self.data 238 | self.status = True 239 | except ConnectionError: 240 | self.status = False 241 | 242 | def get_many_from_redis(self, start, end): 243 | try: 244 | id_list = range(start, end + 1) 245 | object_list = [] 246 | if id_list: 247 | data_list = self.r.hmget(self.key, id_list) 248 | for item in data_list: 249 | if item: 250 | object_list.append(json.loads(item)) 251 | self.data = object_list 252 | self.string_data = json.dumps(object_list) 253 | self.status = True 254 | except ConnectionError: 255 | self.status = False 256 | 257 | def delete_from_redis(self): 258 | try: 259 | self.r.hdel(self.key, self.id) 260 | self.status = True 261 | except ConnectionError: 262 | self.status = False 263 | 264 | def delete_all_from_redis(self): 265 | try: 266 | id_list = range(1, self.length + 1) 267 | for identity in id_list: 268 | self.r.hdel(self.key, identity) 269 | self.status = True 270 | except ConnectionError: 271 | self.status = False 272 | 273 | def update_to_redis(self): 274 | try: 275 | kv_dict = {} 276 | for key in self.request.form.keys(): 277 | kv_dict[key] = self.request.form.get(key) 278 | kv_dict['id'] = self.id 279 | self.string_data = json.dumps(kv_dict) 280 | self.r.hset(self.key, self.id, self.string_data) 281 | self.data = kv_dict 282 | self.status = True 283 | except ConnectionError: 284 | self.status = False 285 | 286 | def flush_model(self): 287 | try: 288 | self.delete_all_from_redis() 289 | self.r.set(self.key + "_counter", 0) 290 | self.status = True 291 | except ConnectionError: 292 | self.status = False 293 | -------------------------------------------------------------------------------- /marlin/marlin_functions.py: -------------------------------------------------------------------------------- 1 | """ 2 | These are examples to get started with creating custom functions in marlin. 3 | simple - A very simple request - response 4 | 5 | """ 6 | 7 | import json 8 | from marlin import app, RedisDatabaseManager 9 | from flask import Response, request 10 | 11 | 12 | @app.route("/simple/") 13 | def simple(): 14 | return Response("Simple Custom Response") 15 | 16 | 17 | @app.route("/simple_get//") 18 | def simple_get(model): 19 | rdm = RedisDatabaseManager(request, model=model) 20 | if rdm: 21 | rdm.manipulate_data() 22 | user_id = 1 23 | rdm.id = user_id 24 | rdm.get_from_redis() # get data for the specific user id 25 | else: 26 | return json.dumps({"status": "Something is not right"}) 27 | if rdm.status and rdm.data: 28 | return Response(rdm.string_data, content_type='application/json; charset=utf-8') 29 | elif rdm.status: 30 | return Response(json.dumps({'status': "No data Found"}), content_type='application/json; charset=utf-8', 31 | status=404) 32 | else: 33 | return json.dumps({"status": "Something is not right"}) 34 | 35 | 36 | @app.route('//', methods=['GET']) 37 | def little_complicated(model): 38 | custom_range_start = 10 39 | custom_range_end = 70 40 | error_response = Response(json.dumps( 41 | {'status': "Some unknown error"}), 42 | content_type='application/json; charset=utf-8', status=500) 43 | rdm = RedisDatabaseManager(request, model=model) 44 | if rdm: 45 | rdm.manipulate_data() 46 | rdm.get_many_from_redis(custom_range_start, custom_range_end) 47 | else: 48 | return error_response 49 | if rdm.status: 50 | if rdm.data: 51 | custom_query_set = [] 52 | for datum in rdm.data: 53 | if datum.get("name") == "Orange": 54 | custom_query_set.append(datum) 55 | return Response(json.dumps(custom_query_set), content_type='application/json; charset=utf-8') 56 | else: 57 | return Response(json.dumps({'status': "No data Found"}), content_type='application/json; charset=utf-8', 58 | status=404) 59 | else: 60 | return error_response 61 | -------------------------------------------------------------------------------- /marlin/static/fonts/copse-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/copse-regular-webfont.eot -------------------------------------------------------------------------------- /marlin/static/fonts/copse-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/copse-regular-webfont.ttf -------------------------------------------------------------------------------- /marlin/static/fonts/copse-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/copse-regular-webfont.woff -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-bold-webfont.eot -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-bold-webfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG webfont generated by Font Squirrel. 6 | Copyright : Copyright c 2011 Pablo Impallari wwwimpallaricomimpallarigmailcomCopyright c 2011 Igino Marini wwwikerncommailiginomarinicomCopyright c 2011 Brenda Gallo gbrenda1987gmailcomwith Reserved Font Name Quattrocento Sans 7 | Designer : Pablo Impallari 8 | Foundry : Pablo Impallari Igino Marini Brenda Gallo 9 | Foundry URL : wwwimpallaricom 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-bold-webfont.ttf -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-bold-webfont.woff -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-bolditalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-bolditalic-webfont.eot -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-bolditalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-bolditalic-webfont.ttf -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-bolditalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-bolditalic-webfont.woff -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-italic-webfont.eot -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-italic-webfont.ttf -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-italic-webfont.woff -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-regular-webfont.eot -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-regular-webfont.ttf -------------------------------------------------------------------------------- /marlin/static/fonts/quattrocentosans-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/fonts/quattrocentosans-regular-webfont.woff -------------------------------------------------------------------------------- /marlin/static/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/images/background.png -------------------------------------------------------------------------------- /marlin/static/images/body-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/images/body-background.png -------------------------------------------------------------------------------- /marlin/static/images/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/images/bullet.png -------------------------------------------------------------------------------- /marlin/static/images/hr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/images/hr.gif -------------------------------------------------------------------------------- /marlin/static/images/octocat-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/images/octocat-logo.png -------------------------------------------------------------------------------- /marlin/static/javascripts/main.js: -------------------------------------------------------------------------------- 1 | var sectionHeight = function() { 2 | var total = $(window).height(), 3 | $section = $('section').css('height','auto'); 4 | 5 | if ($section.outerHeight(true) < total) { 6 | var margin = $section.outerHeight(true) - $section.height(); 7 | $section.height(total - margin - 20); 8 | } else { 9 | $section.css('height','auto'); 10 | } 11 | } 12 | 13 | $(window).resize(sectionHeight); 14 | 15 | $(document).ready(function(){ 16 | $("section h1, section h2").each(function(){ 17 | $("nav ul").append("
  • " + $(this).text() + "
  • "); 18 | $(this).attr("id",$(this).text().toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g,'')); 19 | $("nav ul li:first-child a").parent().addClass("active"); 20 | }); 21 | 22 | $("nav ul li").on("click", "a", function(event) { 23 | var position = $($(this).attr("href")).offset().top - 190; 24 | $("html, body").animate({scrollTop: position}, 400); 25 | $("nav ul li a").parent().removeClass("active"); 26 | $(this).parent().addClass("active"); 27 | event.preventDefault(); 28 | }); 29 | 30 | sectionHeight(); 31 | 32 | $('img').load(sectionHeight); 33 | }); 34 | 35 | fixScale = function(doc) { 36 | 37 | var addEvent = 'addEventListener', 38 | type = 'gesturestart', 39 | qsa = 'querySelectorAll', 40 | scales = [1, 1], 41 | meta = qsa in doc ? doc[qsa]('meta[name=viewport]') : []; 42 | 43 | function fix() { 44 | meta.content = 'width=device-width,minimum-scale=' + scales[0] + ',maximum-scale=' + scales[1]; 45 | doc.removeEventListener(type, fix, true); 46 | } 47 | 48 | if ((meta = meta[meta.length - 1]) && addEvent in doc) { 49 | fix(); 50 | scales = [.25, 1.6]; 51 | doc[addEvent](type, fix, true); 52 | } 53 | }; -------------------------------------------------------------------------------- /marlin/static/marlin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/marlin.jpg -------------------------------------------------------------------------------- /marlin/static/marlin.jpg~: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atmb4u/marlin/3a932e166ec6bbd2b2df4f1a32ef0306c3041c16/marlin/static/marlin.jpg~ -------------------------------------------------------------------------------- /marlin/static/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /* normalize.css 2012-02-07T12:37 UTC - http://github.com/necolas/normalize.css */ 2 | /* ============================================================================= 3 | HTML5 display definitions 4 | ========================================================================== */ 5 | /* 6 | * Corrects block display not defined in IE6/7/8/9 & FF3 7 | */ 8 | article, 9 | aside, 10 | details, 11 | figcaption, 12 | figure, 13 | footer, 14 | header, 15 | hgroup, 16 | nav, 17 | section, 18 | summary { 19 | display: block; 20 | } 21 | 22 | /* 23 | * Corrects inline-block display not defined in IE6/7/8/9 & FF3 24 | */ 25 | audio, 26 | canvas, 27 | video { 28 | display: inline-block; 29 | *display: inline; 30 | *zoom: 1; 31 | } 32 | 33 | /* 34 | * Prevents modern browsers from displaying 'audio' without controls 35 | */ 36 | audio:not([controls]) { 37 | display: none; 38 | } 39 | 40 | /* 41 | * Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4 42 | * Known issue: no IE6 support 43 | */ 44 | [hidden] { 45 | display: none; 46 | } 47 | 48 | /* ============================================================================= 49 | Base 50 | ========================================================================== */ 51 | /* 52 | * 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units 53 | * http://clagnut.com/blog/348/#c790 54 | * 2. Prevents iOS text size adjust after orientation change, without disabling user zoom 55 | * www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/ 56 | */ 57 | html { 58 | font-size: 100%; 59 | /* 1 */ 60 | -webkit-text-size-adjust: 100%; 61 | /* 2 */ 62 | -ms-text-size-adjust: 100%; 63 | /* 2 */ 64 | } 65 | 66 | /* 67 | * Addresses font-family inconsistency between 'textarea' and other form elements. 68 | */ 69 | html, 70 | button, 71 | input, 72 | select, 73 | textarea { 74 | font-family: sans-serif; 75 | } 76 | 77 | /* 78 | * Addresses margins handled incorrectly in IE6/7 79 | */ 80 | body { 81 | margin: 0; 82 | } 83 | 84 | /* ============================================================================= 85 | Links 86 | ========================================================================== */ 87 | /* 88 | * Addresses outline displayed oddly in Chrome 89 | */ 90 | a:focus { 91 | outline: thin dotted; 92 | } 93 | 94 | /* 95 | * Improves readability when focused and also mouse hovered in all browsers 96 | * people.opera.com/patrickl/experiments/keyboard/test 97 | */ 98 | a:hover, 99 | a:active { 100 | outline: 0; 101 | } 102 | 103 | /* ============================================================================= 104 | Typography 105 | ========================================================================== */ 106 | /* 107 | * Addresses font sizes and margins set differently in IE6/7 108 | * Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5 109 | */ 110 | h1 { 111 | font-size: 2em; 112 | margin: 0.67em 0; 113 | } 114 | 115 | h2 { 116 | font-size: 1.5em; 117 | margin: 0.83em 0; 118 | } 119 | 120 | h3 { 121 | font-size: 1.17em; 122 | margin: 1em 0; 123 | } 124 | 125 | h4 { 126 | font-size: 1em; 127 | margin: 1.33em 0; 128 | } 129 | 130 | h5 { 131 | font-size: 0.83em; 132 | margin: 1.67em 0; 133 | } 134 | 135 | h6 { 136 | font-size: 0.75em; 137 | margin: 2.33em 0; 138 | } 139 | 140 | /* 141 | * Addresses styling not present in IE7/8/9, S5, Chrome 142 | */ 143 | abbr[title] { 144 | border-bottom: 1px dotted; 145 | } 146 | 147 | /* 148 | * Addresses style set to 'bolder' in FF3+, S4/5, Chrome 149 | */ 150 | b, 151 | strong { 152 | font-weight: bold; 153 | } 154 | 155 | blockquote { 156 | margin: 1em 40px; 157 | } 158 | 159 | /* 160 | * Addresses styling not present in S5, Chrome 161 | */ 162 | dfn { 163 | font-style: italic; 164 | } 165 | 166 | /* 167 | * Addresses styling not present in IE6/7/8/9 168 | */ 169 | mark { 170 | background: #ff0; 171 | color: #000; 172 | } 173 | 174 | /* 175 | * Addresses margins set differently in IE6/7 176 | */ 177 | p, 178 | pre { 179 | margin: 1em 0; 180 | } 181 | 182 | /* 183 | * Corrects font family set oddly in IE6, S4/5, Chrome 184 | * en.wikipedia.org/wiki/User:Davidgothberg/Test59 185 | */ 186 | pre, 187 | code, 188 | kbd, 189 | samp { 190 | font-family: monospace, serif; 191 | _font-family: 'courier new', monospace; 192 | font-size: 1em; 193 | } 194 | 195 | /* 196 | * 1. Addresses CSS quotes not supported in IE6/7 197 | * 2. Addresses quote property not supported in S4 198 | */ 199 | /* 1 */ 200 | q { 201 | quotes: none; 202 | } 203 | 204 | /* 2 */ 205 | q:before, 206 | q:after { 207 | content: ''; 208 | content: none; 209 | } 210 | 211 | small { 212 | font-size: 75%; 213 | } 214 | 215 | /* 216 | * Prevents sub and sup affecting line-height in all browsers 217 | * gist.github.com/413930 218 | */ 219 | sub, 220 | sup { 221 | font-size: 75%; 222 | line-height: 0; 223 | position: relative; 224 | vertical-align: baseline; 225 | } 226 | 227 | sup { 228 | top: -0.5em; 229 | } 230 | 231 | sub { 232 | bottom: -0.25em; 233 | } 234 | 235 | /* ============================================================================= 236 | Lists 237 | ========================================================================== */ 238 | /* 239 | * Addresses margins set differently in IE6/7 240 | */ 241 | dl, 242 | menu, 243 | ol, 244 | ul { 245 | margin: 1em 0; 246 | } 247 | 248 | dd { 249 | margin: 0 0 0 40px; 250 | } 251 | 252 | /* 253 | * Addresses paddings set differently in IE6/7 254 | */ 255 | menu, 256 | ol, 257 | ul { 258 | padding: 0 0 0 40px; 259 | } 260 | 261 | /* 262 | * Corrects list images handled incorrectly in IE7 263 | */ 264 | nav ul, 265 | nav ol { 266 | list-style: none; 267 | list-style-image: none; 268 | } 269 | 270 | /* ============================================================================= 271 | Embedded content 272 | ========================================================================== */ 273 | /* 274 | * 1. Removes border when inside 'a' element in IE6/7/8/9, FF3 275 | * 2. Improves image quality when scaled in IE7 276 | * code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ 277 | */ 278 | img { 279 | border: 0; 280 | /* 1 */ 281 | -ms-interpolation-mode: bicubic; 282 | /* 2 */ 283 | } 284 | 285 | /* 286 | * Corrects overflow displayed oddly in IE9 287 | */ 288 | svg:not(:root) { 289 | overflow: hidden; 290 | } 291 | 292 | /* ============================================================================= 293 | Figures 294 | ========================================================================== */ 295 | /* 296 | * Addresses margin not present in IE6/7/8/9, S5, O11 297 | */ 298 | figure { 299 | margin: 0; 300 | } 301 | 302 | /* ============================================================================= 303 | Forms 304 | ========================================================================== */ 305 | /* 306 | * Corrects margin displayed oddly in IE6/7 307 | */ 308 | form { 309 | margin: 0; 310 | } 311 | 312 | /* 313 | * Define consistent border, margin, and padding 314 | */ 315 | fieldset { 316 | border: 1px solid #c0c0c0; 317 | margin: 0 2px; 318 | padding: 0.35em 0.625em 0.75em; 319 | } 320 | 321 | /* 322 | * 1. Corrects color not being inherited in IE6/7/8/9 323 | * 2. Corrects text not wrapping in FF3 324 | * 3. Corrects alignment displayed oddly in IE6/7 325 | */ 326 | legend { 327 | border: 0; 328 | /* 1 */ 329 | padding: 0; 330 | white-space: normal; 331 | /* 2 */ 332 | *margin-left: -7px; 333 | /* 3 */ 334 | } 335 | 336 | /* 337 | * 1. Corrects font size not being inherited in all browsers 338 | * 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome 339 | * 3. Improves appearance and consistency in all browsers 340 | */ 341 | button, 342 | input, 343 | select, 344 | textarea { 345 | font-size: 100%; 346 | /* 1 */ 347 | margin: 0; 348 | /* 2 */ 349 | vertical-align: baseline; 350 | /* 3 */ 351 | *vertical-align: middle; 352 | /* 3 */ 353 | } 354 | 355 | /* 356 | * Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet 357 | */ 358 | button, 359 | input { 360 | line-height: normal; 361 | /* 1 */ 362 | } 363 | 364 | /* 365 | * 1. Improves usability and consistency of cursor style between image-type 'input' and others 366 | * 2. Corrects inability to style clickable 'input' types in iOS 367 | * 3. Removes inner spacing in IE7 without affecting normal text inputs 368 | * Known issue: inner spacing remains in IE6 369 | */ 370 | button, 371 | input[type="button"], 372 | input[type="reset"], 373 | input[type="submit"] { 374 | cursor: pointer; 375 | /* 1 */ 376 | -webkit-appearance: button; 377 | /* 2 */ 378 | *overflow: visible; 379 | /* 3 */ 380 | } 381 | 382 | /* 383 | * Re-set default cursor for disabled elements 384 | */ 385 | button[disabled], 386 | input[disabled] { 387 | cursor: default; 388 | } 389 | 390 | /* 391 | * 1. Addresses box sizing set to content-box in IE8/9 392 | * 2. Removes excess padding in IE8/9 393 | * 3. Removes excess padding in IE7 394 | Known issue: excess padding remains in IE6 395 | */ 396 | input[type="checkbox"], 397 | input[type="radio"] { 398 | box-sizing: border-box; 399 | /* 1 */ 400 | padding: 0; 401 | /* 2 */ 402 | *height: 13px; 403 | /* 3 */ 404 | *width: 13px; 405 | /* 3 */ 406 | } 407 | 408 | /* 409 | * 1. Addresses appearance set to searchfield in S5, Chrome 410 | * 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof) 411 | */ 412 | input[type="search"] { 413 | -webkit-appearance: textfield; 414 | /* 1 */ 415 | -moz-box-sizing: content-box; 416 | -webkit-box-sizing: content-box; 417 | /* 2 */ 418 | box-sizing: content-box; 419 | } 420 | 421 | /* 422 | * Removes inner padding and search cancel button in S5, Chrome on OS X 423 | */ 424 | input[type="search"]::-webkit-search-decoration, 425 | input[type="search"]::-webkit-search-cancel-button { 426 | -webkit-appearance: none; 427 | } 428 | 429 | /* 430 | * Removes inner padding and border in FF3+ 431 | * www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/ 432 | */ 433 | button::-moz-focus-inner, 434 | input::-moz-focus-inner { 435 | border: 0; 436 | padding: 0; 437 | } 438 | 439 | /* 440 | * 1. Removes default vertical scrollbar in IE6/7/8/9 441 | * 2. Improves readability and alignment in all browsers 442 | */ 443 | textarea { 444 | overflow: auto; 445 | /* 1 */ 446 | vertical-align: top; 447 | /* 2 */ 448 | } 449 | 450 | /* ============================================================================= 451 | Tables 452 | ========================================================================== */ 453 | /* 454 | * Remove most spacing between table cells 455 | */ 456 | table { 457 | border-collapse: collapse; 458 | border-spacing: 0; 459 | } 460 | -------------------------------------------------------------------------------- /marlin/static/stylesheets/pygment_trac.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #404040 } 2 | .highlight { color: #d0d0d0 } 3 | .highlight .c { color: #999999; font-style: italic } /* Comment */ 4 | .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 5 | .highlight .g { color: #d0d0d0 } /* Generic */ 6 | .highlight .k { color: #6ab825; font-weight: normal } /* Keyword */ 7 | .highlight .l { color: #d0d0d0 } /* Literal */ 8 | .highlight .n { color: #d0d0d0 } /* Name */ 9 | .highlight .o { color: #d0d0d0 } /* Operator */ 10 | .highlight .x { color: #d0d0d0 } /* Other */ 11 | .highlight .p { color: #d0d0d0 } /* Punctuation */ 12 | .highlight .cm { color: #999999; font-style: italic } /* Comment.Multiline */ 13 | .highlight .cp { color: #cd2828; font-weight: normal } /* Comment.Preproc */ 14 | .highlight .c1 { color: #999999; font-style: italic } /* Comment.Single */ 15 | .highlight .cs { color: #e50808; font-weight: normal; background-color: #520000 } /* Comment.Special */ 16 | .highlight .gd { color: #d22323 } /* Generic.Deleted */ 17 | .highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */ 18 | .highlight .gr { color: #d22323 } /* Generic.Error */ 19 | .highlight .gh { color: #ffffff; font-weight: normal } /* Generic.Heading */ 20 | .highlight .gi { color: #589819 } /* Generic.Inserted */ 21 | .highlight .go { color: #cccccc } /* Generic.Output */ 22 | .highlight .gp { color: #aaaaaa } /* Generic.Prompt */ 23 | .highlight .gs { color: #d0d0d0; font-weight: normal } /* Generic.Strong */ 24 | .highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */ 25 | .highlight .gt { color: #d22323 } /* Generic.Traceback */ 26 | .highlight .kc { color: #6ab825; font-weight: normal } /* Keyword.Constant */ 27 | .highlight .kd { color: #6ab825; font-weight: normal } /* Keyword.Declaration */ 28 | .highlight .kn { color: #6ab825; font-weight: normal } /* Keyword.Namespace */ 29 | .highlight .kp { color: #6ab825 } /* Keyword.Pseudo */ 30 | .highlight .kr { color: #6ab825; font-weight: normal } /* Keyword.Reserved */ 31 | .highlight .kt { color: #6ab825; font-weight: normal } /* Keyword.Type */ 32 | .highlight .ld { color: #d0d0d0 } /* Literal.Date */ 33 | .highlight .m { color: #3677a9 } /* Literal.Number */ 34 | .highlight .s { color: #ff8 } /* Literal.String */ 35 | .highlight .na { color: #bbbbbb } /* Name.Attribute */ 36 | .highlight .nb { color: #24909d } /* Name.Builtin */ 37 | .highlight .nc { color: #447fcf; text-decoration: underline } /* Name.Class */ 38 | .highlight .no { color: #40ffff } /* Name.Constant */ 39 | .highlight .nd { color: #ffa500 } /* Name.Decorator */ 40 | .highlight .ni { color: #d0d0d0 } /* Name.Entity */ 41 | .highlight .ne { color: #bbbbbb } /* Name.Exception */ 42 | .highlight .nf { color: #447fcf } /* Name.Function */ 43 | .highlight .nl { color: #d0d0d0 } /* Name.Label */ 44 | .highlight .nn { color: #447fcf; text-decoration: underline } /* Name.Namespace */ 45 | .highlight .nx { color: #d0d0d0 } /* Name.Other */ 46 | .highlight .py { color: #d0d0d0 } /* Name.Property */ 47 | .highlight .nt { color: #6ab825;} /* Name.Tag */ 48 | .highlight .nv { color: #40ffff } /* Name.Variable */ 49 | .highlight .ow { color: #6ab825; font-weight: normal } /* Operator.Word */ 50 | .highlight .w { color: #666666 } /* Text.Whitespace */ 51 | .highlight .mf { color: #3677a9 } /* Literal.Number.Float */ 52 | .highlight .mh { color: #3677a9 } /* Literal.Number.Hex */ 53 | .highlight .mi { color: #3677a9 } /* Literal.Number.Integer */ 54 | .highlight .mo { color: #3677a9 } /* Literal.Number.Oct */ 55 | .highlight .sb { color: #ff8 } /* Literal.String.Backtick */ 56 | .highlight .sc { color: #ff8 } /* Literal.String.Char */ 57 | .highlight .sd { color: #ff8 } /* Literal.String.Doc */ 58 | .highlight .s2 { color: #ff8 } /* Literal.String.Double */ 59 | .highlight .se { color: #ff8 } /* Literal.String.Escape */ 60 | .highlight .sh { color: #ff8 } /* Literal.String.Heredoc */ 61 | .highlight .si { color: #ff8 } /* Literal.String.Interpol */ 62 | .highlight .sx { color: #ffa500 } /* Literal.String.Other */ 63 | .highlight .sr { color: #ff8 } /* Literal.String.Regex */ 64 | .highlight .s1 { color: #ff8 } /* Literal.String.Single */ 65 | .highlight .ss { color: #ff8 } /* Literal.String.Symbol */ 66 | .highlight .bp { color: #24909d } /* Name.Builtin.Pseudo */ 67 | .highlight .vc { color: #40ffff } /* Name.Variable.Class */ 68 | .highlight .vg { color: #40ffff } /* Name.Variable.Global */ 69 | .highlight .vi { color: #40ffff } /* Name.Variable.Instance */ 70 | .highlight .il { color: #3677a9 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /marlin/static/stylesheets/styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | Leap Day for GitHub Pages 3 | by Matt Graham 4 | */ 5 | @font-face { 6 | font-family: 'Quattrocento Sans'; 7 | src: url("../fonts/quattrocentosans-bold-webfont.eot"); 8 | src: url("../fonts/quattrocentosans-bold-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/quattrocentosans-bold-webfont.woff") format("woff"), url("../fonts/quattrocentosans-bold-webfont.ttf") format("truetype"), url("../fonts/quattrocentosans-bold-webfont.svg#QuattrocentoSansBold") format("svg"); 9 | font-weight: bold; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'Quattrocento Sans'; 15 | src: url("../fonts/quattrocentosans-bolditalic-webfont.eot"); 16 | src: url("../fonts/quattrocentosans-bolditalic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/quattrocentosans-bolditalic-webfont.woff") format("woff"), url("../fonts/quattrocentosans-bolditalic-webfont.ttf") format("truetype"), url("../fonts/quattrocentosans-bolditalic-webfont.svg#QuattrocentoSansBoldItalic") format("svg"); 17 | font-weight: bold; 18 | font-style: italic; 19 | } 20 | 21 | @font-face { 22 | font-family: 'Quattrocento Sans'; 23 | src: url("../fonts/quattrocentosans-italic-webfont.eot"); 24 | src: url("../fonts/quattrocentosans-italic-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/quattrocentosans-italic-webfont.woff") format("woff"), url("../fonts/quattrocentosans-italic-webfont.ttf") format("truetype"), url("../fonts/quattrocentosans-italic-webfont.svg#QuattrocentoSansItalic") format("svg"); 25 | font-weight: normal; 26 | font-style: italic; 27 | } 28 | 29 | @font-face { 30 | font-family: 'Quattrocento Sans'; 31 | src: url("../fonts/quattrocentosans-regular-webfont.eot"); 32 | src: url("../fonts/quattrocentosans-regular-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/quattrocentosans-regular-webfont.woff") format("woff"), url("../fonts/quattrocentosans-regular-webfont.ttf") format("truetype"), url("../fonts/quattrocentosans-regular-webfont.svg#QuattrocentoSansRegular") format("svg"); 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | @font-face { 38 | font-family: 'Copse'; 39 | src: url("../fonts/copse-regular-webfont.eot"); 40 | src: url("../fonts/copse-regular-webfont.eot?#iefix") format("embedded-opentype"), url("../fonts/copse-regular-webfont.woff") format("woff"), url("../fonts/copse-regular-webfont.ttf") format("truetype"), url("../fonts/copse-regular-webfont.svg#CopseRegular") format("svg"); 41 | font-weight: normal; 42 | font-style: normal; 43 | } 44 | 45 | /* normalize.css 2012-02-07T12:37 UTC - http://github.com/necolas/normalize.css */ 46 | /* ============================================================================= 47 | HTML5 display definitions 48 | ========================================================================== */ 49 | /* 50 | * Corrects block display not defined in IE6/7/8/9 & FF3 51 | */ 52 | article, 53 | aside, 54 | details, 55 | figcaption, 56 | figure, 57 | footer, 58 | header, 59 | hgroup, 60 | nav, 61 | section, 62 | summary { 63 | display: block; 64 | } 65 | 66 | /* 67 | * Corrects inline-block display not defined in IE6/7/8/9 & FF3 68 | */ 69 | audio, 70 | canvas, 71 | video { 72 | display: inline-block; 73 | *display: inline; 74 | *zoom: 1; 75 | } 76 | 77 | /* 78 | * Prevents modern browsers from displaying 'audio' without controls 79 | */ 80 | audio:not([controls]) { 81 | display: none; 82 | } 83 | 84 | /* 85 | * Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4 86 | * Known issue: no IE6 support 87 | */ 88 | [hidden] { 89 | display: none; 90 | } 91 | 92 | /* ============================================================================= 93 | Base 94 | ========================================================================== */ 95 | /* 96 | * 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units 97 | * http://clagnut.com/blog/348/#c790 98 | * 2. Prevents iOS text size adjust after orientation change, without disabling user zoom 99 | * www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/ 100 | */ 101 | html { 102 | font-size: 100%; 103 | /* 1 */ 104 | -webkit-text-size-adjust: 100%; 105 | /* 2 */ 106 | -ms-text-size-adjust: 100%; 107 | /* 2 */ 108 | } 109 | 110 | /* 111 | * Addresses font-family inconsistency between 'textarea' and other form elements. 112 | */ 113 | html, 114 | button, 115 | input, 116 | select, 117 | textarea { 118 | font-family: sans-serif; 119 | } 120 | 121 | /* 122 | * Addresses margins handled incorrectly in IE6/7 123 | */ 124 | body { 125 | margin: 0; 126 | } 127 | 128 | /* ============================================================================= 129 | Links 130 | ========================================================================== */ 131 | /* 132 | * Addresses outline displayed oddly in Chrome 133 | */ 134 | a:focus { 135 | outline: thin dotted; 136 | } 137 | 138 | /* 139 | * Improves readability when focused and also mouse hovered in all browsers 140 | * people.opera.com/patrickl/experiments/keyboard/test 141 | */ 142 | a:hover, 143 | a:active { 144 | outline: 0; 145 | } 146 | 147 | /* ============================================================================= 148 | Typography 149 | ========================================================================== */ 150 | /* 151 | * Addresses font sizes and margins set differently in IE6/7 152 | * Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5 153 | */ 154 | h1 { 155 | font-size: 2em; 156 | margin: 0.67em 0; 157 | } 158 | 159 | h2 { 160 | font-size: 1.5em; 161 | margin: 0.83em 0; 162 | } 163 | 164 | h3 { 165 | font-size: 1.17em; 166 | margin: 1em 0; 167 | } 168 | 169 | h4 { 170 | font-size: 1em; 171 | margin: 1.33em 0; 172 | } 173 | 174 | h5 { 175 | font-size: 0.83em; 176 | margin: 1.67em 0; 177 | } 178 | 179 | h6 { 180 | font-size: 0.75em; 181 | margin: 2.33em 0; 182 | } 183 | 184 | /* 185 | * Addresses styling not present in IE7/8/9, S5, Chrome 186 | */ 187 | abbr[title] { 188 | border-bottom: 1px dotted; 189 | } 190 | 191 | /* 192 | * Addresses style set to 'bolder' in FF3+, S4/5, Chrome 193 | */ 194 | b, 195 | strong { 196 | font-weight: bold; 197 | } 198 | 199 | blockquote { 200 | margin: 1em 40px; 201 | } 202 | 203 | /* 204 | * Addresses styling not present in S5, Chrome 205 | */ 206 | dfn { 207 | font-style: italic; 208 | } 209 | 210 | /* 211 | * Addresses styling not present in IE6/7/8/9 212 | */ 213 | mark { 214 | background: #ff0; 215 | color: #000; 216 | } 217 | 218 | /* 219 | * Addresses margins set differently in IE6/7 220 | */ 221 | p, 222 | pre { 223 | margin: 1em 0; 224 | } 225 | 226 | /* 227 | * Corrects font family set oddly in IE6, S4/5, Chrome 228 | * en.wikipedia.org/wiki/User:Davidgothberg/Test59 229 | */ 230 | pre, 231 | code, 232 | kbd, 233 | samp { 234 | font-family: monospace, serif; 235 | _font-family: 'courier new', monospace; 236 | font-size: 1em; 237 | } 238 | 239 | /* 240 | * 1. Addresses CSS quotes not supported in IE6/7 241 | * 2. Addresses quote property not supported in S4 242 | */ 243 | /* 1 */ 244 | q { 245 | quotes: none; 246 | } 247 | 248 | /* 2 */ 249 | q:before, 250 | q:after { 251 | content: ''; 252 | content: none; 253 | } 254 | 255 | small { 256 | font-size: 75%; 257 | } 258 | 259 | /* 260 | * Prevents sub and sup affecting line-height in all browsers 261 | * gist.github.com/413930 262 | */ 263 | sub, 264 | sup { 265 | font-size: 75%; 266 | line-height: 0; 267 | position: relative; 268 | vertical-align: baseline; 269 | } 270 | 271 | sup { 272 | top: -0.5em; 273 | } 274 | 275 | sub { 276 | bottom: -0.25em; 277 | } 278 | 279 | /* ============================================================================= 280 | Lists 281 | ========================================================================== */ 282 | /* 283 | * Addresses margins set differently in IE6/7 284 | */ 285 | dl, 286 | menu, 287 | ol, 288 | ul { 289 | margin: 1em 0; 290 | } 291 | 292 | dd { 293 | margin: 0 0 0 40px; 294 | } 295 | 296 | /* 297 | * Addresses paddings set differently in IE6/7 298 | */ 299 | menu, 300 | ol, 301 | ul { 302 | padding: 0 0 0 40px; 303 | } 304 | 305 | /* 306 | * Corrects list images handled incorrectly in IE7 307 | */ 308 | nav ul, 309 | nav ol { 310 | list-style: none; 311 | list-style-image: none; 312 | } 313 | 314 | /* ============================================================================= 315 | Embedded content 316 | ========================================================================== */ 317 | /* 318 | * 1. Removes border when inside 'a' element in IE6/7/8/9, FF3 319 | * 2. Improves image quality when scaled in IE7 320 | * code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ 321 | */ 322 | img { 323 | border: 0; 324 | /* 1 */ 325 | -ms-interpolation-mode: bicubic; 326 | /* 2 */ 327 | } 328 | 329 | /* 330 | * Corrects overflow displayed oddly in IE9 331 | */ 332 | svg:not(:root) { 333 | overflow: hidden; 334 | } 335 | 336 | /* ============================================================================= 337 | Figures 338 | ========================================================================== */ 339 | /* 340 | * Addresses margin not present in IE6/7/8/9, S5, O11 341 | */ 342 | figure { 343 | margin: 0; 344 | } 345 | 346 | /* ============================================================================= 347 | Forms 348 | ========================================================================== */ 349 | /* 350 | * Corrects margin displayed oddly in IE6/7 351 | */ 352 | form { 353 | margin: 0; 354 | } 355 | 356 | /* 357 | * Define consistent border, margin, and padding 358 | */ 359 | fieldset { 360 | border: 1px solid #c0c0c0; 361 | margin: 0 2px; 362 | padding: 0.35em 0.625em 0.75em; 363 | } 364 | 365 | /* 366 | * 1. Corrects color not being inherited in IE6/7/8/9 367 | * 2. Corrects text not wrapping in FF3 368 | * 3. Corrects alignment displayed oddly in IE6/7 369 | */ 370 | legend { 371 | border: 0; 372 | /* 1 */ 373 | padding: 0; 374 | white-space: normal; 375 | /* 2 */ 376 | *margin-left: -7px; 377 | /* 3 */ 378 | } 379 | 380 | /* 381 | * 1. Corrects font size not being inherited in all browsers 382 | * 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome 383 | * 3. Improves appearance and consistency in all browsers 384 | */ 385 | button, 386 | input, 387 | select, 388 | textarea { 389 | font-size: 100%; 390 | /* 1 */ 391 | margin: 0; 392 | /* 2 */ 393 | vertical-align: baseline; 394 | /* 3 */ 395 | *vertical-align: middle; 396 | /* 3 */ 397 | } 398 | 399 | /* 400 | * Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet 401 | */ 402 | button, 403 | input { 404 | line-height: normal; 405 | /* 1 */ 406 | } 407 | 408 | /* 409 | * 1. Improves usability and consistency of cursor style between image-type 'input' and others 410 | * 2. Corrects inability to style clickable 'input' types in iOS 411 | * 3. Removes inner spacing in IE7 without affecting normal text inputs 412 | * Known issue: inner spacing remains in IE6 413 | */ 414 | button, 415 | input[type="button"], 416 | input[type="reset"], 417 | input[type="submit"] { 418 | cursor: pointer; 419 | /* 1 */ 420 | -webkit-appearance: button; 421 | /* 2 */ 422 | *overflow: visible; 423 | /* 3 */ 424 | } 425 | 426 | /* 427 | * Re-set default cursor for disabled elements 428 | */ 429 | button[disabled], 430 | input[disabled] { 431 | cursor: default; 432 | } 433 | 434 | /* 435 | * 1. Addresses box sizing set to content-box in IE8/9 436 | * 2. Removes excess padding in IE8/9 437 | * 3. Removes excess padding in IE7 438 | Known issue: excess padding remains in IE6 439 | */ 440 | input[type="checkbox"], 441 | input[type="radio"] { 442 | box-sizing: border-box; 443 | /* 1 */ 444 | padding: 0; 445 | /* 2 */ 446 | *height: 13px; 447 | /* 3 */ 448 | *width: 13px; 449 | /* 3 */ 450 | } 451 | 452 | /* 453 | * 1. Addresses appearance set to searchfield in S5, Chrome 454 | * 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof) 455 | */ 456 | input[type="search"] { 457 | -webkit-appearance: textfield; 458 | /* 1 */ 459 | -moz-box-sizing: content-box; 460 | -webkit-box-sizing: content-box; 461 | /* 2 */ 462 | box-sizing: content-box; 463 | } 464 | 465 | /* 466 | * Removes inner padding and search cancel button in S5, Chrome on OS X 467 | */ 468 | input[type="search"]::-webkit-search-decoration, 469 | input[type="search"]::-webkit-search-cancel-button { 470 | -webkit-appearance: none; 471 | } 472 | 473 | /* 474 | * Removes inner padding and border in FF3+ 475 | * www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/ 476 | */ 477 | button::-moz-focus-inner, 478 | input::-moz-focus-inner { 479 | border: 0; 480 | padding: 0; 481 | } 482 | 483 | /* 484 | * 1. Removes default vertical scrollbar in IE6/7/8/9 485 | * 2. Improves readability and alignment in all browsers 486 | */ 487 | textarea { 488 | overflow: auto; 489 | /* 1 */ 490 | vertical-align: top; 491 | /* 2 */ 492 | } 493 | 494 | /* ============================================================================= 495 | Tables 496 | ========================================================================== */ 497 | /* 498 | * Remove most spacing between table cells 499 | */ 500 | table { 501 | border-collapse: collapse; 502 | border-spacing: 0; 503 | } 504 | 505 | body { 506 | font: 14px/22px "Quattrocento Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; 507 | color: #666; 508 | font-weight: 300; 509 | margin: 0px; 510 | padding: 0px 0 20px 0px; 511 | background: url(../images/body-background.png) #eae6d1; 512 | } 513 | 514 | h1, h2, h3, h4, h5, h6 { 515 | color: #333; 516 | margin: 0 0 10px; 517 | } 518 | 519 | p, ul, ol, table, pre, dl { 520 | margin: 0 0 20px; 521 | } 522 | 523 | h1, h2, h3 { 524 | line-height: 1.1; 525 | } 526 | 527 | h1 { 528 | font-size: 28px; 529 | } 530 | 531 | h2 { 532 | font-size: 24px; 533 | color: #393939; 534 | } 535 | 536 | h3, h4, h5, h6 { 537 | color: #666666; 538 | } 539 | 540 | h3 { 541 | font-size: 18px; 542 | line-height: 24px; 543 | } 544 | 545 | a { 546 | color: #3399cc; 547 | font-weight: 400; 548 | text-decoration: none; 549 | } 550 | 551 | a small { 552 | font-size: 11px; 553 | color: #666; 554 | margin-top: -0.6em; 555 | display: block; 556 | } 557 | 558 | ul { 559 | list-style-image: url("../images/bullet.png"); 560 | } 561 | 562 | strong { 563 | font-weight: bold; 564 | color: #333; 565 | } 566 | 567 | .wrapper { 568 | width: 650px; 569 | margin: 0 auto; 570 | position: relative; 571 | } 572 | 573 | section img { 574 | max-width: 100%; 575 | } 576 | 577 | blockquote { 578 | border-left: 1px solid #ffcc00; 579 | margin: 0; 580 | padding: 0 0 0 20px; 581 | font-style: italic; 582 | } 583 | 584 | code { 585 | font-family: "Lucida Sans", Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; 586 | font-size: 13px; 587 | color: #efefef; 588 | text-shadow: 0px 1px 0px #000; 589 | margin: 0 4px; 590 | padding: 2px 6px; 591 | background: #333; 592 | -moz-border-radius: 2px; 593 | -webkit-border-radius: 2px; 594 | -o-border-radius: 2px; 595 | -ms-border-radius: 2px; 596 | -khtml-border-radius: 2px; 597 | border-radius: 2px; 598 | } 599 | 600 | pre { 601 | padding: 8px 15px; 602 | background: #333333; 603 | -moz-border-radius: 3px; 604 | -webkit-border-radius: 3px; 605 | -o-border-radius: 3px; 606 | -ms-border-radius: 3px; 607 | -khtml-border-radius: 3px; 608 | border-radius: 3px; 609 | border: 1px solid #c7c7c7; 610 | overflow: auto; 611 | overflow-y: hidden; 612 | } 613 | pre code { 614 | margin: 0px; 615 | padding: 0px; 616 | } 617 | 618 | table { 619 | width: 100%; 620 | border-collapse: collapse; 621 | } 622 | 623 | th { 624 | text-align: left; 625 | padding: 5px 10px; 626 | border-bottom: 1px solid #e5e5e5; 627 | color: #444; 628 | } 629 | 630 | td { 631 | text-align: left; 632 | padding: 5px 10px; 633 | border-bottom: 1px solid #e5e5e5; 634 | border-right: 1px solid #ffcc00; 635 | } 636 | td:first-child { 637 | border-left: 1px solid #ffcc00; 638 | } 639 | 640 | hr { 641 | border: 0; 642 | outline: none; 643 | height: 11px; 644 | background: transparent url("../images/hr.gif") center center repeat-x; 645 | margin: 0 0 20px; 646 | } 647 | 648 | dt { 649 | color: #444; 650 | font-weight: 700; 651 | } 652 | 653 | header { 654 | padding: 25px 20px 40px 20px; 655 | margin: 0; 656 | position: fixed; 657 | top: 0; 658 | left: 0; 659 | right: 0; 660 | width: 100%; 661 | text-align: center; 662 | background: url(../images/background.png) #4276b6; 663 | -moz-box-shadow: 1px 0px 2px rgba(0, 0, 0, 0.75); 664 | -webkit-box-shadow: 1px 0px 2px rgba(0, 0, 0, 0.75); 665 | -o-box-shadow: 1px 0px 2px rgba(0, 0, 0, 0.75); 666 | box-shadow: 1px 0px 2px rgba(0, 0, 0, 0.75); 667 | z-index: 99; 668 | -webkit-font-smoothing: antialiased; 669 | min-height: 76px; 670 | } 671 | header h1 { 672 | font: 40px/48px "Copse", "Helvetica Neue", Helvetica, Arial, sans-serif; 673 | color: #f3f3f3; 674 | text-shadow: 0px 2px 0px #235796; 675 | margin: 0px; 676 | white-space: nowrap; 677 | overflow: hidden; 678 | text-overflow: ellipsis; 679 | -o-text-overflow: ellipsis; 680 | -ms-text-overflow: ellipsis; 681 | } 682 | header p { 683 | color: #d8d8d8; 684 | text-shadow: rgba(0, 0, 0, 0.2) 0 1px 0; 685 | font-size: 18px; 686 | margin: 0px; 687 | } 688 | 689 | #banner { 690 | z-index: 100; 691 | left: 0; 692 | right: 50%; 693 | height: 50px; 694 | margin-right: -382px; 695 | position: fixed; 696 | top: 115px; 697 | background: #ffcc00; 698 | border: 1px solid #f0b500; 699 | -moz-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); 700 | -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); 701 | -o-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); 702 | box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.25); 703 | -moz-border-radius: 0px 2px 2px 0px; 704 | -webkit-border-radius: 0px 2px 2px 0px; 705 | -o-border-radius: 0px 2px 2px 0px; 706 | -ms-border-radius: 0px 2px 2px 0px; 707 | -khtml-border-radius: 0px 2px 2px 0px; 708 | border-radius: 0px 2px 2px 0px; 709 | padding-right: 10px; 710 | } 711 | #banner .button { 712 | border: 1px solid #dba500; 713 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffe788), color-stop(100%, #ffce38)); 714 | background: -webkit-linear-gradient(#ffe788, #ffce38); 715 | background: -moz-linear-gradient(#ffe788, #ffce38); 716 | background: -o-linear-gradient(#ffe788, #ffce38); 717 | background: -ms-linear-gradient(#ffe788, #ffce38); 718 | background: linear-gradient(#ffe788, #ffce38); 719 | -moz-border-radius: 2px; 720 | -webkit-border-radius: 2px; 721 | -o-border-radius: 2px; 722 | -ms-border-radius: 2px; 723 | -khtml-border-radius: 2px; 724 | border-radius: 2px; 725 | -moz-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.4), 0px 1px 1px rgba(0, 0, 0, 0.1); 726 | -webkit-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.4), 0px 1px 1px rgba(0, 0, 0, 0.1); 727 | -o-box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.4), 0px 1px 1px rgba(0, 0, 0, 0.1); 728 | box-shadow: inset 0px 1px 0px rgba(255, 255, 255, 0.4), 0px 1px 1px rgba(0, 0, 0, 0.1); 729 | background-color: #FFE788; 730 | margin-left: 5px; 731 | padding: 10px 12px; 732 | margin-top: 6px; 733 | line-height: 14px; 734 | font-size: 14px; 735 | color: #333; 736 | font-weight: bold; 737 | display: inline-block; 738 | text-align: center; 739 | } 740 | #banner .button:hover { 741 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffe788), color-stop(100%, #ffe788)); 742 | background: -webkit-linear-gradient(#ffe788, #ffe788); 743 | background: -moz-linear-gradient(#ffe788, #ffe788); 744 | background: -o-linear-gradient(#ffe788, #ffe788); 745 | background: -ms-linear-gradient(#ffe788, #ffe788); 746 | background: linear-gradient(#ffe788, #ffe788); 747 | background-color: #ffeca0; 748 | } 749 | #banner .fork { 750 | position: fixed; 751 | left: 50%; 752 | margin-left: -325px; 753 | padding: 10px 12px; 754 | margin-top: 6px; 755 | line-height: 14px; 756 | font-size: 14px; 757 | background-color: #FFE788; 758 | } 759 | #banner .downloads { 760 | float: right; 761 | margin: 0 45px 0 0; 762 | } 763 | #banner .downloads span { 764 | float: left; 765 | line-height: 52px; 766 | font-size: 90%; 767 | color: #9d7f0d; 768 | text-transform: uppercase; 769 | text-shadow: rgba(255, 255, 255, 0.2) 0 1px 0; 770 | } 771 | #banner ul { 772 | list-style: none; 773 | height: 40px; 774 | padding: 0; 775 | float: left; 776 | margin-left: 10px; 777 | } 778 | #banner ul li { 779 | display: inline; 780 | } 781 | #banner ul li a.button { 782 | background-color: #FFE788; 783 | } 784 | #banner #logo { 785 | position: absolute; 786 | height: 36px; 787 | width: 36px; 788 | right: 7px; 789 | top: 7px; 790 | display: block; 791 | background: url(../images/octocat-logo.png); 792 | } 793 | 794 | section { 795 | width: 590px; 796 | padding: 30px 30px 50px 30px; 797 | margin: 20px 0; 798 | margin-top: 190px; 799 | position: relative; 800 | background: #fbfbfb; 801 | -moz-border-radius: 3px; 802 | -webkit-border-radius: 3px; 803 | -o-border-radius: 3px; 804 | -ms-border-radius: 3px; 805 | -khtml-border-radius: 3px; 806 | border-radius: 3px; 807 | border: 1px solid #cbcbcb; 808 | -moz-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.09), inset 0px 0px 2px 2px rgba(255, 255, 255, 0.5), inset 0 0 5px 5px rgba(255, 255, 255, 0.4); 809 | -webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.09), inset 0px 0px 2px 2px rgba(255, 255, 255, 0.5), inset 0 0 5px 5px rgba(255, 255, 255, 0.4); 810 | -o-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.09), inset 0px 0px 2px 2px rgba(255, 255, 255, 0.5), inset 0 0 5px 5px rgba(255, 255, 255, 0.4); 811 | box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.09), inset 0px 0px 2px 2px rgba(255, 255, 255, 0.5), inset 0 0 5px 5px rgba(255, 255, 255, 0.4); 812 | } 813 | 814 | small { 815 | font-size: 12px; 816 | } 817 | 818 | nav { 819 | width: 230px; 820 | position: fixed; 821 | top: 220px; 822 | left: 50%; 823 | margin-left: -580px; 824 | text-align: right; 825 | } 826 | nav ul { 827 | list-style: none; 828 | list-style-image: none; 829 | font-size: 14px; 830 | line-height: 24px; 831 | } 832 | nav ul li { 833 | padding: 5px 0px; 834 | line-height: 16px; 835 | } 836 | nav ul li.tag-h1 { 837 | font-size: 1.2em; 838 | } 839 | nav ul li.tag-h1 a { 840 | font-weight: bold; 841 | color: #333; 842 | } 843 | nav ul li.tag-h2 + .tag-h1 { 844 | margin-top: 10px; 845 | } 846 | nav ul a { 847 | color: #666; 848 | } 849 | nav ul a:hover { 850 | color: #999; 851 | } 852 | 853 | footer { 854 | width: 180px; 855 | position: fixed; 856 | left: 50%; 857 | margin-left: -530px; 858 | bottom: 20px; 859 | text-align: right; 860 | line-height: 16px; 861 | } 862 | 863 | @media print, screen and (max-width: 1060px) { 864 | div.wrapper { 865 | width: auto; 866 | margin: 0; 867 | } 868 | 869 | nav { 870 | display: none; 871 | } 872 | 873 | header, section, footer { 874 | float: none; 875 | } 876 | header h1, section h1, footer h1 { 877 | white-space: nowrap; 878 | overflow: hidden; 879 | text-overflow: ellipsis; 880 | -o-text-overflow: ellipsis; 881 | -ms-text-overflow: ellipsis; 882 | } 883 | 884 | #banner { 885 | width: 100%; 886 | } 887 | #banner .downloads { 888 | margin-right: 60px; 889 | } 890 | #banner #logo { 891 | margin-right: 15px; 892 | } 893 | 894 | section { 895 | border: 1px solid #e5e5e5; 896 | border-width: 1px 0; 897 | padding: 20px auto; 898 | margin: 190px auto 20px; 899 | max-width: 600px; 900 | } 901 | 902 | footer { 903 | text-align: center; 904 | margin: 20px auto; 905 | position: relative; 906 | left: auto; 907 | bottom: auto; 908 | width: auto; 909 | } 910 | } 911 | @media print, screen and (max-width: 720px) { 912 | body { 913 | word-wrap: break-word; 914 | } 915 | 916 | header { 917 | padding: 20px 20px; 918 | margin: 0; 919 | } 920 | header h1 { 921 | font-size: 32px; 922 | white-space: nowrap; 923 | overflow: hidden; 924 | text-overflow: ellipsis; 925 | -o-text-overflow: ellipsis; 926 | -ms-text-overflow: ellipsis; 927 | } 928 | header p { 929 | display: none; 930 | } 931 | 932 | #banner { 933 | top: 80px; 934 | } 935 | #banner .fork { 936 | float: left; 937 | display: inline-block; 938 | margin-left: 0px; 939 | position: fixed; 940 | left: 20px; 941 | } 942 | 943 | section { 944 | margin-top: 130px; 945 | margin-bottom: 0px; 946 | width: auto; 947 | } 948 | 949 | header ul, header p.view { 950 | position: static; 951 | } 952 | } 953 | @media print, screen and (max-width: 480px) { 954 | header { 955 | position: relative; 956 | padding: 5px 0px; 957 | min-height: 0px; 958 | } 959 | header h1 { 960 | font-size: 24px; 961 | white-space: nowrap; 962 | overflow: hidden; 963 | text-overflow: ellipsis; 964 | -o-text-overflow: ellipsis; 965 | -ms-text-overflow: ellipsis; 966 | } 967 | 968 | section { 969 | margin-top: 5px; 970 | } 971 | 972 | #banner { 973 | display: none; 974 | } 975 | 976 | header ul { 977 | display: none; 978 | } 979 | } 980 | @media print { 981 | body { 982 | padding: 0.4in; 983 | font-size: 12pt; 984 | color: #444; 985 | } 986 | } 987 | @media print, screen and (max-height: 680px) { 988 | footer { 989 | text-align: center; 990 | margin: 20px auto; 991 | position: relative; 992 | left: auto; 993 | bottom: auto; 994 | width: auto; 995 | } 996 | } 997 | @media print, screen and (max-height: 480px) { 998 | nav { 999 | display: none; 1000 | } 1001 | 1002 | footer { 1003 | text-align: center; 1004 | margin: 20px auto; 1005 | position: relative; 1006 | left: auto; 1007 | bottom: auto; 1008 | width: auto; 1009 | } 1010 | } 1011 | -------------------------------------------------------------------------------- /marlin/static/toast.css: -------------------------------------------------------------------------------- 1 | /*-----------------------------------*\ 2 | 3 | Toast 4 | A Simple CSS Framework 5 | ================================= 6 | 7 | Values you may want to change: 8 | - .container { max-width:; } 9 | - p { margin-bottom:; } 10 | - html { font:; } 11 | 12 | Remember: no framework will be as 13 | good as a custom built, per- 14 | project one. Toast and other 15 | frameworks are best used for 16 | rapid prototyping. 17 | 18 | \*-----------------------------------*/ 19 | 20 | /*-----------------------------------*\ 21 | $RESET 22 | \*-----------------------------------*/ 23 | 24 | * { 25 | margin: 0; 26 | padding: 0; 27 | position: relative; 28 | -webkit-box-sizing: border-box; 29 | -moz-box-sizing: border-box; 30 | box-sizing: border-box; 31 | } 32 | 33 | /*-----------------------------------*\ 34 | $GRID 35 | \*-----------------------------------*/ 36 | 37 | .container { 38 | /* Whatever you want. They’re your oats. */ 39 | max-width: 960px; 40 | margin: 0 auto; 41 | padding: 0 30px; 42 | padding: 0 1.5rem; 43 | } 44 | 45 | .grid { 46 | margin-left: -3%; 47 | max-width: 105%; 48 | } 49 | 50 | .unit { 51 | display: inline-block; 52 | *display: inline; 53 | *zoom: 1; 54 | vertical-align: top; 55 | margin-left: 3%; 56 | margin-right: -.25em; 57 | /* Clearfix */ 58 | overflow: hidden; 59 | *overflow: visible; 60 | } 61 | 62 | .unit.demo { 63 | background-color: #fff8eb; 64 | height: 48px; 65 | height: 3rem; 66 | margin-bottom: 24px; 67 | margin-bottom: 1.5rem; 68 | } 69 | 70 | .span-grid { 71 | width: 97%; 72 | } 73 | 74 | .one-of-two { width: 47%; } 75 | 76 | .one-of-three { width: 30.36%; } 77 | .two-of-three { width: 63.666666666%; } 78 | 79 | .one-of-four { width: 22.05%; } 80 | .three-of-four { width: 72%; } 81 | 82 | .one-of-five { width: 17.07%; } 83 | .two-of-five { width: 37%; } 84 | .three-of-five { width: 57%; } 85 | .four-of-five { width: 77%; } 86 | 87 | @media screen and (max-width: 650px) { 88 | .grid { 89 | margin-left: 0; 90 | max-width: none; 91 | } 92 | 93 | .unit { 94 | width: auto; 95 | margin-left: 0; 96 | display: block; 97 | } 98 | } 99 | 100 | /*-----------------------------------*\ 101 | $TYPE 102 | 103 | Works off the assumption of a 1.5 104 | line height @ 20px. Again, change 105 | as necessary. 106 | \*-----------------------------------*/ 107 | 108 | p, .p, ul, ol, hr, table, form, pre, 109 | h1, .alpha, h2, .beta { 110 | margin-bottom: 30px; 111 | margin-bottom: 1.5rem; 112 | } 113 | 114 | h1, .alpha { 115 | font-size: 60px; 116 | font-size: 3rem; 117 | font-weight: 700; 118 | line-height: 1; 119 | } 120 | 121 | h2, .beta { 122 | font-size: 30px; 123 | font-size: 1.5rem; 124 | font-weight: 400; 125 | line-height: 2; 126 | } 127 | 128 | h3, .gamma { 129 | font-size: 20px; 130 | font-size: 1rem; 131 | font-weight: 700; 132 | } 133 | 134 | hr { 135 | border: none; 136 | border-bottom: 1px solid rgba(0,0,0,.1); 137 | margin-top: -1px; 138 | } 139 | 140 | /*-----------------------------------*\ 141 | $MAIN 142 | \*-----------------------------------*/ 143 | 144 | @font-face { 145 | font-family: 'Open Sans'; 146 | font-style: normal; 147 | font-weight: 300; 148 | src: local('Open Sans Light'), local('OpenSans-Light'), url(http://themes.googleusercontent.com/static/fonts/opensans/v8/DXI1ORHCpsQm3Vp6mXoaTXhCUOGz7vYGh680lGh-uXM.woff) format('woff'); 149 | } 150 | @font-face { 151 | font-family: 'Open Sans'; 152 | font-style: normal; 153 | font-weight: 700; 154 | src: local('Open Sans Bold'), local('OpenSans-Bold'), url(http://themes.googleusercontent.com/static/fonts/opensans/v8/k3k702ZOKiLJc3WVjuplzHhCUOGz7vYGh680lGh-uXM.woff) format('woff'); 155 | } 156 | 157 | 158 | html { 159 | font: 125%/1.5 'Open Sans', Helvetica Neue, Helvetica, Arial, sans-serif; 160 | font-weight: 100; 161 | } 162 | 163 | @media screen and (max-width: 650px) { 164 | html { 165 | font-size: 100%; 166 | } 167 | } 168 | 169 | .gridtable { 170 | font-size:14px; 171 | color:#333333; 172 | border-width: 1px; 173 | border-color: #666666; 174 | border-collapse: collapse; 175 | } 176 | .gridtable thead tr td { 177 | font-weight: 700; 178 | border-width: 1px; 179 | padding: 8px; 180 | border-style: solid; 181 | border: 2px solid #6678b1; 182 | background-color: #dedede; 183 | } 184 | .gridtable td { 185 | border-width: 1px; 186 | padding: 8px; 187 | border-style: solid; 188 | border: 2px solid #6678b1; 189 | background-color: #ffffff; 190 | } -------------------------------------------------------------------------------- /marlin/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Marlin by atmb4u 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 |
    20 |

    Marlin

    21 |

    Fast and easy ReST API server on redis

    22 |
    23 | 24 | 36 | 37 |
    38 | 41 |
    42 |

    Marlin

    43 | 44 |

    45 | Marlin

    46 | 47 |

    48 | a fast, frill-free REST API with ZERO setup time is too interesting.

    49 | 50 |

    51 | Quick Start Guide

    52 | 53 |
     54 | pip install marlin  # install marlin to the python environment.
     55 | 
     56 | marlin-server start  # start marlin server - port: 5000
     57 | 
     58 | 
    59 | 60 |

    61 | Detailed Installation in Ubuntu

    62 | 63 |
      64 |
    • redis-server
    • 65 |
    sudo apt-get install redis-server
     66 | 
    67 | 68 |
      69 |
    • create virtualenv
    • 70 |
    sudo apt-get install python-pip
     71 | sudo pip install virtualenv
     72 | virtualenv marlin-env
     73 | source marlin-env/bin/activate
     74 | 
    75 | 76 |
      77 |
    • requests, ujson, flask, python-daemon
    • 78 |
    pip install flask requests ujson python-daemon
     79 | 
    80 | 81 |
      82 |
    • install marlin
    • 83 |
    pip install marlin  # install marlin to the python environment.
     84 | 
     85 | 
    86 | 87 |

    88 | Managing Server

    89 | 90 |
    marlin-server start  # starts server with default conf on port 5000
     91 | 
     92 | marlin-server stop  # stops the server
     93 | 
     94 | marlin-server restart  # restart the server
     95 | 
     96 | marlin-server live  # starts a server on DEBUG mode
     97 | 
    98 | 99 |

    100 | Request Methods

    101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 |
    METHODURLRESPONSEDESCRIPTION
    GET/api/v1//?start=1&end=10[data] 1-10returns the 1-10 elements in the
    GET/api/v1//1data itemreturns the element with id 1
    GET/ping/200/500check if service is up and connected
    POST/api/v1//[data]adds data to the model
    PUT/api/v1//1/[data]edit data
    DELETE/api/v1//1200delete the data item
    DELETE/api/v1//-delete complete data in model
    DELETE/api/v1//&force=1-delete and reset model (starts with id=1

    160 | Server Configuration

    161 | 162 |

    marlin.config

    163 | 164 |

    For custom configuration, just create a marlin.config on the directory from where you are starting marlin-server.

    165 | 166 |
    
    167 | [SERVER]
    168 | DEBUG = True
    169 | PID_FILE = /tmp/marlin.pid
    170 | LOG_FILE = /tmp/marlin.log
    171 | SERVER_PORT = 5000
    172 | 
    173 | [REDIS]
    174 | REDIS_SERVER = localhost
    175 | REDIS_PORT = 6379
    176 | API_PREFIX = /api/
    177 | 
    178 | [APP]
    179 | APP_NAME = marlin
    180 | 
    181 | 182 |

    183 | Custom Urls and Functions

    184 | 185 |

    Always, a basic REST API is just a scaffolding for the application, and custom defined urls and functions make it beautiful. As marlin is more focused on performance, it is designed for flexibility as well.

    186 | 187 |

    It is pretty simple to create custom functions in Marlin.

    188 | 189 |

    Just place marlin_function.py in the present working directory (pwd), with custom routes and custom responses.

    190 | 191 |
    # marlin_functions.py
    192 | from marlin import app
    193 | 
    194 | 
    195 | @app.route("/example/"):
    196 |     return Response("Simple Custom Response")
    197 | 
    198 | 199 |

    or a more complex example.

    200 | 201 |

    202 | To get a custom element based on a user id

    203 | 204 |
    import json
    205 | from marlin import app, RedisDatabaseManager
    206 | from flask import Response, request
    207 | 
    208 | 
    209 | @app.route("/simple_get/<model>")
    210 | def custom_get(model):
    211 |     rdm = RedisDatabaseManager(request, model, version='v1')
    212 |     user_id = 127
    213 |     if rdm:
    214 |         rdm.manipulate_data()
    215 |         rdm.get_from_redis(user_id)  # get data for the specific user id
    216 |     else:
    217 |         return json.dumps({"status": "Something is not right"})
    218 |     if rdm.status and rdm.data:
    219 |         return Response(rdm.string_data, content_type='application/json; charset=utf-8')
    220 |     elif rdm.status:
    221 |         return Response(json.dumps({'status': "No data Found"}), content_type='application/json; charset=utf-8',
    222 |                         status=404)
    223 |     else:
    224 |         return json.dumps({"status": "Something is not right"})
    225 | 
    226 | 227 |

    A little more complicated Example

    228 | 229 |

    230 | Following example filter all the objects with name=Apple 231 |

    232 | 233 |
    @app.route('/<model>/', methods=['GET'])
    234 | def little_complicated(model):
    235 |     custom_range_start = 10
    236 |     custom_range_end = 70
    237 |     error_response = Response(json.dumps(
    238 |         {'status': "Some unknown error"}),
    239 |         content_type='application/json; charset=utf-8', status=500)
    240 |     rdm = RedisDatabaseManager(request, model=model)
    241 |     if rdm:
    242 |         rdm.manipulate_data()
    243 |         rdm.get_many_from_redis(custom_range_start, custom_range_end)
    244 |     else:
    245 |         return error_response
    246 |     if rdm.status:
    247 |         if rdm.data:
    248 |             custom_query_set = []
    249 |             for datum in rdm.data:
    250 |                 if datum.get("name") == "Apple":
    251 |                     custom_query_set.append(datum)
    252 |             return Response(json.dumps(custom_query_set), content_type='application/json; charset=utf-8')
    253 |         else:
    254 |             return Response(json.dumps({'status': "No data Found"}), content_type='application/json; charset=utf-8',
    255 |                             status=404)
    256 |     else:
    257 |         return error_response
    258 | 
    259 |
    260 |
    261 |

    Project maintained by atmb4u

    262 |

    Hosted on GitHub Pages — Theme by mattgraham

    263 |
    264 |
    265 | 266 | 270 | 276 | 277 | 278 | -------------------------------------------------------------------------------- /marlin/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import marlin 3 | import ujson as json 4 | 5 | 6 | class MarlinTest(unittest.TestCase): 7 | 8 | def setUp(self): 9 | app = marlin.app 10 | app.config['TESTING'] = True 11 | self.client = app.test_client() 12 | 13 | def test_001_ping(self): 14 | resp = self.client.get("/ping/") 15 | self.assertTrue(resp.status, 200) 16 | 17 | def test_002_post_item(self): 18 | resp = self.client.post(path="/api/v1/fruits/", data={"name": "Apple", "calories": 52, "price": 120.00}) 19 | self.assertTrue(json.loads(resp.data), dict(price=120.00, calories=52, name="Apple", id=1)) 20 | 21 | def test_003_get_item(self): 22 | resp = self.client.get("/api/v1/fruits/1/") 23 | self.assertTrue(json.loads(resp.data), dict(price=120.00, calories=52, name="Apple", id=1)) 24 | 25 | def test_004_get_list(self): 26 | resp = self.client.get("/api/v1/fruits/") 27 | self.assertTrue(json.loads(resp.data), list(dict(price=120.00, calories=52, name="Apple", id=1))) 28 | 29 | def test_005_delete_item(self): 30 | resp = self.client.delete("/api/v1/fruits/1/") 31 | self.assertTrue(resp.status, 200) 32 | 33 | def test_006_delete_all(self): 34 | resp = self.client.delete("/api/v1/fruits/") 35 | self.assertTrue(resp.status, 200) 36 | resp = self.client.post(path="/api/v1/fruits/", data={"name": "Apricot", "calories": 48, "price": 80.00}) 37 | self.assertGreater(json.loads(resp.data).get("id"), 1) 38 | 39 | def test_007_flush_db(self): 40 | resp = self.client.delete("/api/v1/fruits/", data={'force': 1}) 41 | self.assertTrue(resp.status, 200) 42 | resp = self.client.get("/api/v1/fruits/") 43 | self.assertTrue(json.loads(resp.data), {"status": "No data Found"}) 44 | 45 | if __name__ == '__main__': 46 | unittest.main() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | redis 2 | flask 3 | ujson 4 | requests 5 | python-daemon 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Marlin setup.py script 2 | # 3 | # It doesn't depend on setuptools, but if setuptools is available it'll use 4 | # some of its features, like package dependencies. 5 | 6 | from distutils.command.install_data import install_data 7 | from distutils.command.install import INSTALL_SCHEMES 8 | from glob import glob 9 | import os 10 | import sys 11 | import marlin 12 | 13 | 14 | class osx_install_data(install_data): 15 | # On MacOS, the platform-specific lib dir is /System/Library/Framework/Python/.../ 16 | # which is wrong. Python 2.5 supplied with MacOS 10.5 has an Apple-specific fix 17 | # for this in distutils.command.install_data#306. It fixes install_lib but not 18 | # install_data, which is why we roll our own install_data class. 19 | 20 | def finalize_options(self): 21 | # By the time finalize_options is called, install.install_lib is set to the 22 | # fixed directory, so we set the installdir to install_lib. The 23 | # install_data class uses ('install_data', 'install_dir') instead. 24 | self.set_undefined_options('install', ('install_lib', 'install_dir')) 25 | install_data.finalize_options(self) 26 | 27 | 28 | if sys.platform == "darwin": 29 | cmdclasses = {'install_data': osx_install_data} 30 | else: 31 | cmdclasses = {'install_data': install_data} 32 | 33 | 34 | def fullsplit(path, result=None): 35 | """ 36 | Split a pathname into components (the opposite of os.path.join) in a 37 | platform-neutral way. 38 | """ 39 | if result is None: 40 | result = [] 41 | head, tail = os.path.split(path) 42 | if head == '': 43 | return [tail] + result 44 | if head == path: 45 | return result 46 | return fullsplit(head, [tail] + result) 47 | 48 | # Tell distutils to put the data_files in platform-specific installation 49 | # locations. See here for an explanation: 50 | # http://groups.google.com/group/comp.lang.python/browse_thread/thread/35ec7b2fed36eaec/2105ee4d9e8042cb 51 | for scheme in INSTALL_SCHEMES.values(): 52 | scheme['data'] = scheme['purelib'] 53 | 54 | # Compile the list of packages available, because distutils doesn't have 55 | # an easy way to do this. 56 | packages, data_files = [], [] 57 | root_dir = os.path.dirname(__file__) 58 | if root_dir != '': 59 | os.chdir(root_dir) 60 | 61 | 62 | def is_not_module(filename): 63 | return os.path.splitext(filename)[1] not in ['.py', '.pyc', '.pyo'] 64 | 65 | 66 | for marlin_dir in ['marlin']: 67 | for dirpath, dirnames, filenames in os.walk(marlin_dir): 68 | # Ignore dirnames that start with '.' 69 | for i, dirname in enumerate(dirnames): 70 | if dirname.startswith('.'): del dirnames[i] 71 | if '__init__.py' in filenames: 72 | packages.append('.'.join(fullsplit(dirpath))) 73 | data = [f for f in filenames if is_not_module(f)] 74 | if data: 75 | data_files.append([dirpath, [os.path.join(dirpath, f) for f in data]]) 76 | elif filenames: 77 | data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]]) 78 | 79 | 80 | # Small hack for working with bdist_wininst. 81 | # See http://mail.python.org/pipermail/distutils-sig/2004-August/004134.html 82 | if len(sys.argv) > 1 and sys.argv[1] == 'bdist_wininst': 83 | for file_info in data_files: 84 | file_info[0] = '\\PURELIB\\%s' % file_info[0] 85 | 86 | scripts = ['marlin/marlin-server'] 87 | 88 | version = marlin.marlin.VERSION 89 | 90 | setup_args = { 91 | 'name': 'marlin', 92 | 'version': version, 93 | 'url': 'http://atmb4u.github.io/marlin', 94 | 'description': 'A fast and easy ReST API on top of redis', 95 | 'author': 'Anoop Thomas Mathew', 96 | 'author_email': 'atmb4u@gmail.com', 97 | 'license': 'BSD', 98 | 'packages': packages, 99 | 'cmdclass': cmdclasses, 100 | 'data_files': data_files, 101 | 'scripts': scripts, 102 | 'include_package_data': True, 103 | 'classifiers': [ 104 | 'Programming Language :: Python', 105 | 'Programming Language :: Python :: 2.7', 106 | 'License :: OSI Approved :: BSD License', 107 | 'Operating System :: OS Independent', 108 | 'Intended Audience :: Developers', 109 | 'Environment :: Console', 110 | 'Topic :: Software Development :: Libraries :: Python Modules', 111 | 'Topic :: Internet :: WWW/HTTP', 112 | ] 113 | } 114 | 115 | try: 116 | from setuptools import setup 117 | except ImportError: 118 | from distutils.core import setup 119 | else: 120 | setup_args['install_requires'] = [ 121 | "redis>=2.9.1", 122 | "requests>=2.2.1", 123 | "ujson>=1.33", 124 | 'python-daemon==1.6', 125 | 'flask' 126 | ] 127 | 128 | setup(**setup_args) 129 | --------------------------------------------------------------------------------