# -*- coding: utf-8 -*-
from __future__ import print_function
from setuptools import setup, find_packages
import os
import stat
import sys

#VERSION = "2.1dev4"
VERSION = "3.4"

# Taken from kennethreitz/requests/setup.py
package_directory = os.path.realpath(os.path.dirname(__file__))


def get_file_contents(file_path):
    """Get the context of the file using full path name."""
    content = ""
    try:
        full_path = os.path.join(package_directory, file_path)
        content = open(full_path, 'r').read()
    except:
        print("### could not open file {0!r}".format(file_path), file=sys.stderr)
    return content


def get_file_list(file_path):
    full_path = os.path.join(package_directory, file_path)
    file_list = os.listdir(full_path)
    # now we need to add the path to the files
    return [file_path + f for f in file_list]


install_requires = ["beautifulsoup4[lxml]>=4.3.2",
                    "cbor2>=5.0.1",
                    "configobj>=5.0.6",
                    "croniter>=0.3.8",
                    "cryptography>=2.4.2",
                    "defusedxml>=0.4.1",
                    "ecdsa>=0.13.3",
                    "Flask>=0.10.1",
                    "Flask-Babel>=0.9",
                    "Flask-Migrate>=1.2.0",
                    "Flask-Script>=2.0.5",
                    "Flask-SQLAlchemy>=2.0",
                    "Flask-Versioned>=0.9.4",
                    "future>=0.18.2;python_version<'3.0'",
                    "huey[redis]>=1.11.0",
                    "ldap3>=2.6",
                    "netaddr>=0.7.12",
                    "oauth2client>=2.0.1",
                    "passlib[bcrypt]>=1.7.0",
                    "Pillow>=6.2.1",
                    "PyJWT>=1.3.0",
                    "PyMySQL>=0.6.6",
                    "pyOpenSSL>=17.5",
                    "pyrad>=2.0",
                    "python-dateutil>=2.7.3",
                    "python-gnupg>=0.4.4",
                    "PyYAML>=5.1",
                    "qrcode>=6.1",
                    "requests>=2.7.0",
                    "smpplib>=2.0",
                    "SQLAlchemy>=1.3.0",
                    "sqlsoup>=0.9.0"]


def get_man_pages(dir):
    """
    Get man pages in a directory.
    :param dir:
    :return: list of file names
    """
    files = os.listdir(dir)
    r_files = []
    for file in files:
        if file.endswith(".1"):
            r_files.append(dir + "/" + file)
    return r_files


def get_scripts(dir):
    """
    Get files that are executable
    :param dir:
    :return: list of file names
    """
    files = os.listdir(dir)
    r_files = []
    for file in files:
        if os.stat(dir + "/" + file)[stat.ST_MODE] & stat.S_IEXEC:
            r_files.append(dir + "/" + file)
    return r_files


setup(
    name='privacyIDEA',
    version=VERSION,
    description='privacyIDEA: identity, multifactor authentication (OTP), '
                'authorization, audit',
    author='privacyidea.org',
    license='AGPLv3',
    author_email='cornelius@privacyidea.org',
    url='http://www.privacyidea.org',
    keywords='OTP, two factor authentication, management, security',
    python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.9.*',
    packages=find_packages(),
    scripts=["pi-manage"] + get_scripts("tools"),
    extras_require={
        'doc': ["Sphinx>=1.3.1",
                "sphinxcontrib-httpdomain>=1.3.0",
                "sphinxcontrib-plantuml>=0.18"],
        'test': ["mock>=2.0.0",
                 "pytest>=3.6.0",
                 "pytest-cov>=2.5.1",
                 "responses>=0.9.0"],
        'postgres': ['psycopg2>=2.8.3']
    },
    install_requires=install_requires,
    include_package_data=True,
    data_files=[('etc/privacyidea/',
                 ['deploy/apache/privacyideaapp.wsgi',
                  'deploy/privacyidea/dictionary']),
                ('share/man/man1', get_man_pages("tools")),
                ('lib/privacyidea/migrations',
                 ["migrations/alembic.ini",
                  "migrations/env.py",
                  "migrations/README",
                  "migrations/script.py.mako"]),
                ('lib/privacyidea/migrations/versions',
                 get_file_list("migrations/versions/")),
                ('lib/privacyidea/', ['requirements.txt'])
                ],
    classifiers=["Framework :: Flask",
                 "License :: OSI Approved :: "
                 "GNU Affero General Public License v3",
                 "Programming Language :: Python",
                 "Development Status :: 5 - Production/Stable",
                 "Topic :: Internet",
                 "Topic :: Security",
                 "Topic :: System ::"
                 " Systems Administration :: Authentication/Directory",
                 'Programming Language :: Python',
                 'Programming Language :: Python :: 2',
                 '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 :: 3.8'
                 ],
    zip_safe=False,
    long_description=get_file_contents('README.rst')
)

