mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-28 17:32:38 +02:00
Remove support for python2.
In the same move : - use a setup.cfg file for packaging - remove the use of six
This commit is contained in:
parent
9fe84bc1a2
commit
480939afe5
11 changed files with 112 additions and 146 deletions
|
@ -3,7 +3,6 @@ script: tox
|
||||||
install:
|
install:
|
||||||
- pip install tox-travis
|
- pip install tox-travis
|
||||||
python:
|
python:
|
||||||
- "2.7"
|
|
||||||
- "3.5"
|
- "3.5"
|
||||||
- "3.6"
|
- "3.6"
|
||||||
matrix:
|
matrix:
|
||||||
|
|
|
@ -21,7 +21,7 @@ encouraged to do so.
|
||||||
Requirements
|
Requirements
|
||||||
============
|
============
|
||||||
|
|
||||||
* **Python**: 2.7, 3.5, 3.6, 3.7.
|
* **Python**: 3.5, 3.6, 3.7.
|
||||||
* **Backends**: MySQL, PostgreSQL, SQLite, Memory.
|
* **Backends**: MySQL, PostgreSQL, SQLite, Memory.
|
||||||
|
|
||||||
Contributing
|
Contributing
|
||||||
|
|
|
@ -3,4 +3,3 @@ tox
|
||||||
pytest
|
pytest
|
||||||
flake8
|
flake8
|
||||||
Flask-Testing
|
Flask-Testing
|
||||||
mock; python_version < '3.3'
|
|
||||||
|
|
21
docs/conf.py
21
docs/conf.py
|
@ -1,14 +1,15 @@
|
||||||
# -*- coding: utf-8 -*-
|
# coding: utf8
|
||||||
import sys, os
|
import sys, os
|
||||||
templates_path = ['_templates']
|
|
||||||
source_suffix = '.rst'
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
project = u'I hate money'
|
templates_path = ["_templates"]
|
||||||
copyright = u'2011, The \'I hate money\' team'
|
source_suffix = ".rst"
|
||||||
|
master_doc = "index"
|
||||||
|
|
||||||
version = '1.0'
|
project = u"I hate money"
|
||||||
release = '1.0'
|
copyright = u"2011, The 'I hate money' team"
|
||||||
|
|
||||||
|
version = "1.0"
|
||||||
|
release = "1.0"
|
||||||
|
exclude_patterns = ["_build"]
|
||||||
|
pygments_style = "sphinx"
|
||||||
|
|
||||||
exclude_patterns = ['_build']
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# coding: utf8
|
||||||
from flask import Blueprint, request
|
from flask import Blueprint, request
|
||||||
from flask_restful import Resource, Api, abort
|
from flask_restful import Resource, Api, abort
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
# coding: utf8
|
||||||
from __future__ import unicode_literals
|
import unittest
|
||||||
try:
|
from unittest.mock import patch
|
||||||
import unittest2 as unittest
|
|
||||||
except ImportError:
|
|
||||||
import unittest # NOQA
|
|
||||||
try:
|
|
||||||
from unittest.mock import patch
|
|
||||||
except ImportError:
|
|
||||||
from mock import patch
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
|
import io
|
||||||
import json
|
import json
|
||||||
|
import base64
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import six
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
@ -67,7 +62,7 @@ class BaseTestCase(TestCase):
|
||||||
def create_project(self, name):
|
def create_project(self, name):
|
||||||
project = models.Project(
|
project = models.Project(
|
||||||
id=name,
|
id=name,
|
||||||
name=six.text_type(name),
|
name=str(name),
|
||||||
password=generate_password_hash(name),
|
password=generate_password_hash(name),
|
||||||
contact_email="%s@notmyidea.org" % name)
|
contact_email="%s@notmyidea.org" % name)
|
||||||
models.db.session.add(project)
|
models.db.session.add(project)
|
||||||
|
@ -711,7 +706,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
||||||
# rounding issues that prevent test from working.
|
# rounding issues that prevent test from working.
|
||||||
# However, we should obtain the same values as the theorical ones if we
|
# However, we should obtain the same values as the theorical ones if we
|
||||||
# round to 2 decimals, like in the UI.
|
# round to 2 decimals, like in the UI.
|
||||||
for key, value in six.iteritems(balance):
|
for key, value in balance.items():
|
||||||
self.assertEqual(round(value, 2), result[key])
|
self.assertEqual(round(value, 2), result[key])
|
||||||
|
|
||||||
def test_edit_project(self):
|
def test_edit_project(self):
|
||||||
|
@ -1043,7 +1038,7 @@ class APITestCase(IhatemoneyTestCase):
|
||||||
|
|
||||||
def get_auth(self, username, password=None):
|
def get_auth(self, username, password=None):
|
||||||
password = password or username
|
password = password or username
|
||||||
base64string = utils.base64_encode(
|
base64string = base64.encodebytes(
|
||||||
('%s:%s' % (username, password)).encode('utf-8')).decode('utf-8').replace('\n', '')
|
('%s:%s' % (username, password)).encode('utf-8')).decode('utf-8').replace('\n', '')
|
||||||
return {"Authorization": "Basic %s" % base64string}
|
return {"Authorization": "Basic %s" % base64string}
|
||||||
|
|
||||||
|
@ -1605,14 +1600,14 @@ class CommandTestCase(BaseTestCase):
|
||||||
"""
|
"""
|
||||||
cmd = GenerateConfig()
|
cmd = GenerateConfig()
|
||||||
for config_file in cmd.get_options()[0].kwargs['choices']:
|
for config_file in cmd.get_options()[0].kwargs['choices']:
|
||||||
with patch('sys.stdout', new=six.StringIO()) as stdout:
|
with patch('sys.stdout', new=io.StringIO()) as stdout:
|
||||||
cmd.run(config_file)
|
cmd.run(config_file)
|
||||||
print(stdout.getvalue())
|
print(stdout.getvalue())
|
||||||
self.assertNotEqual(len(stdout.getvalue().strip()), 0)
|
self.assertNotEqual(len(stdout.getvalue().strip()), 0)
|
||||||
|
|
||||||
def test_generate_password_hash(self):
|
def test_generate_password_hash(self):
|
||||||
cmd = GeneratePasswordHash()
|
cmd = GeneratePasswordHash()
|
||||||
with patch('sys.stdout', new=six.StringIO()) as stdout, \
|
with patch('sys.stdout', new=io.StringIO()) as stdout, \
|
||||||
patch('getpass.getpass', new=lambda prompt: 'secret'): # NOQA
|
patch('getpass.getpass', new=lambda prompt: 'secret'): # NOQA
|
||||||
cmd.run()
|
cmd.run()
|
||||||
print(stdout.getvalue())
|
print(stdout.getvalue())
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import division
|
|
||||||
import base64
|
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import ast
|
import ast
|
||||||
|
@ -11,7 +9,6 @@ from json import dumps, JSONEncoder
|
||||||
from flask import redirect, current_app
|
from flask import redirect, current_app
|
||||||
from babel import Locale
|
from babel import Locale
|
||||||
from werkzeug.routing import HTTPException, RoutingException
|
from werkzeug.routing import HTTPException, RoutingException
|
||||||
import six
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import csv
|
import csv
|
||||||
|
@ -20,16 +17,13 @@ import csv
|
||||||
def slugify(value):
|
def slugify(value):
|
||||||
"""Normalizes string, converts to lowercase, removes non-alpha characters,
|
"""Normalizes string, converts to lowercase, removes non-alpha characters,
|
||||||
and converts spaces to hyphens.
|
and converts spaces to hyphens.
|
||||||
|
|
||||||
Copy/Pasted from ametaireau/pelican/utils itself took from django sources.
|
|
||||||
"""
|
"""
|
||||||
if isinstance(value, six.text_type):
|
if isinstance(value, str):
|
||||||
import unicodedata
|
import unicodedata
|
||||||
value = unicodedata.normalize('NFKD', value)
|
|
||||||
if six.PY2:
|
value = unicodedata.normalize("NFKD", value)
|
||||||
value = value.encode('ascii', 'ignore')
|
value = str(re.sub(r"[^\w\s-]", "", value).strip().lower())
|
||||||
value = six.text_type(re.sub(r'[^\w\s-]', '', value).strip().lower())
|
return re.sub(r"[-\s]+", "-", value)
|
||||||
return re.sub(r'[-\s]+', '-', value)
|
|
||||||
|
|
||||||
|
|
||||||
class Redirect303(HTTPException, RoutingException):
|
class Redirect303(HTTPException, RoutingException):
|
||||||
|
@ -39,6 +33,7 @@ class Redirect303(HTTPException, RoutingException):
|
||||||
|
|
||||||
The attribute `new_url` contains the absolute destination url.
|
The attribute `new_url` contains the absolute destination url.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
code = 303
|
code = 303
|
||||||
|
|
||||||
def __init__(self, new_url):
|
def __init__(self, new_url):
|
||||||
|
@ -51,7 +46,7 @@ class Redirect303(HTTPException, RoutingException):
|
||||||
|
|
||||||
class PrefixedWSGI(object):
|
class PrefixedWSGI(object):
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Wrap the application in this middleware and configure the
|
Wrap the application in this middleware and configure the
|
||||||
front-end server to add these headers, to let you quietly bind
|
front-end server to add these headers, to let you quietly bind
|
||||||
this to a URL other than / and to an HTTP scheme that is
|
this to a URL other than / and to an HTTP scheme that is
|
||||||
|
@ -62,23 +57,23 @@ class PrefixedWSGI(object):
|
||||||
Inspired from http://flask.pocoo.org/snippets/35/
|
Inspired from http://flask.pocoo.org/snippets/35/
|
||||||
|
|
||||||
:param app: the WSGI application
|
:param app: the WSGI application
|
||||||
'''
|
"""
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.wsgi_app = app.wsgi_app
|
self.wsgi_app = app.wsgi_app
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
script_name = self.app.config['APPLICATION_ROOT']
|
script_name = self.app.config["APPLICATION_ROOT"]
|
||||||
if script_name:
|
if script_name:
|
||||||
environ['SCRIPT_NAME'] = script_name
|
environ["SCRIPT_NAME"] = script_name
|
||||||
path_info = environ['PATH_INFO']
|
path_info = environ["PATH_INFO"]
|
||||||
if path_info.startswith(script_name):
|
if path_info.startswith(script_name):
|
||||||
environ['PATH_INFO'] = path_info[len(script_name):]
|
environ["PATH_INFO"] = path_info[len(script_name) :] # NOQA
|
||||||
|
|
||||||
scheme = environ.get('HTTP_X_SCHEME', '')
|
scheme = environ.get("HTTP_X_SCHEME", "")
|
||||||
if scheme:
|
if scheme:
|
||||||
environ['wsgi.url_scheme'] = scheme
|
environ["wsgi.url_scheme"] = scheme
|
||||||
return self.wsgi_app(environ, start_response)
|
return self.wsgi_app(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,12 +87,12 @@ def minimal_round(*args, **kw):
|
||||||
# Test if the result is equivalent to an integer and
|
# Test if the result is equivalent to an integer and
|
||||||
# return depending on it
|
# return depending on it
|
||||||
ires = int(res)
|
ires = int(res)
|
||||||
return (res if res != ires else ires)
|
return res if res != ires else ires
|
||||||
|
|
||||||
|
|
||||||
def static_include(filename):
|
def static_include(filename):
|
||||||
fullpath = os.path.join(current_app.static_folder, filename)
|
fullpath = os.path.join(current_app.static_folder, filename)
|
||||||
with open(fullpath, 'r') as f:
|
with open(fullpath, "r") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
@ -109,7 +104,7 @@ def list_of_dicts2json(dict_to_convert):
|
||||||
"""Take a list of dictionnaries and turns it into
|
"""Take a list of dictionnaries and turns it into
|
||||||
a json in-memory file
|
a json in-memory file
|
||||||
"""
|
"""
|
||||||
return BytesIO(dumps(dict_to_convert).encode('utf-8'))
|
return BytesIO(dumps(dict_to_convert).encode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
def list_of_dicts2csv(dict_to_convert):
|
def list_of_dicts2csv(dict_to_convert):
|
||||||
|
@ -119,41 +114,27 @@ def list_of_dicts2csv(dict_to_convert):
|
||||||
# CSV writer has a different behavior in PY2 and PY3
|
# CSV writer has a different behavior in PY2 and PY3
|
||||||
# http://stackoverflow.com/a/37974772
|
# http://stackoverflow.com/a/37974772
|
||||||
try:
|
try:
|
||||||
if six.PY3:
|
csv_file = StringIO()
|
||||||
csv_file = StringIO()
|
# using list() for py3.4 compat. Otherwise, writerows() fails
|
||||||
# using list() for py3.4 compat. Otherwise, writerows() fails
|
# (expecting a sequence getting a view)
|
||||||
# (expecting a sequence getting a view)
|
csv_data = [list(dict_to_convert[0].keys())]
|
||||||
csv_data = [list(dict_to_convert[0].keys())]
|
for dic in dict_to_convert:
|
||||||
for dic in dict_to_convert:
|
csv_data.append([dic[h] for h in dict_to_convert[0].keys()])
|
||||||
csv_data.append([dic[h] for h in dict_to_convert[0].keys()])
|
|
||||||
else:
|
|
||||||
csv_file = BytesIO()
|
|
||||||
csv_data = []
|
|
||||||
csv_data.append([key.encode('utf-8') for key in dict_to_convert[0].keys()])
|
|
||||||
for dic in dict_to_convert:
|
|
||||||
csv_data.append(
|
|
||||||
[dic[h].encode('utf8')
|
|
||||||
if isinstance(dic[h], unicode) else str(dic[h]).encode('utf8') # NOQA
|
|
||||||
for h in dict_to_convert[0].keys()])
|
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
csv_data = []
|
csv_data = []
|
||||||
writer = csv.writer(csv_file)
|
writer = csv.writer(csv_file)
|
||||||
writer.writerows(csv_data)
|
writer.writerows(csv_data)
|
||||||
csv_file.seek(0)
|
csv_file.seek(0)
|
||||||
if six.PY3:
|
csv_file = BytesIO(csv_file.getvalue().encode("utf-8"))
|
||||||
csv_file = BytesIO(csv_file.getvalue().encode('utf-8'))
|
|
||||||
return csv_file
|
return csv_file
|
||||||
|
|
||||||
|
|
||||||
# base64 encoding that works with both py2 and py3 and yield no warning
|
class LoginThrottler:
|
||||||
base64_encode = base64.encodestring if six.PY2 else base64.encodebytes
|
|
||||||
|
|
||||||
|
|
||||||
class LoginThrottler():
|
|
||||||
"""Simple login throttler used to limit authentication attempts based on client's ip address.
|
"""Simple login throttler used to limit authentication attempts based on client's ip address.
|
||||||
When using multiple workers, remaining number of attempts can get inconsistent
|
When using multiple workers, remaining number of attempts can get inconsistent
|
||||||
but will still be limited to num_workers * max_attempts.
|
but will still be limited to num_workers * max_attempts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, max_attempts=3, delay=1):
|
def __init__(self, max_attempts=3, delay=1):
|
||||||
self._max_attempts = max_attempts
|
self._max_attempts = max_attempts
|
||||||
# Delay in minutes before resetting the attempts counter
|
# Delay in minutes before resetting the attempts counter
|
||||||
|
@ -195,16 +176,17 @@ def create_jinja_env(folder, strict_rendering=False):
|
||||||
if set to `True`, all templates which use an undefined variable will
|
if set to `True`, all templates which use an undefined variable will
|
||||||
throw an exception (default to `False`).
|
throw an exception (default to `False`).
|
||||||
"""
|
"""
|
||||||
loader = jinja2.PackageLoader('ihatemoney', folder)
|
loader = jinja2.PackageLoader("ihatemoney", folder)
|
||||||
kwargs = {'loader': loader}
|
kwargs = {"loader": loader}
|
||||||
if strict_rendering:
|
if strict_rendering:
|
||||||
kwargs['undefined'] = jinja2.StrictUndefined
|
kwargs["undefined"] = jinja2.StrictUndefined
|
||||||
return jinja2.Environment(**kwargs)
|
return jinja2.Environment(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class IhmJSONEncoder(JSONEncoder):
|
class IhmJSONEncoder(JSONEncoder):
|
||||||
"""Subclass of the default encoder to support custom objects.
|
"""Subclass of the default encoder to support custom objects.
|
||||||
Taken from the deprecated flask-rest package."""
|
Taken from the deprecated flask-rest package."""
|
||||||
|
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
if hasattr(o, "_to_serialize"):
|
if hasattr(o, "_to_serialize"):
|
||||||
return o._to_serialize
|
return o._to_serialize
|
||||||
|
@ -213,6 +195,7 @@ class IhmJSONEncoder(JSONEncoder):
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
from flask_babel import speaklater
|
from flask_babel import speaklater
|
||||||
|
|
||||||
if isinstance(o, speaklater.LazyString):
|
if isinstance(o, speaklater.LazyString):
|
||||||
try:
|
try:
|
||||||
return unicode(o) # For python 2.
|
return unicode(o) # For python 2.
|
||||||
|
@ -246,7 +229,7 @@ def eval_arithmetic_expression(expr):
|
||||||
expr = str(expr)
|
expr = str(expr)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = _eval(ast.parse(expr, mode='eval').body)
|
result = _eval(ast.parse(expr, mode="eval").body)
|
||||||
except (SyntaxError, TypeError, ZeroDivisionError, KeyError):
|
except (SyntaxError, TypeError, ZeroDivisionError, KeyError):
|
||||||
raise ValueError("Error evaluating expression: {}".format(expr))
|
raise ValueError("Error evaluating expression: {}".format(expr))
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ Mako==1.1.0
|
||||||
MarkupSafe==1.1.1
|
MarkupSafe==1.1.1
|
||||||
python-dateutil==2.8.0
|
python-dateutil==2.8.0
|
||||||
pytz==2019.2
|
pytz==2019.2
|
||||||
six==1.12.0
|
|
||||||
SQLAlchemy==1.3.8
|
SQLAlchemy==1.3.8
|
||||||
Werkzeug==0.16.0
|
Werkzeug==0.16.0
|
||||||
WTForms==2.2.1
|
WTForms==2.2.1
|
||||||
|
|
53
setup.cfg
Normal file
53
setup.cfg
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
[metadata]
|
||||||
|
name = ihatemoney
|
||||||
|
version = 5.dev0
|
||||||
|
url = https://github.com/spiral-project/ihatemoney
|
||||||
|
description = A simple shared budget manager web application.
|
||||||
|
long_description = file: README.rst, CHANGELOG.rst
|
||||||
|
author = Alexis Métaireau & contributors
|
||||||
|
author_email= alexis@notmyidea.org
|
||||||
|
keywords = web, budget
|
||||||
|
license = Custom BSD Beerware
|
||||||
|
classifiers =
|
||||||
|
Programming Language :: Python
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3.5
|
||||||
|
Programming Language :: Python :: 3.6
|
||||||
|
Programming Language :: Python :: 3.7
|
||||||
|
Topic :: Internet :: WWW/HTTP
|
||||||
|
Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
||||||
|
|
||||||
|
[options]
|
||||||
|
packages = find:
|
||||||
|
include_package_data = True
|
||||||
|
zip_safe = False
|
||||||
|
install_requires =
|
||||||
|
flask
|
||||||
|
flask-wtf
|
||||||
|
flask-sqlalchemy<3.0
|
||||||
|
flask-mail
|
||||||
|
Flask-Migrate
|
||||||
|
Flask-script
|
||||||
|
flask-babel
|
||||||
|
flask-restful
|
||||||
|
jinja2
|
||||||
|
blinker
|
||||||
|
flask-cors
|
||||||
|
itsdangerous
|
||||||
|
email_validator
|
||||||
|
debts
|
||||||
|
|
||||||
|
[options.extras_require]
|
||||||
|
dev =
|
||||||
|
zest.releaser
|
||||||
|
tox
|
||||||
|
pytest
|
||||||
|
flake8
|
||||||
|
Flask-Testing
|
||||||
|
|
||||||
|
[options.entry_points]
|
||||||
|
console_scripts =
|
||||||
|
ihatemoney = ihatemoney.manage:main
|
||||||
|
|
||||||
|
paste.app_factory =
|
||||||
|
main = ihatemoney.run:main
|
66
setup.py
66
setup.py
|
@ -1,65 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
from setuptools import setup
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from io import open
|
setup()
|
||||||
from setuptools import setup, find_packages
|
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
|
|
||||||
README = open('README.rst', encoding='utf-8').read()
|
|
||||||
CHANGELOG = open('CHANGELOG.rst', encoding='utf-8').read()
|
|
||||||
|
|
||||||
description = u'\n'.join([README, CHANGELOG])
|
|
||||||
if sys.version_info.major < 3:
|
|
||||||
description = description.encode('utf-8')
|
|
||||||
|
|
||||||
ENTRY_POINTS = {
|
|
||||||
'paste.app_factory': [
|
|
||||||
'main = ihatemoney.run:main',
|
|
||||||
],
|
|
||||||
'console_scripts': [
|
|
||||||
'ihatemoney = ihatemoney.manage:main'
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
setup(name='ihatemoney',
|
|
||||||
version='4.2.dev0',
|
|
||||||
description='A simple shared budget manager web application.',
|
|
||||||
long_description=description,
|
|
||||||
license='Custom BSD Beerware',
|
|
||||||
classifiers=[
|
|
||||||
"Programming Language :: Python",
|
|
||||||
"Programming Language :: Python :: 2.7",
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Programming Language :: Python :: 3.5",
|
|
||||||
"Programming Language :: Python :: 3.6",
|
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
|
||||||
"Topic :: Internet :: WWW/HTTP",
|
|
||||||
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
|
|
||||||
],
|
|
||||||
keywords="web budget",
|
|
||||||
author='Alexis Métaireau & contributors',
|
|
||||||
author_email='alexis@notmyidea.org',
|
|
||||||
url='https://github.com/spiral-project/ihatemoney',
|
|
||||||
packages=find_packages(),
|
|
||||||
include_package_data=True,
|
|
||||||
zip_safe=False,
|
|
||||||
install_requires=[
|
|
||||||
"flask",
|
|
||||||
"flask-wtf",
|
|
||||||
"flask-sqlalchemy<3.0",
|
|
||||||
"flask-mail",
|
|
||||||
"Flask-Migrate",
|
|
||||||
"Flask-script",
|
|
||||||
"flask-babel",
|
|
||||||
"flask-restful",
|
|
||||||
"jinja2",
|
|
||||||
"blinker",
|
|
||||||
"flask-cors",
|
|
||||||
"six",
|
|
||||||
"itsdangerous",
|
|
||||||
"email_validator",
|
|
||||||
"debts"],
|
|
||||||
entry_points=ENTRY_POINTS)
|
|
||||||
|
|
3
tox.ini
3
tox.ini
|
@ -1,5 +1,5 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py37,py36,py35,py34,py27,docs,lint
|
envlist = py37,py36,py35,py34,docs,lint
|
||||||
skip_missing_interpreters = True
|
skip_missing_interpreters = True
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
@ -33,7 +33,6 @@ max_line_length = 100
|
||||||
|
|
||||||
[travis]
|
[travis]
|
||||||
python =
|
python =
|
||||||
2.7: py27
|
|
||||||
3.5: py35
|
3.5: py35
|
||||||
3.6: py36, docs, lint
|
3.6: py36, docs, lint
|
||||||
3.7: py37
|
3.7: py37
|
||||||
|
|
Loading…
Reference in a new issue