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:
|
||||
- pip install tox-travis
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
matrix:
|
||||
|
|
|
@ -21,7 +21,7 @@ encouraged to do so.
|
|||
Requirements
|
||||
============
|
||||
|
||||
* **Python**: 2.7, 3.5, 3.6, 3.7.
|
||||
* **Python**: 3.5, 3.6, 3.7.
|
||||
* **Backends**: MySQL, PostgreSQL, SQLite, Memory.
|
||||
|
||||
Contributing
|
||||
|
|
|
@ -3,4 +3,3 @@ tox
|
|||
pytest
|
||||
flake8
|
||||
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
|
||||
templates_path = ['_templates']
|
||||
source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
project = u'I hate money'
|
||||
copyright = u'2011, The \'I hate money\' team'
|
||||
templates_path = ["_templates"]
|
||||
source_suffix = ".rst"
|
||||
master_doc = "index"
|
||||
|
||||
version = '1.0'
|
||||
release = '1.0'
|
||||
project = u"I hate money"
|
||||
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_restful import Resource, Api, abort
|
||||
from flask_cors import CORS
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
try:
|
||||
import unittest2 as unittest
|
||||
except ImportError:
|
||||
import unittest # NOQA
|
||||
try:
|
||||
from unittest.mock import patch
|
||||
except ImportError:
|
||||
from mock import patch
|
||||
# coding: utf8
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import io
|
||||
import json
|
||||
import base64
|
||||
|
||||
from collections import defaultdict
|
||||
import six
|
||||
from time import sleep
|
||||
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
@ -67,7 +62,7 @@ class BaseTestCase(TestCase):
|
|||
def create_project(self, name):
|
||||
project = models.Project(
|
||||
id=name,
|
||||
name=six.text_type(name),
|
||||
name=str(name),
|
||||
password=generate_password_hash(name),
|
||||
contact_email="%s@notmyidea.org" % name)
|
||||
models.db.session.add(project)
|
||||
|
@ -711,7 +706,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
|||
# rounding issues that prevent test from working.
|
||||
# However, we should obtain the same values as the theorical ones if we
|
||||
# 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])
|
||||
|
||||
def test_edit_project(self):
|
||||
|
@ -1043,7 +1038,7 @@ class APITestCase(IhatemoneyTestCase):
|
|||
|
||||
def get_auth(self, username, password=None):
|
||||
password = password or username
|
||||
base64string = utils.base64_encode(
|
||||
base64string = base64.encodebytes(
|
||||
('%s:%s' % (username, password)).encode('utf-8')).decode('utf-8').replace('\n', '')
|
||||
return {"Authorization": "Basic %s" % base64string}
|
||||
|
||||
|
@ -1605,14 +1600,14 @@ class CommandTestCase(BaseTestCase):
|
|||
"""
|
||||
cmd = GenerateConfig()
|
||||
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)
|
||||
print(stdout.getvalue())
|
||||
self.assertNotEqual(len(stdout.getvalue().strip()), 0)
|
||||
|
||||
def test_generate_password_hash(self):
|
||||
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
|
||||
cmd.run()
|
||||
print(stdout.getvalue())
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import division
|
||||
import base64
|
||||
import re
|
||||
import os
|
||||
import ast
|
||||
|
@ -11,7 +9,6 @@ from json import dumps, JSONEncoder
|
|||
from flask import redirect, current_app
|
||||
from babel import Locale
|
||||
from werkzeug.routing import HTTPException, RoutingException
|
||||
import six
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import csv
|
||||
|
@ -20,16 +17,13 @@ import csv
|
|||
def slugify(value):
|
||||
"""Normalizes string, converts to lowercase, removes non-alpha characters,
|
||||
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
|
||||
value = unicodedata.normalize('NFKD', value)
|
||||
if six.PY2:
|
||||
value = value.encode('ascii', 'ignore')
|
||||
value = six.text_type(re.sub(r'[^\w\s-]', '', value).strip().lower())
|
||||
return re.sub(r'[-\s]+', '-', value)
|
||||
|
||||
value = unicodedata.normalize("NFKD", value)
|
||||
value = str(re.sub(r"[^\w\s-]", "", value).strip().lower())
|
||||
return re.sub(r"[-\s]+", "-", value)
|
||||
|
||||
|
||||
class Redirect303(HTTPException, RoutingException):
|
||||
|
@ -39,6 +33,7 @@ class Redirect303(HTTPException, RoutingException):
|
|||
|
||||
The attribute `new_url` contains the absolute destination url.
|
||||
"""
|
||||
|
||||
code = 303
|
||||
|
||||
def __init__(self, new_url):
|
||||
|
@ -51,7 +46,7 @@ class Redirect303(HTTPException, RoutingException):
|
|||
|
||||
class PrefixedWSGI(object):
|
||||
|
||||
'''
|
||||
"""
|
||||
Wrap the application in this middleware and configure the
|
||||
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
|
||||
|
@ -62,23 +57,23 @@ class PrefixedWSGI(object):
|
|||
Inspired from http://flask.pocoo.org/snippets/35/
|
||||
|
||||
:param app: the WSGI application
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.wsgi_app = app.wsgi_app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
script_name = self.app.config['APPLICATION_ROOT']
|
||||
script_name = self.app.config["APPLICATION_ROOT"]
|
||||
if script_name:
|
||||
environ['SCRIPT_NAME'] = script_name
|
||||
path_info = environ['PATH_INFO']
|
||||
environ["SCRIPT_NAME"] = script_name
|
||||
path_info = environ["PATH_INFO"]
|
||||
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:
|
||||
environ['wsgi.url_scheme'] = scheme
|
||||
environ["wsgi.url_scheme"] = scheme
|
||||
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
|
||||
# return depending on it
|
||||
ires = int(res)
|
||||
return (res if res != ires else ires)
|
||||
return res if res != ires else ires
|
||||
|
||||
|
||||
def static_include(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()
|
||||
|
||||
|
||||
|
@ -109,7 +104,7 @@ def list_of_dicts2json(dict_to_convert):
|
|||
"""Take a list of dictionnaries and turns it into
|
||||
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):
|
||||
|
@ -119,41 +114,27 @@ def list_of_dicts2csv(dict_to_convert):
|
|||
# CSV writer has a different behavior in PY2 and PY3
|
||||
# http://stackoverflow.com/a/37974772
|
||||
try:
|
||||
if six.PY3:
|
||||
csv_file = StringIO()
|
||||
# using list() for py3.4 compat. Otherwise, writerows() fails
|
||||
# (expecting a sequence getting a view)
|
||||
csv_data = [list(dict_to_convert[0].keys())]
|
||||
for dic in dict_to_convert:
|
||||
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):
|
||||
csv_data = []
|
||||
writer = csv.writer(csv_file)
|
||||
writer.writerows(csv_data)
|
||||
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
|
||||
|
||||
|
||||
# base64 encoding that works with both py2 and py3 and yield no warning
|
||||
base64_encode = base64.encodestring if six.PY2 else base64.encodebytes
|
||||
|
||||
|
||||
class LoginThrottler():
|
||||
class LoginThrottler:
|
||||
"""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
|
||||
but will still be limited to num_workers * max_attempts.
|
||||
"""
|
||||
|
||||
def __init__(self, max_attempts=3, delay=1):
|
||||
self._max_attempts = max_attempts
|
||||
# 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
|
||||
throw an exception (default to `False`).
|
||||
"""
|
||||
loader = jinja2.PackageLoader('ihatemoney', folder)
|
||||
kwargs = {'loader': loader}
|
||||
loader = jinja2.PackageLoader("ihatemoney", folder)
|
||||
kwargs = {"loader": loader}
|
||||
if strict_rendering:
|
||||
kwargs['undefined'] = jinja2.StrictUndefined
|
||||
kwargs["undefined"] = jinja2.StrictUndefined
|
||||
return jinja2.Environment(**kwargs)
|
||||
|
||||
|
||||
class IhmJSONEncoder(JSONEncoder):
|
||||
"""Subclass of the default encoder to support custom objects.
|
||||
Taken from the deprecated flask-rest package."""
|
||||
|
||||
def default(self, o):
|
||||
if hasattr(o, "_to_serialize"):
|
||||
return o._to_serialize
|
||||
|
@ -213,6 +195,7 @@ class IhmJSONEncoder(JSONEncoder):
|
|||
else:
|
||||
try:
|
||||
from flask_babel import speaklater
|
||||
|
||||
if isinstance(o, speaklater.LazyString):
|
||||
try:
|
||||
return unicode(o) # For python 2.
|
||||
|
@ -246,7 +229,7 @@ def eval_arithmetic_expression(expr):
|
|||
expr = str(expr)
|
||||
|
||||
try:
|
||||
result = _eval(ast.parse(expr, mode='eval').body)
|
||||
result = _eval(ast.parse(expr, mode="eval").body)
|
||||
except (SyntaxError, TypeError, ZeroDivisionError, KeyError):
|
||||
raise ValueError("Error evaluating expression: {}".format(expr))
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ Mako==1.1.0
|
|||
MarkupSafe==1.1.1
|
||||
python-dateutil==2.8.0
|
||||
pytz==2019.2
|
||||
six==1.12.0
|
||||
SQLAlchemy==1.3.8
|
||||
Werkzeug==0.16.0
|
||||
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 -*-
|
||||
import os
|
||||
import sys
|
||||
from setuptools import setup
|
||||
|
||||
from io import open
|
||||
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)
|
||||
setup()
|
||||
|
|
3
tox.ini
3
tox.ini
|
@ -1,5 +1,5 @@
|
|||
[tox]
|
||||
envlist = py37,py36,py35,py34,py27,docs,lint
|
||||
envlist = py37,py36,py35,py34,docs,lint
|
||||
skip_missing_interpreters = True
|
||||
|
||||
[testenv]
|
||||
|
@ -33,7 +33,6 @@ max_line_length = 100
|
|||
|
||||
[travis]
|
||||
python =
|
||||
2.7: py27
|
||||
3.5: py35
|
||||
3.6: py36, docs, lint
|
||||
3.7: py37
|
||||
|
|
Loading…
Reference in a new issue