setup.py
# -*- coding: utf-8 -*-
from __future__ import print_function
from setuptools import setup, find_packages
import os
import stat
import sys

#VERSION = "2.1dev4"
VERSION = "3.4"

# Taken from kennethreitz/requests/setup.py
package_directory = os.path.realpath(os.path.dirname(__file__))


def get_file_contents(file_path):
    """Get the context of the file using full path name."""
    content = ""
    try:
        full_path = os.path.join(package_directory, file_path)
        content = open(full_path, 'r').read()
    except:
        print("### could not open file {0!r}".format(file_path), file=sys.stderr)
    return content


def get_file_list(file_path):
    full_path = os.path.join(package_directory, file_path)
    file_list = os.listdir(full_path)
    # now we need to add the path to the files
    return [file_path + f for f in file_list]


install_requires = ["beautifulsoup4[lxml]>=4.3.2",
                    "cbor2>=5.0.1",
                    "configobj>=5.0.6",
                    "croniter>=0.3.8",
                    "cryptography>=2.4.2",
                    "defusedxml>=0.4.1",
                    "ecdsa>=0.13.3",
                    "Flask>=0.10.1",
                    "Flask-Babel>=0.9",
                    "Flask-Migrate>=1.2.0",
                    "Flask-Script>=2.0.5",
                    "Flask-SQLAlchemy>=2.0",
                    "Flask-Versioned>=0.9.4",
                    "future>=0.18.2;python_version<'3.0'",
                    "huey[redis]>=1.11.0",
                    "ldap3>=2.6",
                    "netaddr>=0.7.12",
                    "oauth2client>=2.0.1",
                    "passlib[bcrypt]>=1.7.0",
                    "Pillow>=6.2.1",
                    "pydash>=4.7.4",
                    "PyJWT>=1.3.0",
                    "PyMySQL>=0.6.6",
                    "pyOpenSSL>=17.5",
                    "pyrad>=2.0",
                    "python-dateutil>=2.7.3",
                    "python-gnupg>=0.4.4",
                    "PyYAML>=5.1",
                    "qrcode>=6.1",
                    "requests>=2.7.0",
                    "smpplib>=2.0",
                    "SQLAlchemy>=1.3.0",
                    "sqlsoup>=0.9.0"]


def get_man_pages(dir):
    """
    Get man pages in a directory.
    :param dir:
    :return: list of file names
    """
    files = os.listdir(dir)
    r_files = []
    for file in files:
        if file.endswith(".1"):
            r_files.append(dir + "/" + file)
    return r_files


def get_scripts(dir):
    """
    Get files that are executable
    :param dir:
    :return: list of file names
    """
    files = os.listdir(dir)
    r_files = []
    for file in files:
        if os.stat(dir + "/" + file)[stat.ST_MODE] & stat.S_IEXEC:
            r_files.append(dir + "/" + file)
    return r_files


