Initial commit, with 'host' server example.
Even that isn't quite complete, but I'm anxious to test other things..
This commit is contained in:
commit
cf06a1987d
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
rattail_fabdemo.egg-info/
|
||||
servers/*/fabenv.py
|
||||
servers/*/.vagrant/
|
3
MANIFEST.in
Normal file
3
MANIFEST.in
Normal file
|
@ -0,0 +1,3 @@
|
|||
# -*- mode: conf -*-
|
||||
|
||||
recursive-include servers *
|
12
README.rst
Normal file
12
README.rst
Normal file
|
@ -0,0 +1,12 @@
|
|||
.. -*- coding: utf-8 -*-
|
||||
|
||||
Rattail Fabric Demo
|
||||
===================
|
||||
|
||||
This project serves as a working demo, to illustrate various concepts of the
|
||||
Rattail server deployment framework. See the `Rattail Wiki`_ for more info.
|
||||
|
||||
Note that it also aims to be usable as a starting point for your own deployment
|
||||
project(s), should you need one.
|
||||
|
||||
.. _`Rattail Wiki`: https://rattailproject.org/moin/Deployment
|
6
fabdemo/__init__.py
Normal file
6
fabdemo/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Rattail Fabric Demo
|
||||
"""
|
||||
|
||||
from ._version import __version__
|
3
fabdemo/_version.py
Normal file
3
fabdemo/_version.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = u'0.1.0'
|
10
servers/host/README.rst
Normal file
10
servers/host/README.rst
Normal file
|
@ -0,0 +1,10 @@
|
|||
.. -*- coding: utf-8 -*-
|
||||
|
||||
Bundle for 'host' Server
|
||||
========================
|
||||
|
||||
This is a fictitious "host" server, with no special requirements other than
|
||||
general bundle prep (fabenv only, no secure files needed for this one). Please
|
||||
see the `Rattail Wiki`_ for instructions.
|
||||
|
||||
.. _`Rattail Wiki`: https://rattailproject.org/moin/Deployment/ServerBundlePrep
|
7
servers/host/Vagrantfile
vendored
Normal file
7
servers/host/Vagrantfile
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
# -*- mode: ruby -*-
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "debian/jessie64"
|
||||
config.vm.network "forwarded_port", guest: 80, host: 9080
|
||||
config.vm.network "forwarded_port", guest: 443, host: 9443
|
||||
end
|
3
servers/host/deploy/apache/wsgi.conf
Normal file
3
servers/host/deploy/apache/wsgi.conf
Normal file
|
@ -0,0 +1,3 @@
|
|||
# -*- mode: apache -*-
|
||||
|
||||
WSGIPythonHome /srv/envs/BASELINE
|
49
servers/host/deploy/pod/pod.localhost.conf
Normal file
49
servers/host/deploy/pod/pod.localhost.conf
Normal file
|
@ -0,0 +1,49 @@
|
|||
# -*- mode: apache -*-
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName pod.localhost
|
||||
|
||||
DocumentRoot /srv/pod/
|
||||
<Directory /srv/pod/>
|
||||
Options +Indexes
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
# Forbid directory browsing of POD images; there are just too many.
|
||||
<DirectoryMatch "^/srv/pod/pictures/gtin/gtin-\d{3}">
|
||||
Options -Indexes
|
||||
</DirectoryMatch>
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
LogLevel warn
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName pod.localhost
|
||||
|
||||
DocumentRoot /srv/pod/
|
||||
<Directory /srv/pod>
|
||||
Options +Indexes
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
# Forbid directory browsing of POD images; there are just too many.
|
||||
<DirectoryMatch "^/srv/pod/pictures/gtin/gtin-\d{3}">
|
||||
Options -Indexes
|
||||
</DirectoryMatch>
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
LogLevel warn
|
||||
CustomLog ${APACHE_LOG_DIR}/ssl_access.log combined
|
||||
|
||||
BrowserMatch "MSIE [2-6]" \
|
||||
nokeepalive ssl-unclean-shutdown \
|
||||
downgrade-1.0 force-response-1.0
|
||||
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
|
||||
</VirtualHost>
|
17
servers/host/deploy/python/premkvirtualenv
Executable file
17
servers/host/deploy/python/premkvirtualenv
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
# This hook is run after a new virtualenv is created and before it is activated.
|
||||
|
||||
cat >$1/pip.conf <<EOF
|
||||
[global]
|
||||
allow-external = PIL
|
||||
allow-unverified = PIL
|
||||
log-file = $WORKON_HOME/$1/pip.log
|
||||
EOF
|
||||
|
||||
cat >$1/bin/postactivate <<EOF
|
||||
export PIP_CONFIG_FILE=$WORKON_HOME/$1/pip.conf
|
||||
EOF
|
||||
|
||||
cat >$1/bin/postdeactivate <<EOF
|
||||
unset PIP_CONFIG_FILE
|
||||
EOF
|
120
servers/host/deploy/rattail/rattail.conf.template
Normal file
120
servers/host/deploy/rattail/rattail.conf.template
Normal file
|
@ -0,0 +1,120 @@
|
|||
|
||||
######################################################################
|
||||
#
|
||||
# Rattail config for 'host' server
|
||||
#
|
||||
######################################################################
|
||||
|
||||
|
||||
##############################
|
||||
# Rattail
|
||||
##############################
|
||||
|
||||
[rattail]
|
||||
timezone.default = America/Chicago
|
||||
|
||||
[rattail.config]
|
||||
configure_logging = true
|
||||
|
||||
[rattail.db]
|
||||
default.url = postgresql://rattail:%(password_postgresql_rattail)s@localhost/rattail
|
||||
|
||||
[rattail.mail]
|
||||
smtp.server = localhost
|
||||
templates = rattail:templates/mail
|
||||
|
||||
default.from = rattail@localhost
|
||||
default.to = root@localhost
|
||||
default.subject = [Rattail] Automated email
|
||||
|
||||
[rattail.pod]
|
||||
pictures.gtin.root_url = http://pod.localhost/pictures/gtin
|
||||
pictures.gtin.root_path = /srv/pod/pictures/gtin
|
||||
|
||||
|
||||
####################
|
||||
# alembic
|
||||
####################
|
||||
|
||||
[alembic]
|
||||
script_location = rattail.db:alembic
|
||||
version_locations = rattail.db:alembic/versions
|
||||
|
||||
|
||||
##############################
|
||||
# Logging
|
||||
##############################
|
||||
|
||||
[loggers]
|
||||
keys = root, exc_logger, beaker, txn, sqlalchemy, django_db, flufl_bounce
|
||||
|
||||
[handlers]
|
||||
keys = file, console, email
|
||||
|
||||
[formatters]
|
||||
keys = generic, console
|
||||
|
||||
[logger_root]
|
||||
handlers = file, console
|
||||
level = DEBUG
|
||||
|
||||
[logger_exc_logger]
|
||||
qualname = exc_logger
|
||||
# Set this to 'email' in order to enable the Pyramid exception logger.
|
||||
handlers =
|
||||
level = ERROR
|
||||
|
||||
[logger_beaker]
|
||||
qualname = beaker
|
||||
handlers =
|
||||
# By default the 'beaker' logger is a bit noisy, this helps.
|
||||
level = INFO
|
||||
|
||||
[logger_txn]
|
||||
qualname = txn
|
||||
handlers =
|
||||
# By default the 'txn' logger is a bit noisy, this helps.
|
||||
level = INFO
|
||||
|
||||
[logger_sqlalchemy]
|
||||
qualname = sqlalchemy.engine
|
||||
handlers =
|
||||
# Uncomment this to make SQLAlchemy log the SQL as it is executed.
|
||||
#level = INFO
|
||||
|
||||
[logger_django_db]
|
||||
qualname = django.db.backends
|
||||
handlers =
|
||||
# If you use the Django ORM at all, this will help to cut down on logging
|
||||
# noise. Set this to DEBUG if you do wish Django to log the SQL as it is
|
||||
# executed.
|
||||
level = INFO
|
||||
|
||||
[logger_flufl_bounce]
|
||||
qualname = flufl.bounce
|
||||
handlers =
|
||||
# By default the 'flufl.bounce' logger is a bit noisy, this helps.
|
||||
level = WARNING
|
||||
|
||||
[handler_file]
|
||||
class = handlers.RotatingFileHandler
|
||||
args = ('/var/log/rattail/rattail.log', 'a', 1000000, 20, 'utf_8')
|
||||
formatter = generic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
formatter = console
|
||||
|
||||
[handler_email]
|
||||
class = handlers.SMTPHandler
|
||||
args = ('localhost', 'rattail@localhost', ['root@localhost'], "[Rattail] Logging")
|
||||
formatter = generic
|
||||
level = ERROR
|
||||
|
||||
[formatter_generic]
|
||||
format = %%(asctime)s %%(levelname)-5.5s [%%(name)s][%%(threadName)s] %%(funcName)s: %%(message)s
|
||||
datefmt = %%Y-%%m-%%d %%H:%%M:%%S
|
||||
|
||||
[formatter_console]
|
||||
format = %%(levelname)-5.5s [%%(name)s][%%(threadName)s] %%(message)s
|
40
servers/host/deploy/rattail/rattail.localhost.conf
Normal file
40
servers/host/deploy/rattail/rattail.localhost.conf
Normal file
|
@ -0,0 +1,40 @@
|
|||
# -*- mode: apache -*-
|
||||
|
||||
WSGIDaemonProcess rattail user=rattail group=rattail
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName rattail.localhost
|
||||
|
||||
WSGIScriptAlias / /srv/envs/rattail/app/rattail.wsgi
|
||||
<Directory /srv/envs/rattail/app/>
|
||||
WSGIProcessGroup rattail
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
LogLevel warn
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
ServerName rattail.localhost
|
||||
|
||||
WSGIScriptAlias / /srv/envs/rattail/app/rattail.wsgi
|
||||
<Directory /srv/envs/rattail/app/>
|
||||
WSGIProcessGroup rattail
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
LogLevel warn
|
||||
CustomLog ${APACHE_LOG_DIR}/ssl_access.log combined
|
||||
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
|
||||
|
||||
BrowserMatch "MSIE [2-6]" \
|
||||
nokeepalive ssl-unclean-shutdown \
|
||||
downgrade-1.0 force-response-1.0
|
||||
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
|
||||
</VirtualHost>
|
9
servers/host/deploy/rattail/rattail.wsgi
Normal file
9
servers/host/deploy/rattail/rattail.wsgi
Normal file
|
@ -0,0 +1,9 @@
|
|||
# -*- coding: utf-8; mode: python -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import site
|
||||
site.addsitedir('/srv/envs/rattail/lib/python2.7/site-packages')
|
||||
|
||||
from pyramid.paster import get_app
|
||||
application = get_app('/srv/envs/rattail/app/web.conf')
|
44
servers/host/deploy/rattail/web.conf
Normal file
44
servers/host/deploy/rattail/web.conf
Normal file
|
@ -0,0 +1,44 @@
|
|||
|
||||
######################################################################
|
||||
#
|
||||
# Tailbone Website
|
||||
#
|
||||
######################################################################
|
||||
|
||||
|
||||
##############################
|
||||
# Rattail
|
||||
##############################
|
||||
|
||||
[rattail.config]
|
||||
include = %(here)s/rattail.conf
|
||||
|
||||
|
||||
####################
|
||||
# Pyramid
|
||||
####################
|
||||
|
||||
[app:main]
|
||||
use = egg:Tailbone
|
||||
|
||||
pyramid.reload_templates = true
|
||||
pyramid.debug_all = true
|
||||
pyramid.default_locale_name = en
|
||||
pyramid.includes = pyramid_debugtoolbar
|
||||
|
||||
beaker.session.type = file
|
||||
beaker.session.data_dir = %(here)s/sessions/data
|
||||
beaker.session.lock_dir = %(here)s/sessions/lock
|
||||
beaker.session.secret = some-gobbledy-gook
|
||||
beaker.session.key = rattail
|
||||
|
||||
# Hack so rattail can find this file from within WSGI app.
|
||||
edbob.config = %(here)s/web.conf
|
||||
|
||||
|
||||
##############################
|
||||
# Logging
|
||||
##############################
|
||||
|
||||
[handler_file]
|
||||
args = ('/srv/envs/rattail/app/log/web.log', 'a', 1000000, 20, 'utf_8')
|
25
servers/host/fabenv.py.dist
Normal file
25
servers/host/fabenv.py.dist
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8; mode: python -*-
|
||||
"""
|
||||
Fabric environment tweaks.
|
||||
|
||||
You must save this file as 'fabenv.py' within the current directory, then edit
|
||||
the settings however you need/like to manage a particular target server.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from fabric.api import env
|
||||
|
||||
|
||||
# Whether or not the target server should be considered "live". This may
|
||||
# control various things, such as whether certain automated tasks are enabled.
|
||||
env.server_is_live = False
|
||||
|
||||
# Whether or not Product Open Data (POD) files should be downloaded.
|
||||
env.rattail_download_pod = False
|
||||
|
||||
# Alternate download URL for POD source file.
|
||||
#env.setting_pod_download_url = 'https://rattailproject.org/downloads/POD/pod_pictures_gtin_2013.08.29_01.zip'
|
||||
|
||||
# Password for 'rattail' PostgreSQL user; used to access the Rattail database.
|
||||
env.password_postgresql_rattail = 'password'
|
144
servers/host/fabfile.py
vendored
Normal file
144
servers/host/fabfile.py
vendored
Normal file
|
@ -0,0 +1,144 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Fabric script for the 'host' server
|
||||
|
||||
Please see the accompanying README for full instructions.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from fabric.api import *
|
||||
from fabric.utils import warn, puts
|
||||
from fabric.contrib.files import append, exists
|
||||
|
||||
from rattail.fablib import make_deploy, mkdir, make_system_user, cdvirtualenv, workon
|
||||
from rattail.fablib import apt, pod, postgresql, python
|
||||
|
||||
|
||||
__all__ = ['bootstrap_all', 'bootstrap_system', 'bootstrap_rattail', 'bootstrap_pod']
|
||||
|
||||
# Set the 'live' role to the canonical hostname for this server.
|
||||
#env.roledefs = {'live': ['host.example.com']}
|
||||
|
||||
deploy = make_deploy(__file__)
|
||||
|
||||
try:
|
||||
import fabenv
|
||||
except ImportError as error:
|
||||
warn("Couldn't import fabenv: {0}".format(error))
|
||||
|
||||
|
||||
@task
|
||||
def bootstrap_all():
|
||||
"""
|
||||
Bootstrap all aspects of the server.
|
||||
"""
|
||||
bootstrap_system()
|
||||
bootstrap_rattail()
|
||||
bootstrap_pod()
|
||||
|
||||
|
||||
@task
|
||||
def bootstrap_system():
|
||||
"""
|
||||
Bootstrap the base system.
|
||||
"""
|
||||
apt.dist_upgrade()
|
||||
|
||||
# postgresql
|
||||
apt.install('postgresql')
|
||||
|
||||
# python
|
||||
apt.install('libpython-dev', 'libpq-dev')
|
||||
python.install_pip()
|
||||
python.install_virtualenvwrapper()
|
||||
deploy('python/premkvirtualenv', '/srv/envs/premkvirtualenv')
|
||||
sudo('mkvirtualenv BASELINE')
|
||||
|
||||
# apache
|
||||
apt.install('apache2', 'libapache2-mod-wsgi')
|
||||
deploy('apache/wsgi.conf', '/etc/apache2/conf-available/wsgi.conf')
|
||||
sudo('a2enconf wsgi')
|
||||
sudo('a2enmod ssl')
|
||||
sudo('service apache2 restart')
|
||||
|
||||
# misc
|
||||
apt.install('git', 'emacs-nox')
|
||||
|
||||
|
||||
@task
|
||||
def bootstrap_rattail():
|
||||
"""
|
||||
Bootstrap the Rattail software.
|
||||
"""
|
||||
from rattail.fablib.rattail import bootstrap_rattail
|
||||
|
||||
# rattail user and core files
|
||||
bootstrap_rattail()
|
||||
|
||||
# virtual environment
|
||||
python.mkvirtualenv('rattail')
|
||||
with cdvirtualenv('rattail'):
|
||||
mkdir('src')
|
||||
with workon('rattail'):
|
||||
python.pip('psycopg2')
|
||||
|
||||
# rattail source
|
||||
with cdvirtualenv('rattail', 'src'):
|
||||
if not exists('rattail'):
|
||||
sudo('git clone https://rattailproject.org/git/rattail.git')
|
||||
with cdvirtualenv('rattail', 'src/rattail'):
|
||||
sudo('git pull')
|
||||
sudo('pip install --editable .')
|
||||
with workon('rattail'):
|
||||
sudo('pip install --upgrade rattail[db,auth]')
|
||||
|
||||
# tailbone source
|
||||
with cdvirtualenv('rattail', 'src'):
|
||||
if not exists('tailbone'):
|
||||
sudo('git clone https://rattailproject.org/git/tailbone.git')
|
||||
with cdvirtualenv('rattail', 'src/tailbone'):
|
||||
sudo('git pull')
|
||||
sudo('pip install --upgrade --editable .')
|
||||
|
||||
# config
|
||||
with cdvirtualenv('rattail', 'app'):
|
||||
deploy('rattail/rattail.conf.template', 'rattail.conf')
|
||||
|
||||
# database
|
||||
postgresql.create_user('rattail', password=env.password_postgresql_rattail)
|
||||
postgresql.create_db('rattail', owner='rattail')
|
||||
with cd('/srv/envs/rattail'):
|
||||
sudo('bin/alembic --config=app/rattail.conf upgrade heads', user='rattail')
|
||||
sudo('bin/rattail --config app/rattail.conf initdb --with-admin', user='rattail')
|
||||
|
||||
# website
|
||||
with cdvirtualenv('rattail', 'app'):
|
||||
mkdir('sessions', owner='rattail:rattail')
|
||||
deploy('rattail/web.conf', 'web.conf')
|
||||
deploy('rattail/rattail.wsgi', 'rattail.wsgi')
|
||||
append('/etc/hosts', '127.0.0.1 rattail.localhost', use_sudo=True)
|
||||
deploy('rattail/rattail.localhost.conf', '/etc/apache2/sites-available/rattail.localhost.conf')
|
||||
sudo('a2ensite rattail.localhost')
|
||||
sudo('service apache2 restart')
|
||||
|
||||
|
||||
@task
|
||||
def bootstrap_pod():
|
||||
"""
|
||||
Bootstrap the POD images website.
|
||||
"""
|
||||
# images
|
||||
mkdir('/srv/pod')
|
||||
if getattr(env, 'rattail_download_pod', False):
|
||||
pod.install_pod()
|
||||
else:
|
||||
puts("Skipping POD download, per 'fabenv' settings.")
|
||||
|
||||
# website
|
||||
append('/etc/hosts', '127.0.0.1 pod.localhost', use_sudo=True)
|
||||
deploy('pod/pod.localhost.conf', '/etc/apache2/sites-available/pod.localhost.conf')
|
||||
sudo('a2ensite pod.localhost')
|
||||
sudo('service apache2 restart')
|
76
setup.py
Normal file
76
setup.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Setup script for Rattail Fabric Demo
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
execfile(os.path.join(here, 'fabdemo', '_version.py'))
|
||||
README = open(os.path.join(here, 'README.rst')).read()
|
||||
|
||||
|
||||
requires = [
|
||||
#
|
||||
# Version numbers within comments below have specific meanings.
|
||||
# Basically the 'low' value is a "soft low," and 'high' a "soft high."
|
||||
# In other words:
|
||||
#
|
||||
# If either a 'low' or 'high' value exists, the primary point to be
|
||||
# made about the value is that it represents the most current (stable)
|
||||
# version available for the package (assuming typical public access
|
||||
# methods) whenever this project was started and/or documented.
|
||||
# Therefore:
|
||||
#
|
||||
# If a 'low' version is present, you should know that attempts to use
|
||||
# versions of the package significantly older than the 'low' version
|
||||
# may not yield happy results. (A "hard" high limit may or may not be
|
||||
# indicated by a true version requirement.)
|
||||
#
|
||||
# Similarly, if a 'high' version is present, and especially if this
|
||||
# project has laid dormant for a while, you may need to refactor a bit
|
||||
# when attempting to support a more recent version of the package. (A
|
||||
# "hard" low limit should be indicated by a true version requirement
|
||||
# when a 'high' version is present.)
|
||||
#
|
||||
# In any case, developers and other users are encouraged to play
|
||||
# outside the lines with regard to these soft limits. If bugs are
|
||||
# encountered then they should be filed as such.
|
||||
#
|
||||
# package # low high
|
||||
|
||||
'Fabric', # 1.10.2
|
||||
'rattail', # 0.5.9
|
||||
]
|
||||
|
||||
|
||||
setup(
|
||||
name = "rattail-fabdemo",
|
||||
version = __version__,
|
||||
author = "Lance Edgar",
|
||||
author_email = "lance@edbob.org",
|
||||
url = "https://rattailproject.org/",
|
||||
description = "Rattail Fabric Deployment Demo",
|
||||
long_description = README,
|
||||
|
||||
classifiers = [
|
||||
'Private :: Do No Upload',
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Topic :: Office/Business',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
],
|
||||
|
||||
install_requires = requires,
|
||||
packages = find_packages(),
|
||||
)
|
Loading…
Reference in a new issue