├── .gitignore ├── LICENSE ├── README.rst ├── cookiecutter.json └── {{cookiecutter.app_name}} ├── .gitignore ├── LICENSE ├── README.rst ├── ansible ├── install.yml ├── inventory └── update.yml ├── dev_server.sh ├── etc ├── default │ └── {{cookiecutter.app_name}} ├── init.d │ └── {{cookiecutter.app_name}} ├── logrotate.d │ └── {{cookiecutter.app_name}} ├── monit │ └── conf.d │ │ └── {{cookiecutter.app_name}} ├── rsyslog.conf ├── systemd │ └── system │ │ ├── {{cookiecutter.app_name}}.service │ │ └── {{cookiecutter.app_name}}.socket └── {{cookiecutter.app_name}} │ ├── api_hour │ ├── gunicorn_conf.py │ └── logging.ini │ └── main │ └── main.yaml ├── requirements.txt └── {{cookiecutter.app_name}} ├── __init__.py ├── endpoints ├── __init__.py └── {{cookiecutter.endpoint_name}}.py ├── services ├── __init__.py └── {{cookiecutter.service_name}}.py └── utils └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .gitignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | lib/ 19 | libs/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Logging 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # PyCharm/IDEA files 60 | .idea 61 | *.iml 62 | 63 | # Virtualenvs 64 | .env 65 | venv*/ 66 | pyvenv*/ 67 | 68 | # Misc 69 | .*.kate-swp 70 | /local/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright [2015] [Eyepea Dev Team] 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | API-Hour Starter Kit (cookiecutter) 2 | =================================== 3 | 4 | * `Cookiecutter `_ 5 | 6 | How to use this template 7 | ------------------------ 8 | 9 | - pip install cookiecutter 10 | - cookiecutter https://github.com/Eyepea/cookiecutter-API-Hour.git 11 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "app_name": "appname", 3 | "email": "you@example.com", 4 | "hostname": "your.server.example.com", 5 | "endpoint_name": "index", 6 | "service_name": "data", 7 | "server_name": "web.example.com", 8 | "git_repository": "https://github.com/your_username/example" 9 | } -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .gitignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | /lib/ 19 | /lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ 57 | 58 | # PyCharm/IDEA files 59 | .idea 60 | *.iml 61 | 62 | # Virtualenvs 63 | venv*/ 64 | pyvenv*/ -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright [2015] [Eyepea Dev Team] 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/README.rst: -------------------------------------------------------------------------------- 1 | {{cookiecutter.app_name}} 2 | ===== 3 | 4 | Start manually 5 | -------------- 6 | 7 | In this current folder, launch: `api_hour -ac {{cookiecutter.app_name}}:Container` 8 | 9 | Deploy using Ansible 10 | -------------------- 11 | 12 | #. `ansible-playbook ansible/install.yml -i ansible/inventory` 13 | #. Customize config files in /etc/{{cookiecutter.app_name}}/ 14 | #. Merge rsyslog config file 15 | #. service {{cookiecutter.app_name}} start 16 | 17 | Deploy new version using Ansible 18 | -------------------------------- 19 | 20 | #. `ansible-playbook ansible/update.yml` 21 | 22 | Manual install 23 | -------------- 24 | 25 | #. Follow pythonz install doc: https://github.com/saghul/pythonz 26 | #. pythonz install 3.5.1 27 | #. cd /opt 28 | #. Git clone your app here 29 | #. cd /opt/{{cookiecutter.app_name}}/ 30 | #. /usr/local/pythonz/pythons/CPython-3.5.1/bin/pyvenv pyvenv 31 | #. . pyvenv/bin/activate 32 | #. pip install -r requirements.txt 33 | #. cd /etc/init.d/ && ln -s /opt/{{cookiecutter.app_name}}/etc/init.d/{{cookiecutter.app_name}} 34 | #. To define right boot order for your daemon (for example, your daemon needs PostgreSQL), customize file header of: /opt/{{cookiecutter.app_name}}/etc/default/{{cookiecutter.app_name}} 35 | #. cd /etc/default/ && ln -s /opt/{{cookiecutter.app_name}}/etc/default/{{cookiecutter.app_name}} 36 | #. update-rc.d {{cookiecutter.app_name}} defaults 37 | #. cp -a /opt/{{cookiecutter.app_name}}/etc/{{cookiecutter.app_name}} /etc/ 38 | #. Adapt rsyslog and logrotate 39 | #. For logrotate config file, apply the access rights: rw-r--r-- 40 | #. service {{cookiecutter.app_name}} start 41 | 42 | To restart automatically daemon if it crashes 43 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 44 | #. apt-get install monit 45 | #. cd /etc/monit/conf.d/ && ln -s /opt/{{cookiecutter.app_name}}/etc/monit/conf.d/{{cookiecutter.app_name}} 46 | #. service monit restart 47 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/ansible/install.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install {{cookiecutter.app_name}} on a server 3 | hosts: 4 | - {{cookiecutter.server_name}} 5 | gather_facts: True 6 | tasks: 7 | - name: Install packages on Debian 8 | apt: name={{ '{{' }}item{{ '}}' }} state=present 9 | when: ansible_os_family == 'Debian' 10 | with_items: 11 | - git 12 | 13 | - name: Install packages on CentOS 14 | yum: name={{ '{{' }}item{{ '}}' }} state=present 15 | when: ansible_os_family == 'RedHat' 16 | with_items: 17 | - git 18 | 19 | - name: Clone project 20 | git: repo={{cookiecutter.git_repository}} dest=/opt/{{cookiecutter.app_name}} 21 | 22 | - name: Install requirements 23 | pip: requirements=/opt/{{cookiecutter.app_name}}/requirements.txt 24 | virtualenv=/opt/{{cookiecutter.app_name}}/pyvenv 25 | virtualenv_command=/usr/local/pythonz/pythons/CPython-3.5.1/bin/pyvenv 26 | 27 | - name: Create log dir 28 | file: dest=/var/log/{{cookiecutter.app_name}} owner=root group=root state=directory 29 | 30 | - name: Link default config file 31 | file: state=link dest=/etc/default/{{cookiecutter.app_name}} src=/opt/{{cookiecutter.app_name}}/etc/default/{{cookiecutter.app_name}} 32 | 33 | - name: Link startup script 34 | file: state=link dest=/etc/init.d/{{cookiecutter.app_name}} src=/opt/{{cookiecutter.app_name}}/etc/init.d/{{cookiecutter.app_name}} 35 | 36 | - name: Link logrotate config file 37 | file: state=link dest=/etc/logrotate.d/{{cookiecutter.app_name}} src=/opt/{{cookiecutter.app_name}}/etc/logrotate.d/{{cookiecutter.app_name}} 38 | 39 | - name: Make it start with the system 40 | service: name={{cookiecutter.app_name}} enabled=yes 41 | 42 | - name: Copy the template configuration file to etc 43 | command: cp -a /opt/{{cookiecutter.app_name}}/etc/{{cookiecutter.app_name}} /etc/ 44 | args: 45 | creates: "/etc/{{cookiecutter.app_name}}" 46 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/ansible/inventory: -------------------------------------------------------------------------------- 1 | {{cookiecutter.server_name}} ansible_ssh_user=root -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/ansible/update.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install {{cookiecutter.app_name}} on a server 3 | hosts: 4 | - {{cookiecutter.server_name}} 5 | gather_facts: False 6 | tasks: 7 | - name: Update main repo 8 | command: "git push" 9 | delegate_to: localhost 10 | 11 | - name: Pull the fresh code 12 | git: repo={{cookiecutter.git_repository}} dest=/opt/{{cookiecutter.app_name}} 13 | 14 | - name: Install requirements 15 | pip: requirements=/opt/{{cookiecutter.app_name}}/requirements.txt 16 | virtualenv=/opt/{{cookiecutter.app_name}}/pyvenv 17 | virtualenv_command=/usr/local/pythonz/pythons/CPython-3.5.1/bin/pyvenv 18 | 19 | - name: Restart the service 20 | service: name={{cookiecutter.app_name}} state=restarted 21 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/dev_server.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This will start api-hour with a single worker and a super-long timeout to make things easier to debug 3 | clear && api_hour -w 1 -t 999999 -ac {{cookiecutter.app_name}}:Container 4 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/etc/default/{{cookiecutter.app_name}}: -------------------------------------------------------------------------------- 1 | # Start the daemon by default, let the user disable it. 2 | START_DAEMON=yes 3 | DAEMON_ARGS="--config_dir=/etc/{{cookiecutter.app_name}} -ac --chdir=/opt/{{cookiecutter.app_name}} {{cookiecutter.app_name}}:Container" 4 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/etc/init.d/{{cookiecutter.app_name}}: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | ### BEGIN INIT INFO 4 | # Provides: {{cookiecutter.app_name}} 5 | # Required-Start: $local_fs $remote_fs $network $syslog 6 | # Required-Stop: $local_fs $remote_fs $network $syslog 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: Startup daemon script for {{cookiecutter.app_name}} 10 | ### END INIT INFO 11 | # 12 | # Author: Ludovic Gasc 13 | set -e 14 | 15 | EXECUTABLE_PATH=/opt/{{cookiecutter.app_name}}/pyvenv/bin 16 | PATH=$EXECUTABLE_PATH:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin 17 | DAEMONNAME={{cookiecutter.app_name}} 18 | RUNDIR=/run/$DAEMONNAME 19 | DAEMON=$EXECUTABLE_PATH/api_hour 20 | PIDFILE=/run/lock/${DAEMONNAME}.pid 21 | DAEMON_ARGS="" 22 | 23 | # Exit if the package is not installed 24 | [ -x "$DAEMON" ] || exit 0 25 | 26 | # Create RUNDIR if it doesn't exist 27 | [ -d "$RUNDIR" ] || mkdir -p "$RUNDIR" 28 | 29 | # Read configuration variable file if it is present 30 | [ -r /etc/default/$DAEMONNAME ] && . /etc/default/$DAEMONNAME 31 | 32 | # Load the VERBOSE setting and other rcS variables 33 | [ -f /etc/default/rcS ] && . /etc/default/rcS 34 | 35 | # Define LSB log_* functions. 36 | # Depend on lsb-base (>= 3.0-6) to ensure that this file is present. 37 | . /lib/lsb/init-functions 38 | 39 | case "$1" in 40 | start) 41 | log_daemon_msg "Starting" "$DAEMONNAME" 42 | if start-stop-daemon -b --start --pidfile $PIDFILE --startas $DAEMON -- $DAEMON_ARGS; 43 | then 44 | log_end_msg 0 45 | else 46 | log_end_msg 1 47 | fi 48 | ;; 49 | stop) 50 | log_daemon_msg "Stopping" "$DAEMONNAME" 51 | if start-stop-daemon --stop --retry 5 --pidfile $PIDFILE; 52 | then 53 | log_end_msg 0 54 | else 55 | log_end_msg 1 56 | fi 57 | ;; 58 | reload|force-reload) 59 | log_daemon_msg "Reloading" "$DAEMONNAME" 60 | if start-stop-daemon --stop --signal 1 --pidfile $PIDFILE --startas $DAEMON; 61 | then 62 | log_end_msg 0 63 | else 64 | log_end_msg 1 65 | fi 66 | ;; 67 | restart) 68 | $0 stop 69 | $0 start 70 | ;; 71 | status) 72 | status_of_proc -p $PIDFILE "$DAEMON" $DAEMONNAME && exit 0 || exit $? 73 | ;; 74 | *) 75 | echo "Usage: $0 {start|stop|reload|force-reload|restart|status}" 76 | exit 1 77 | ;; 78 | esac 79 | 80 | exit 0 81 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/etc/logrotate.d/{{cookiecutter.app_name}}: -------------------------------------------------------------------------------- 1 | /var/log/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}.log { 2 | daily 3 | rotate 15 4 | compress 5 | copytruncate 6 | missingok 7 | } -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/etc/monit/conf.d/{{cookiecutter.app_name}}: -------------------------------------------------------------------------------- 1 | check process cache_updater with pidfile /run/lock/{{cookiecutter.app_name}}.pid 2 | group {{cookiecutter.app_name}} 3 | start program = "/etc/init.d/{{cookiecutter.app_name}} start" 4 | stop program = "/etc/init.d/{{cookiecutter.app_name}} stop" 5 | if 5 restarts within 5 cycles then timeout -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/etc/rsyslog.conf: -------------------------------------------------------------------------------- 1 | # /etc/rsyslog.conf Configuration file for rsyslog. 2 | # 3 | # For more information see 4 | # /usr/share/doc/rsyslog-doc/html/rsyslog_conf.html 5 | 6 | 7 | ################# 8 | #### MODULES #### 9 | ################# 10 | 11 | $ModLoad imuxsock # provides support for local system logging 12 | $ModLoad imklog # provides kernel logging support 13 | #$ModLoad immark # provides --MARK-- message capability 14 | $SystemLogRateLimitInterval 1 15 | $SystemLogRateLimitBurst 1000 16 | 17 | # provides UDP syslog reception 18 | #$ModLoad imudp 19 | #$UDPServerRun 514 20 | 21 | # provides TCP syslog reception 22 | #$ModLoad imtcp 23 | #$InputTCPServerRun 514 24 | 25 | 26 | ########################### 27 | #### GLOBAL DIRECTIVES #### 28 | ########################### 29 | 30 | # 31 | # Use traditional timestamp format. 32 | # To enable high precision timestamps, comment out the following line. 33 | # 34 | $ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat 35 | 36 | # 37 | # Set the default permissions for all log files. 38 | # 39 | $FileOwner root 40 | $FileGroup adm 41 | $FileCreateMode 0640 42 | $DirCreateMode 0755 43 | $Umask 0022 44 | 45 | # 46 | # Where to place spool and state files 47 | # 48 | $WorkDirectory /var/spool/rsyslog 49 | 50 | # 51 | # Include all config files in /etc/rsyslog.d/ 52 | # 53 | $IncludeConfig /etc/rsyslog.d/*.conf 54 | 55 | 56 | ############### 57 | #### RULES #### 58 | ############### 59 | 60 | # 61 | # First some standard log files. Log by facility. 62 | # 63 | auth,authpriv.* /var/log/auth.log 64 | *.*;auth,authpriv.none;\ 65 | local6.none -/var/log/syslog 66 | #cron.* /var/log/cron.log 67 | daemon.* -/var/log/daemon.log 68 | kern.* -/var/log/kern.log 69 | lpr.* -/var/log/lpr.log 70 | mail.* -/var/log/mail.log 71 | user.* -/var/log/user.log 72 | 73 | local6.* /var/log/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}.log 74 | 75 | # 76 | # Logging for the mail system. Split it up so that 77 | # it is easy to write scripts to parse these files. 78 | # 79 | mail.info -/var/log/mail.info 80 | mail.warn -/var/log/mail.warn 81 | mail.err /var/log/mail.err 82 | 83 | # 84 | # Logging for INN news system. 85 | # 86 | news.crit /var/log/news/news.crit 87 | news.err /var/log/news/news.err 88 | news.notice -/var/log/news/news.notice 89 | 90 | # 91 | # Some "catch-all" log files. 92 | # 93 | *.=debug;\ 94 | auth,authpriv.none;\ 95 | news.none;mail.none;local6.none;local7.none -/var/log/debug 96 | *.=info;*.=notice;*.=warn;\ 97 | auth,authpriv.none;\ 98 | cron,daemon.none;\ 99 | mail,news.none;local6.none;local7.none -/var/log/messages 100 | 101 | # 102 | # Emergencies are sent to everybody logged in. 103 | # 104 | *.emerg :omusrmsg:* 105 | 106 | # 107 | # I like to have messages displayed on the console, but only on a virtual 108 | # console I usually leave idle. 109 | # 110 | #daemon,mail.*;\ 111 | # news.=crit;news.=err;news.=notice;\ 112 | # *.=debug;*.=info;\ 113 | # *.=notice;*.=warn /dev/tty8 114 | 115 | # The named pipe /dev/xconsole is for the `xconsole' utility. To use it, 116 | # you must invoke `xconsole' with the `-file' option: 117 | # 118 | # $ xconsole -file /dev/xconsole [...] 119 | # 120 | # NOTE: adjust the list below, or you'll go crazy if you have a reasonably 121 | # busy site.. 122 | # 123 | daemon.*;mail.*;\ 124 | news.err;\ 125 | *.=debug;*.=info;\ 126 | *.=notice;*.=warn |/dev/xconsole 127 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/etc/systemd/system/{{cookiecutter.app_name}}.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description={{cookiecutter.app_name}} daemon 3 | Requires={{cookiecutter.app_name}}.socket 4 | After=network.target 5 | AssertPathExists=/opt/{{cookiecutter.app_name}}/pyvenv/bin/api_hour 6 | AssertPathExists=/etc/{{cookiecutter.app_name}} 7 | AssertPathExists=/opt/{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/__init__.py 8 | 9 | [Service] 10 | Type=simple 11 | PIDFile=/run/lock/{{cookiecutter.app_name}}.pid 12 | User=root 13 | Group=root 14 | Environment="PYTHONPATH=." 15 | Environment="PYTHONUNBUFFERED=true" 16 | WorkingDirectory=/opt/{{cookiecutter.app_name}} 17 | ExecStart=/opt/{{cookiecutter.app_name}}/pyvenv/bin/api_hour -ac --config_dir=/etc/{{cookiecutter.app_name}} {{cookiecutter.app_name}}:Container 18 | ExecReload=/bin/kill -s HUP $MAINPID 19 | ExecStop=/bin/kill -s TERM $MAINPID 20 | PrivateTmp=true 21 | Restart=always 22 | RestartSec=4 23 | KillSignal=SIGTERM 24 | SendSIGKILL=yes 25 | LimitNOFILE=65535 26 | 27 | [Install] 28 | WantedBy=multi-user.target 29 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/etc/systemd/system/{{cookiecutter.app_name}}.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description={{cookiecutter.app_name}} socket 3 | 4 | [Socket] 5 | ListenStream=0.0.0.0:8001 6 | 7 | [Install] 8 | WantedBy=sockets.target -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/etc/{{cookiecutter.app_name}}/api_hour/gunicorn_conf.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import os 3 | 4 | workers = multiprocessing.cpu_count() * 2 5 | workers = 1 # dev mode 6 | 7 | if os.environ.get('TRAVIS') == 'true': 8 | workers = 2 9 | 10 | bind = ('0.0.0.0:8000', ) 11 | keepalive = 15 12 | pidfile = '/run/lock/{{cookiecutter.app_name}}.pid' 13 | backlog = 10240000 14 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/etc/{{cookiecutter.app_name}}/api_hour/logging.ini: -------------------------------------------------------------------------------- 1 | [formatters] 2 | keys=detailed,simple 3 | 4 | [handlers] 5 | keys=console,syslog,smtp 6 | 7 | [loggers] 8 | keys=root,pywarnings,asyncio,gunicorn,aiohttp,api_hour,{{cookiecutter.app_name}} 9 | 10 | [formatter_simple] 11 | format=%(name)s:%(levelname)s %(asctime)s %(module)s.py => %(message)s 12 | 13 | [formatter_detailed] 14 | format=%(name)s:%(levelname)s %(asctime)s %(module)s.py:%(lineno)d => %(message)s 15 | 16 | [handler_console] 17 | class=StreamHandler 18 | args=(sys.stdout,) 19 | formatter=detailed 20 | 21 | [handler_syslog] 22 | class=handlers.SysLogHandler 23 | args=('/dev/log', handlers.SysLogHandler.LOG_LOCAL6) 24 | formatter=detailed 25 | 26 | [handler_smtp] 27 | class=handlers.SMTPHandler 28 | level=WARN 29 | args=('127.0.0.1', '{{cookiecutter.email}}', ['{{cookiecutter.email}}'], '{{cookiecutter.app_name}} error on server: {{cookiecutter.hostname}}') 30 | formatter=detailed 31 | 32 | # You can add smtp in handlers list to receive e-mail alerts 33 | [logger_root] 34 | level=WARN 35 | handlers=console,syslog 36 | 37 | # https://docs.python.org/3.5/library/warnings.html 38 | [logger_pywarnings] 39 | level=WARN 40 | handlers=console,syslog 41 | qualname=py.warnings 42 | propagate=0 43 | 44 | [logger_asyncio] 45 | level=WARN 46 | handlers=console,syslog 47 | qualname=asyncio 48 | propagate=0 49 | 50 | [logger_gunicorn] 51 | level=INFO 52 | handlers=console,syslog 53 | qualname=gunicorn 54 | propagate=0 55 | 56 | [logger_aiohttp] 57 | level=INFO 58 | handlers=console,syslog 59 | qualname=aiohttp 60 | propagate=0 61 | 62 | [logger_api_hour] 63 | level=INFO 64 | handlers=console,syslog 65 | qualname=api_hour 66 | propagate=0 67 | 68 | [logger_{{cookiecutter.app_name}}] 69 | level=DEBUG 70 | handlers=console,syslog 71 | qualname={{cookiecutter.app_name}} 72 | propagate=0 73 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/etc/{{cookiecutter.app_name}}/main/main.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | main: 3 | example_key: example_value 4 | engines: 5 | pg: 6 | host: 127.0.0.1 7 | port: 5432 8 | dbname: hello_world 9 | user: benchmarkdbuser 10 | password: benchmarkdbpass 11 | minsize: 3 12 | maxsize: 20 -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/requirements.txt: -------------------------------------------------------------------------------- 1 | api_hour 2 | aiohttp 3 | ujson 4 | aiopg -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import asyncio 3 | 4 | import aiohttp.web 5 | import aiopg 6 | import psycopg2.extras 7 | 8 | import api_hour 9 | 10 | from . import endpoints 11 | 12 | 13 | LOG = logging.getLogger(__name__) 14 | 15 | 16 | class Container(api_hour.Container): 17 | def __init__(self, *args, **kwargs): 18 | super().__init__(*args, **kwargs) 19 | 20 | if self.config is None: # Remove this line if you don't want to use API-Hour config file 21 | raise ValueError('An API-Hour config dir is needed.') 22 | 23 | ## Servers 24 | # You can define several servers, to listen HTTP and SSH for example. 25 | # If you do that, you need to listen on two ports with api_hour --bind command line. 26 | self.servers['http'] = aiohttp.web.Application(loop=kwargs['loop']) 27 | self.servers['http']['ah_container'] = self # keep a reference to Container 28 | # routes 29 | self.servers['http'].router.add_route('GET', 30 | '/{{cookiecutter.endpoint_name}}', 31 | endpoints.{{cookiecutter.endpoint_name}}.{{cookiecutter.endpoint_name}}) 32 | 33 | async def make_servers(self, sockets): 34 | # This coroutine is used by api_hour command line to have the list of handlers 35 | handlers = {} 36 | handler = self.servers['http'].make_handler(logger=self.worker.log, 37 | keep_alive=self.worker.cfg.keepalive, 38 | access_log=self.worker.log.access_log) 39 | for sock in sockets: 40 | srv = await self.loop.create_server(handler, sock=sock.sock) 41 | handlers[srv] = handler 42 | return handlers 43 | 44 | async def start(self): 45 | await super().start() 46 | LOG.info('Starting engines...') 47 | # Add your custom engines here, example with PostgreSQL: 48 | self.engines['pg'] = self.loop.create_task(aiopg.create_pool(host=self.config['engines']['pg']['host'], 49 | port=int(self.config['engines']['pg']['port']), 50 | sslmode='disable', 51 | dbname=self.config['engines']['pg']['dbname'], 52 | user=self.config['engines']['pg']['user'], 53 | password=self.config['engines']['pg']['password'], 54 | cursor_factory=psycopg2.extras.RealDictCursor, 55 | minsize=int(self.config['engines']['pg']['minsize']), 56 | maxsize=int(self.config['engines']['pg']['maxsize']), 57 | loop=self.loop)) 58 | await asyncio.wait([self.engines['pg']], return_when=asyncio.ALL_COMPLETED) 59 | 60 | LOG.info('All engines ready !') 61 | 62 | 63 | async def stop(self): 64 | LOG.info('Stopping engines...') 65 | # Add your custom end here, example with PostgreSQL: 66 | if 'pg' in self.engines: 67 | if self.engines['pg'].done(): 68 | self.engines['pg'].result().terminate() 69 | await self.engines['pg'].result().wait_closed() 70 | else: 71 | await self.engines['pg'].cancel() 72 | LOG.info('All engines stopped !') 73 | await super().stop() -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | from . import {{cookiecutter.endpoint_name}} -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/endpoints/{{cookiecutter.endpoint_name}}.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import asyncio 3 | 4 | from api_hour.plugins.aiohttp import JSON 5 | 6 | 7 | LOG = logging.getLogger(__name__) 8 | 9 | """ 10 | You handle inputs with outside world here 11 | """ 12 | 13 | async def {{cookiecutter.endpoint_name}}(request): 14 | return JSON({ 15 | 'index': 'hello!' 16 | }) 17 | 18 | # Endpoint example with a Service 19 | 20 | # from ..services.{{cookiecutter.service_name}} import get_random_record 21 | # 22 | # async def db(request): 23 | # """Test type 2: Single database query""" 24 | # container = request.app['ah_container'] 25 | # 26 | # return JSON((await get_random_record(container))) 27 | # 28 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/services/__init__.py: -------------------------------------------------------------------------------- 1 | from . import {{cookiecutter.service_name}} -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/services/{{cookiecutter.service_name}}.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from random import randint 3 | 4 | """ 5 | You can add your business logic here 6 | """ 7 | 8 | async def get_random_record(container): 9 | pg = await container.engines['pg'] 10 | 11 | with (await pg.cursor()) as cur: 12 | await cur.execute('SELECT id AS "Id", randomnumber AS "RandomNumber" FROM world WHERE id=%(idx)s LIMIT 1', 13 | {'idx': randint(1, 10000)}) 14 | world = await cur.fetchone() 15 | return world 16 | -------------------------------------------------------------------------------- /{{cookiecutter.app_name}}/{{cookiecutter.app_name}}/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import asyncio 3 | 4 | LOG = logging.getLogger(__name__) --------------------------------------------------------------------------------