setup(
    name='privacyIDEA',
    version=VERSION,
    description='privacyIDEA: identity, multifactor authentication (OTP), '
                'authorization, audit',
    author='privacyidea.org',
    license='AGPLv3',
    author_email='cornelius@privacyidea.org',
    url='http://www.privacyidea.org',
    keywords='OTP, two factor authentication, management, security',
    python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.9.*',
    packages=find_packages(),
    scripts=["pi-manage"] + get_scripts("tools"),
    extras_require={
        'doc': ["Sphinx>=1.3.1",
                "sphinxcontrib-httpdomain>=1.3.0",
                "sphinxcontrib-plantuml>=0.18"],
        'test': ["mock>=2.0.0",
                 "pytest>=3.6.0",
                 "pytest-cov>=2.5.1",
                 "responses>=0.9.0"],
        'postgres': ['psycopg2>=2.8.3']
    },
    install_requires=install_requires,
    include_package_data=True,
    data_files=[('etc/privacyidea/',
                 ['deploy/apache/privacyideaapp.wsgi',
                  'deploy/privacyidea/dictionary']),
                ('share/man/man1', get_man_pages("tools")),
                ('lib/privacyidea/migrations',
                 ["migrations/alembic.ini",
                  "migrations/env.py",
                  "migrations/README",
                  "migrations/script.py.mako"]),
                ('lib/privacyidea/migrations/versions',
                 get_file_list("migrations/versions/")),
                ('lib/privacyidea/', ['requirements.txt'])
                ],
    classifiers=["Framework :: Flask",
                 "License :: OSI Approved :: "
                 "GNU Affero General Public License v3",
                 "Programming Language :: Python",
                 "Development Status :: 5 - Production/Stable",
                 "Topic :: Internet",
                 "Topic :: Security",
                 "Topic :: System ::"
                 " Systems Administration :: Authentication/Directory",
                 'Programming Language :: Python',
                 'Programming Language :: Python :: 2',
                 '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 :: 3.8'
                 ],
    zip_safe=False,
    long_description=get_file_contents('README.rst')
)

setup.py
diff --git a/requirements.txt b/requirements.txt
index 6918bcb893..64762c6e80 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,64 +1,75 @@
-alembic==1.4.2
+alembic==1.4.3
 Babel==2.8.0
 backports.functools-lru-cache==1.6.1; python_version < '3.0'
-bcrypt==3.1.7
-beautifulsoup4==4.9.0
-cbor2==5.1.0
-certifi==2020.4.5.1
-cffi==1.14.0
+bcrypt==3.1.7; python_version <= '3.5'
+bcrypt==3.2.0; python_version > '3.5'
+beautifulsoup4==4.9.2
+cbor2==5.1.2
+certifi==2020.6.20
+cffi==1.14.3
 chardet==3.0.4
-click==7.1.1
+click==7.1.2
 configobj==5.0.6
-croniter==0.3.31
-cryptography==2.9
+contextlib2==0.6.0.post1; python_version < '3.0'
+croniter==0.3.34
+cryptography==3.1.1
 defusedxml==0.6.0
-ecdsa==0.15
-enum34==1.1.6; python_version < '3.0'
+ecdsa==0.16.0
+enum34==1.1.10; python_version < '3.0'
 Flask==1.1.2
-Flask-Babel==1.0.0
+Flask-Babel==2.0.0
 Flask-Migrate==2.5.3
 Flask-Script==2.0.6
-Flask-SQLAlchemy==2.4.1
+Flask-SQLAlchemy==2.4.4
 Flask-Versioned==0.9.4.post20101221
 future==0.18.2; python_version < '3.0'
-httplib2==0.18.0
-huey==2.2.0
-idna==2.9
+httplib2==0.18.1
+huey==2.3.0
+idna==2.10
+importlib-resources==3.0.0
 ipaddress==1.0.23; python_version < '3.0'
 itsdangerous==1.1.0
 Jinja2==2.11.2
-ldap3==2.6.1
-lxml==4.5.0
-Mako==1.1.2
+ldap3==2.8.1
+lxml==4.5.2
+Mako==1.1.3
 MarkupSafe==1.1.1
-netaddr==0.7.19
+natsort==6.2.1; python_version < '3.0'
+natsort==7.0.1; python_version > '3.0'
+netaddr==0.8.0
 oauth2client==4.1.3
 passlib==1.7.2
+pathlib2==2.3.5; python_version < '3.0'
 Pillow==6.2.2; python_version < '3.0'
-Pillow==7.1.1; python_version > '3.0'
+Pillow==7.2.0; python_version > '3.0'
 pyasn1==0.4.8
 pyasn1-modules==0.2.8
 pycparser==2.20
+pydash==4.8.0
 PyJWT==1.7.1
-PyMySQL==0.9.3
+PyMySQL==0.10.1
 pyOpenSSL==19.1.0
 pyrad==2.3
 python-dateutil==2.8.1
 python-editor==1.0.4
-python-gnupg==0.4.5
-pytz==2019.3
+python-gnupg==0.4.6
+pytz==2020.1
 PyYAML==5.3.1
 qrcode==6.1
-redis==3.4.1
-requests==2.23.0
-rsa==4.0; python_version < '3.0'
-rsa==4.1; python_version > '3.0'
-six==1.14.0
+redis==3.5.3
+requests==2.24.0
+rsa==4.5; python_version < '3.0'
+rsa==4.6; python_version > '3.0'
+scandir==1.10.0; python_version < '3.0'
+singledispatch==3.4.0.3; python_version < '3.0'
+six==1.15.0
 smpplib==2.1.0
-soupsieve==1.9.5; python_version < '3.0'
-soupsieve==2.0; python_version > '3.0'
-SQLAlchemy==1.3.16
+soupsieve==1.9.6; python_version < '3.0'
+soupsieve==2.0.1; python_version > '3.0'
+SQLAlchemy==1.3.19
 sqlsoup==0.9.1
-urllib3==1.25.8
+typing==3.7.4.3; python_version < '3.0'
+urllib3==1.25.10
 Werkzeug==1.0.1
-pydash==4.7.4
+zipp==1.2.0; python_version <= '3.5'
+zipp==3.2.0; python_version > '3.5'
diff --git a/setup.py b/setup.py
index 6cbe65b60a..adaf3e05a8 100644
--- a/setup.py
+++ b/setup.py
@@ -50,6 +50,7 @@ def get_file_list(file_path):
                     "oauth2client>=2.0.1",
                     "passlib[bcrypt]>=1.7.0",
                     "Pillow>=6.2.1",
+                    "pydash>=4.7.4",
                     "PyJWT>=1.3.0",
                     "PyMySQL>=0.6.6",
                     "pyOpenSSL>=17.5",
diff --git a/tests/requirements.txt b/tests/requirements.txt
index 981b2fc101..05e1abc27c 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -1,28 +1,24 @@
 -r ../requirements.txt
-atomicwrites==1.3.0; python_version < '3.0'
-attrs==19.3.0
+atomicwrites==1.4.0; python_version < '3.0'
+attrs==20.2.0
 configparser==4.0.2; python_version < '3.0'
-contextlib2==0.6.0.post1; python_version < '3.0'
 cookies==2.2.1; python_version < '3.0'
-coverage==5.1
+coverage==5.3
 funcsigs==1.0.2; python_version < '3.0'
-importlib-metadata==1.6.0
-mock==4.0.2; python_version > '3.5'
+importlib-metadata==2.0.0
+iniconfig==1.0.1; python_version > '3.0'
 mock==3.0.5; python_version <= '3.5'
+mock==4.0.2; python_version > '3.5'
 more-itertools==5.0.0; python_version < '3.0'
-more-itertools==8.2.0; python_version > '3.0'
-packaging==20.3
-pathlib2==2.3.5; python_version < '3.0'
+more-itertools==8.5.0; python_version > '3.0'
+packaging==20.4
 pluggy==0.13.1
-py==1.8.1
+py==1.9.0
 pyparsing==2.4.7
-pytest==4.6.9; python_version < '3.0'
-pytest==5.4.1; python_version > '3.0'
-pytest-cov==2.8.1
-pytest-subtests==0.3.1; python_version > '3.0'
-responses==0.10.12
-scandir==1.10.0; python_version < '3.0'
-testfixtures==6.14.0
-wcwidth==0.1.9
-zipp==3.1.0; python_version > '3.5'
-zipp==1.2.0; python_version <= '3.5'
+pytest==4.6.11; python_version < '3.0'
+pytest==6.1.0; python_version > '3.0'
+pytest-cov==2.10.1
+responses==0.12.0
+testfixtures==6.14.2
+toml==0.10.1; python_version > '3.0'
+wcwidth==0.2.5; python_version < '3.0'
diff --git a/tests/test_lib_caconnector.py b/tests/test_lib_caconnector.py
index f6cb0e1a01..08ee4efd5e 100644
--- a/tests/test_lib_caconnector.py
+++ b/tests/test_lib_caconnector.py
@@ -186,7 +186,7 @@ def tearDownClass(cls):
             try:
                 os.remove("{0!s}/{1!s}".format(WORKINGDIR, f))
             except OSError:
-                print("File {1!s} could not be deleted.".format(f))
+                print("File {0!s} could not be deleted.".format(f))
 
         # restore backup of index.txt and serial
         shutil.copyfile("{0!s}/serial.orig".format(WORKINGDIR),

Update requirements for upcoming version 3.5
Push requirements to newest versions according to https://github.com/privacyidea/privacyidea/wiki/Development-workflow#requirements

