Add Travis CI for Python (#74)
- Add Python testing to the Travis-CI - pip moved away from holding pep425tags in their codebase in pip version 20+, so we just import from wheel instead. - The way we resolved requirement specs was not the most robust. We can just use pkg_resources's Requirement class functionality instead of writing our own. In particular, our original implementation did not work with "~=". - Removed a number of tests that were redundant. These tests were fine when run on the user machine, but when run on the Travis-CI server they will time out.
This commit is contained in:
Родитель
b0d8c1b456
Коммит
53f42ab253
14
.travis.yml
14
.travis.yml
|
@ -11,3 +11,17 @@ matrix:
|
|||
- ls
|
||||
before_script:
|
||||
- sudo bash "../Travis-CI/installODBC.sh"
|
||||
- language: python
|
||||
python:
|
||||
- 3.7
|
||||
before_install:
|
||||
- cd Python
|
||||
- sudo bash "../Travis-CI/installODBC.sh"
|
||||
before_script:
|
||||
- bash ./buildandinstall.sh
|
||||
- pip install --find-links=dist sqlmlutils
|
||||
script:
|
||||
- pytest tests
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
|
|
@ -10,7 +10,7 @@ pip install sqlmlutils
|
|||
```
|
||||
To install from file, run:
|
||||
```
|
||||
pip install Python/dist/sqlmlutils-1.0.1.zip
|
||||
pip install --find-links=Python/dist sqlmlutils
|
||||
```
|
||||
|
||||
If you are developing on your own branch and want to rebuild and install the package, you can use the buildandinstall.cmd script that is included.
|
||||
|
@ -231,7 +231,7 @@ pkgmanager.uninstall("astor")
|
|||
1. Make sure a SQL Server with an updated ML Services Python is running on localhost.
|
||||
2. Restore the AirlineTestDB from the .bak file in this repo
|
||||
3. Make sure Trusted (Windows) authentication works for connecting to the database
|
||||
4. Setup a user with db_owner role (and not server admin) with uid: "Tester" and password "FakeT3sterPwd!"
|
||||
4. Setup a user with db_owner role (and not server admin) with uid: "AirlineUser" and password "FakeT3sterPwd!"
|
||||
|
||||
### Notable TODOs and open issues
|
||||
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
del /q dist\*
|
||||
python.exe setup.py sdist --formats=zip
|
||||
python.exe -m pip install --upgrade --upgrade-strategy only-if-needed dist\sqlmlutils-1.0.1.zip
|
||||
python.exe -m pip install --upgrade --upgrade-strategy only-if-needed --find-links=dist sqlmlutils
|
|
@ -0,0 +1,3 @@
|
|||
rm -f dist/*
|
||||
python setup.py sdist --formats=zip
|
||||
python -m pip install --upgrade --upgrade-strategy only-if-needed --find-links=dist sqlmlutils
|
Двоичный файл не отображается.
|
@ -3,4 +3,5 @@ pyodbc>=4.0.25
|
|||
dill>=0.2.6
|
||||
pkginfo>=1.4.2
|
||||
requirements-parser>=0.2.0
|
||||
pandas>=0.19.2
|
||||
pandas>=0.19.2
|
||||
wheel>=0.32.3
|
||||
|
|
|
@ -6,10 +6,10 @@ from setuptools import setup
|
|||
setup(
|
||||
name='sqlmlutils',
|
||||
packages=['sqlmlutils', 'sqlmlutils/packagemanagement'],
|
||||
version='1.0.1',
|
||||
version='1.0.2',
|
||||
url='https://github.com/Microsoft/sqlmlutils/Python',
|
||||
license='MIT License',
|
||||
desciption='A client side package for working with SQL Server',
|
||||
description='A client side package for working with SQL Server',
|
||||
long_description='A client side package for working with SQL Server Machine Learning Python Services. '
|
||||
'sqlmlutils enables easy package installation and remote code execution on your SQL Server machine.',
|
||||
author='Microsoft',
|
||||
|
@ -19,8 +19,9 @@ setup(
|
|||
'pyodbc',
|
||||
'dill',
|
||||
'pkginfo',
|
||||
'requirements-parser',
|
||||
'pandas'
|
||||
'requirements-parser',
|
||||
'pandas',
|
||||
'wheel'
|
||||
],
|
||||
python_requires='>=3.5'
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import operator
|
||||
|
||||
from pkg_resources import Requirement
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
class DependencyResolver:
|
||||
|
@ -28,9 +29,8 @@ class DependencyResolver:
|
|||
for requirement in target_requirements:
|
||||
reqmet = self._package_exists_on_server(requirement.name)
|
||||
|
||||
for spec in requirement.specs:
|
||||
reqmet = reqmet & self._check_if_installed_package_meets_spec(
|
||||
self._server_packages, requirement.name, spec)
|
||||
reqmet = reqmet and self._check_if_installed_package_meets_spec(
|
||||
self._server_packages, requirement)
|
||||
|
||||
if not reqmet or requirement.name == self._target_package:
|
||||
required_packages.append(self.clean_requirement_name(requirement.name))
|
||||
|
@ -39,26 +39,20 @@ class DependencyResolver:
|
|||
def _package_exists_on_server(self, pkgname):
|
||||
return any([self.clean_requirement_name(pkgname.lower()) ==
|
||||
self.clean_requirement_name(serverpkg[0].lower())
|
||||
for serverpkg in self._server_packages])
|
||||
for serverpkg in self._server_packages])
|
||||
|
||||
@staticmethod
|
||||
def clean_requirement_name(reqname: str):
|
||||
return reqname.replace("-", "_")
|
||||
|
||||
@staticmethod
|
||||
def _check_if_installed_package_meets_spec(package_tuples, name, spec):
|
||||
op_str = spec[0]
|
||||
req_version = spec[1]
|
||||
|
||||
def _check_if_installed_package_meets_spec(package_tuples, requirement):
|
||||
installed_package_name_and_version = [package for package in package_tuples \
|
||||
if DependencyResolver.clean_requirement_name(name.lower()) == \
|
||||
if DependencyResolver.clean_requirement_name(requirement.name.lower()) == \
|
||||
DependencyResolver.clean_requirement_name(package[0].lower())]
|
||||
|
||||
if not installed_package_name_and_version:
|
||||
return False
|
||||
|
||||
installed_package_name_and_version = installed_package_name_and_version[0]
|
||||
installed_version = installed_package_name_and_version[1]
|
||||
|
||||
operator_map = {'>': 'gt', '>=': 'ge', '<': 'lt', '==': 'eq', '<=': 'le', '!=': 'ne'}
|
||||
return getattr(operator, operator_map[op_str])(LooseVersion(installed_version), LooseVersion(req_version))
|
||||
installed_version = installed_package_name_and_version[0][1]
|
||||
return Requirement.parse(requirement.line).specifier.contains(installed_version)
|
||||
|
|
|
@ -10,7 +10,7 @@ from distutils.version import LooseVersion
|
|||
pipversion = LooseVersion(pip.__version__ )
|
||||
|
||||
if pipversion >= LooseVersion("19.3"):
|
||||
from pip._internal import pep425tags
|
||||
from wheel import pep425tags
|
||||
from pip._internal.main import main as pipmain
|
||||
elif pipversion > LooseVersion("10"):
|
||||
from pip._internal import pep425tags
|
||||
|
|
|
@ -11,11 +11,19 @@ database = os.environ['DATABASE'] if 'DATABASE' in os.environ else "AirlineTestD
|
|||
uid = os.environ['USER'] if 'USER' in os.environ else ""
|
||||
pwd = os.environ['PASSWORD'] if 'PASSWORD' in os.environ else ""
|
||||
|
||||
scope = Scope.public_scope() if uid == "" else Scope.private_scope()
|
||||
uidAirlineUser = "AirlineUserdbowner"
|
||||
pwdAirlineUser = os.environ['PASSWORD_AIRLINE_USER'] if 'PASSWORD_AIRLINE_USER' in os.environ else "FakeT3sterPwd!"
|
||||
|
||||
scope = Scope.public_scope() if uid == "" else Scope.private_scope()
|
||||
|
||||
connection = ConnectionInfo(driver=driver,
|
||||
server=server,
|
||||
database=database,
|
||||
uid=uid,
|
||||
pwd=pwd)
|
||||
pwd=pwd)
|
||||
|
||||
airline_user_connection = ConnectionInfo(driver=driver,
|
||||
server=server,
|
||||
database=database,
|
||||
uid=uidAirlineUser,
|
||||
pwd=pwdAirlineUser)
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
from contextlib import redirect_stdout
|
||||
|
@ -13,7 +14,7 @@ from sqlmlutils import ConnectionInfo, SQLPackageManager, SQLPythonExecutor, Sco
|
|||
from package_helper_functions import _get_sql_package_table, _get_package_names_list
|
||||
from sqlmlutils.packagemanagement.pipdownloader import PipDownloader
|
||||
|
||||
from conftest import connection
|
||||
from conftest import connection, airline_user_connection
|
||||
|
||||
path_to_packages = os.path.join((os.path.dirname(os.path.realpath(__file__))), "scripts", "test_packages")
|
||||
_SUCCESS_TOKEN = "SUCCESS"
|
||||
|
@ -24,6 +25,7 @@ pkgmanager = SQLPackageManager(connection)
|
|||
originals = _get_sql_package_table(connection)
|
||||
|
||||
def check_package(package_name: str, exists: bool, class_to_check: str = ""):
|
||||
"""Check and assert whether a package exists, and if a class is in the module"""
|
||||
if exists:
|
||||
themodule = __import__(package_name)
|
||||
assert themodule is not None
|
||||
|
@ -33,25 +35,13 @@ def check_package(package_name: str, exists: bool, class_to_check: str = ""):
|
|||
with pytest.raises(Exception):
|
||||
__import__(package_name)
|
||||
|
||||
|
||||
def _execute_sql(script: str) -> bool:
|
||||
tmpfile = tempfile.NamedTemporaryFile(delete=False)
|
||||
tmpfile.write(script.encode())
|
||||
tmpfile.close()
|
||||
command = ["sqlcmd", "-d", "AirlineTestDB", "-i", tmpfile.name]
|
||||
try:
|
||||
output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True).decode()
|
||||
return _SUCCESS_TOKEN in output
|
||||
finally:
|
||||
os.remove(tmpfile.name)
|
||||
|
||||
|
||||
def _drop(package_name: str, ddl_name: str):
|
||||
"""Uninstall a package and check that it is gone"""
|
||||
pkgmanager.uninstall(package_name)
|
||||
pyexecutor.execute_function_in_sql(check_package, package_name=package_name, exists=False)
|
||||
|
||||
|
||||
def _create(module_name: str, package_file: str, class_to_check: str, drop: bool = True):
|
||||
"""Install a package and check that it is installed"""
|
||||
try:
|
||||
pyexecutor.execute_function_in_sql(check_package, package_name=module_name, exists=False)
|
||||
pkgmanager.install(package_file)
|
||||
|
@ -60,8 +50,8 @@ def _create(module_name: str, package_file: str, class_to_check: str, drop: bool
|
|||
if drop:
|
||||
_drop(package_name=module_name, ddl_name=module_name)
|
||||
|
||||
|
||||
def _remove_all_new_packages(manager):
|
||||
"""Drop all packages that were not there in the original list"""
|
||||
df = _get_sql_package_table(connection)
|
||||
|
||||
libs = {df['external_library_id'][i]: (df['name'][i], df['scope'][i]) for i in range(len(df.index))}
|
||||
|
@ -83,14 +73,16 @@ def _remove_all_new_packages(manager):
|
|||
manager.uninstall(pkg, scope=Scope.public_scope())
|
||||
|
||||
|
||||
packages = ["absl-py==0.1.13", "astor==0.8.1", "bleach==1.5.0",
|
||||
"html5lib==1.0.1", "Markdown==2.6.11", "termcolor==1.1.0", "webencodings==0.5.1"]
|
||||
# Download the package zips we will use for these tests
|
||||
#
|
||||
packages = ["astor==0.8.1", "html5lib==1.0.1", "termcolor==1.1.0"]
|
||||
|
||||
for package in packages:
|
||||
pipdownloader = PipDownloader(connection, path_to_packages, package)
|
||||
pipdownloader.download_single()
|
||||
|
||||
def test_install_basic_zip_package():
|
||||
"""Test a basic zip package"""
|
||||
package = os.path.join(path_to_packages, "testpackageA-0.0.1.zip")
|
||||
module_name = "testpackageA"
|
||||
|
||||
|
@ -98,21 +90,12 @@ def test_install_basic_zip_package():
|
|||
|
||||
_create(module_name=module_name, package_file=package, class_to_check="ClassA")
|
||||
|
||||
|
||||
def test_install_basic_zip_package_different_name():
|
||||
package = os.path.join(path_to_packages, "testpackageA-0.0.1.zip")
|
||||
module_name = "testpackageA"
|
||||
|
||||
_remove_all_new_packages(pkgmanager)
|
||||
|
||||
_create(module_name=module_name, package_file=package, class_to_check="ClassA")
|
||||
|
||||
|
||||
def test_install_whl_files():
|
||||
packages = ["webencodings-0.5.1-py2.py3-none-any.whl", "html5lib-1.0.1-py2.py3-none-any.whl",
|
||||
"""Test some basic wheel files"""
|
||||
packages = ["html5lib-1.0.1-py2.py3-none-any.whl",
|
||||
"astor-0.8.1-py2.py3-none-any.whl"]
|
||||
module_names = ["webencodings", "html5lib", "astor"]
|
||||
classes_to_check = ["LABELS", "parse", "code_gen"]
|
||||
module_names = ["html5lib", "astor"]
|
||||
classes_to_check = ["parse", "code_gen"]
|
||||
|
||||
_remove_all_new_packages(pkgmanager)
|
||||
|
||||
|
@ -122,6 +105,7 @@ def test_install_whl_files():
|
|||
|
||||
|
||||
def test_install_targz_files():
|
||||
"""Test a basic tar.gz file"""
|
||||
packages = ["termcolor-1.1.0.tar.gz"]
|
||||
module_names = ["termcolor"]
|
||||
ddl_names = ["termcolor"]
|
||||
|
@ -133,9 +117,8 @@ def test_install_targz_files():
|
|||
full_package = os.path.join(path_to_packages, package)
|
||||
_create(module_name=module, package_file=full_package, class_to_check=class_to_check)
|
||||
|
||||
|
||||
def test_install_bad_package_badzipfile():
|
||||
|
||||
"""Test a zip that is not a package, then make sure it is not in the external_libraries table"""
|
||||
_remove_all_new_packages(pkgmanager)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temporary_directory:
|
||||
|
@ -147,21 +130,12 @@ def test_install_bad_package_badzipfile():
|
|||
|
||||
assert "badpackageA" not in _get_package_names_list(connection)
|
||||
|
||||
query = """
|
||||
declare @val int;
|
||||
set @val = (select count(*) from sys.external_libraries where name='badpackageA')
|
||||
if @val = 0
|
||||
print('{}')
|
||||
""".format(_SUCCESS_TOKEN)
|
||||
|
||||
assert _execute_sql(query)
|
||||
|
||||
|
||||
def test_package_already_exists_on_sql_table():
|
||||
|
||||
"""Test the 'upgrade' parameter in installation"""
|
||||
_remove_all_new_packages(pkgmanager)
|
||||
|
||||
# Install a downgraded version of the package first
|
||||
#
|
||||
package = os.path.join(path_to_packages, "testpackageA-0.0.1.zip")
|
||||
pkgmanager.install(package)
|
||||
|
||||
|
@ -175,6 +149,7 @@ def test_package_already_exists_on_sql_table():
|
|||
package = os.path.join(path_to_packages, "testpackageA-0.0.2.zip")
|
||||
|
||||
# Without upgrade
|
||||
#
|
||||
output = io.StringIO()
|
||||
with redirect_stdout(output):
|
||||
pkgmanager.install(package, upgrade=False)
|
||||
|
@ -184,6 +159,7 @@ def test_package_already_exists_on_sql_table():
|
|||
assert version == "0.0.1"
|
||||
|
||||
# With upgrade
|
||||
#
|
||||
pkgmanager.install(package, upgrade=True)
|
||||
|
||||
version = pyexecutor.execute_function_in_sql(check_version)
|
||||
|
@ -192,9 +168,8 @@ def test_package_already_exists_on_sql_table():
|
|||
pkgmanager.uninstall("testpackageA")
|
||||
|
||||
|
||||
# TODO: more tests for drop external library
|
||||
def test_scope():
|
||||
|
||||
"""Test installing in a private scope with a db_owner (not dbo) user"""
|
||||
_remove_all_new_packages(pkgmanager)
|
||||
|
||||
package = os.path.join(path_to_packages, "testpackageA-0.0.1.zip")
|
||||
|
@ -203,13 +178,13 @@ def test_scope():
|
|||
import testpackageA
|
||||
return testpackageA.__file__
|
||||
|
||||
_revotesterconnection = ConnectionInfo(server="localhost",
|
||||
database="AirlineTestDB",
|
||||
uid="Tester",
|
||||
pwd="FakeT3sterPwd!")
|
||||
revopkgmanager = SQLPackageManager(_revotesterconnection)
|
||||
revoexecutor = SQLPythonExecutor(_revotesterconnection)
|
||||
# The airline_user_connection is NOT dbo, so it has access to both Private and Public scopes
|
||||
#
|
||||
revopkgmanager = SQLPackageManager(airline_user_connection)
|
||||
revoexecutor = SQLPythonExecutor(airline_user_connection)
|
||||
|
||||
# Install a package into the private scope
|
||||
#
|
||||
revopkgmanager.install(package, scope=Scope.private_scope())
|
||||
private_location = revoexecutor.execute_function_in_sql(get_location)
|
||||
|
||||
|
@ -219,6 +194,8 @@ def test_scope():
|
|||
|
||||
revopkgmanager.uninstall(pkg_name, scope=Scope.private_scope())
|
||||
|
||||
# Try the same installation in public scope
|
||||
#
|
||||
revopkgmanager.install(package, scope=Scope.public_scope())
|
||||
public_location = revoexecutor.execute_function_in_sql(get_location)
|
||||
|
||||
|
@ -227,5 +204,7 @@ def test_scope():
|
|||
|
||||
revopkgmanager.uninstall(pkg_name, scope=Scope.public_scope())
|
||||
|
||||
# Make sure the package was removed properly
|
||||
#
|
||||
revoexecutor.execute_function_in_sql(check_package, package_name=pkg_name, exists=False)
|
||||
pyexecutor.execute_function_in_sql(check_package, package_name=pkg_name, exists=False)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
from contextlib import redirect_stdout
|
||||
|
@ -11,7 +12,12 @@ from sqlmlutils import SQLPythonExecutor, SQLPackageManager, Scope
|
|||
|
||||
from conftest import connection, scope
|
||||
|
||||
pyexecutor = SQLPythonExecutor(connection)
|
||||
pkgmanager = SQLPackageManager(connection)
|
||||
initial_list = _get_sql_package_table(connection)['name']
|
||||
|
||||
def _drop_all_ddl_packages(conn, scope):
|
||||
"""Clean the external libraries - drop all packages"""
|
||||
pkgs = _get_sql_package_table(conn)
|
||||
if(len(pkgs.index) > 0 ):
|
||||
for pkg in pkgs['name']:
|
||||
|
@ -21,61 +27,39 @@ def _drop_all_ddl_packages(conn, scope):
|
|||
except Exception as e:
|
||||
pass
|
||||
|
||||
def _get_initial_list(conn, scope):
|
||||
pkgs = _get_sql_package_table(conn)
|
||||
return pkgs['name']
|
||||
|
||||
pyexecutor = SQLPythonExecutor(connection)
|
||||
pkgmanager = SQLPackageManager(connection)
|
||||
initial_list = _get_sql_package_table(connection)['name']
|
||||
|
||||
def _package_exists(module_name: str):
|
||||
"""Check if a package exists"""
|
||||
mod = __import__(module_name)
|
||||
return mod is not None
|
||||
|
||||
|
||||
def _package_no_exist(module_name: str):
|
||||
"""Check that a package does NOT exist"""
|
||||
import pytest
|
||||
with pytest.raises(Exception):
|
||||
__import__(module_name)
|
||||
return True
|
||||
|
||||
@pytest.mark.skip(reason="No version of tensorflow works with currently installed numpy (1.15.4)")
|
||||
def test_install_tensorflow():
|
||||
def use_tensorflow():
|
||||
import tensorflow as tf
|
||||
node1 = tf.constant(3.0, tf.float32)
|
||||
return str(node1.dtype)
|
||||
|
||||
try:
|
||||
pkgmanager.install("tensorflow", upgrade=True)
|
||||
val = pyexecutor.execute_function_in_sql(use_tensorflow)
|
||||
assert 'float32' in val
|
||||
def test_install_different_names():
|
||||
"""Test installing a single package with different capitalization"""
|
||||
def useit():
|
||||
import theano.tensor as T
|
||||
return str(T)
|
||||
|
||||
try:
|
||||
pkgmanager.install("Theano==1.0.4")
|
||||
pyexecutor.execute_function_in_sql(useit)
|
||||
|
||||
pkgmanager.uninstall("Theano")
|
||||
|
||||
pkgmanager.install("theano==1.0.4")
|
||||
pyexecutor.execute_function_in_sql(useit)
|
||||
pkgmanager.uninstall("theano")
|
||||
|
||||
pkgmanager.uninstall("tensorflow")
|
||||
val = pyexecutor.execute_function_in_sql(_package_no_exist, "tensorflow")
|
||||
assert val
|
||||
finally:
|
||||
_drop_all_ddl_packages(connection, scope)
|
||||
|
||||
|
||||
def test_install_many_packages():
|
||||
packages = ["multiprocessing_on_dill", "simplejson"]
|
||||
|
||||
try:
|
||||
for package in packages:
|
||||
pkgmanager.install(package, upgrade=True)
|
||||
val = pyexecutor.execute_function_in_sql(_package_exists, module_name=package)
|
||||
assert val
|
||||
|
||||
pkgmanager.uninstall(package)
|
||||
val = pyexecutor.execute_function_in_sql(_package_no_exist, module_name=package)
|
||||
assert val
|
||||
finally:
|
||||
_drop_all_ddl_packages(connection, scope)
|
||||
|
||||
|
||||
def test_install_version():
|
||||
"""Test the 'version' installation parameter"""
|
||||
package = "simplejson"
|
||||
v = "3.0.3"
|
||||
|
||||
|
@ -94,30 +78,53 @@ def test_install_version():
|
|||
finally:
|
||||
_drop_all_ddl_packages(connection, scope)
|
||||
|
||||
|
||||
def test_dependency_resolution():
|
||||
package = "latex"
|
||||
def test_dependency_spec():
|
||||
"""Test that the DepedencyResolver handles ~= requirement spec.
|
||||
Also tests when package name and module name are different."""
|
||||
package = "azure_cli_telemetry"
|
||||
version = "1.0.4"
|
||||
dependent = "portalocker"
|
||||
module = "azure"
|
||||
|
||||
try:
|
||||
pkgmanager.install(package, upgrade=True)
|
||||
val = pyexecutor.execute_function_in_sql(_package_exists, module_name=package)
|
||||
# Install the package and its dependencies
|
||||
#
|
||||
pkgmanager.install(package, version=version)
|
||||
val = pyexecutor.execute_function_in_sql(_package_exists, module_name=module)
|
||||
assert val
|
||||
|
||||
pkgs = _get_package_names_list(connection)
|
||||
|
||||
assert package in pkgs
|
||||
assert "funcsigs" in pkgs
|
||||
|
||||
assert dependent in pkgs
|
||||
|
||||
# Uninstall the top package only, not the dependencies
|
||||
#
|
||||
pkgmanager.uninstall(package)
|
||||
val = pyexecutor.execute_function_in_sql(_package_no_exist, module_name=package)
|
||||
val = pyexecutor.execute_function_in_sql(_package_no_exist, module_name=module)
|
||||
assert val
|
||||
|
||||
pkgs = _get_package_names_list(connection)
|
||||
|
||||
assert package not in pkgs
|
||||
assert dependent in pkgs
|
||||
|
||||
# Install the package again, make sure DepedencyResolver can handle ~= Requirement spec
|
||||
#
|
||||
pkgmanager.install(package, version=version)
|
||||
val = pyexecutor.execute_function_in_sql(_package_exists, module_name=module)
|
||||
assert val
|
||||
|
||||
pkgs = _get_package_names_list(connection)
|
||||
|
||||
assert package in pkgs
|
||||
assert dependent in pkgs
|
||||
|
||||
finally:
|
||||
_drop_all_ddl_packages(connection, scope)
|
||||
|
||||
|
||||
def test_upgrade_parameter():
|
||||
|
||||
"""Test that "upgrade" installation parameter"""
|
||||
try:
|
||||
pkg = "cryptography"
|
||||
|
||||
|
@ -125,18 +132,22 @@ def test_upgrade_parameter():
|
|||
second_version = "2.8"
|
||||
|
||||
# Install package first so we can test upgrade param
|
||||
#
|
||||
pkgmanager.install(pkg, version=first_version)
|
||||
|
||||
# Get sql packages
|
||||
#
|
||||
originalsqlpkgs = _get_sql_package_table(connection)
|
||||
|
||||
# Try installing WITHOUT the upgrade parameter, it should fail
|
||||
#
|
||||
output = io.StringIO()
|
||||
with redirect_stdout(output):
|
||||
pkgmanager.install(pkg, upgrade=False, version=second_version)
|
||||
assert "exists on server. Set upgrade to True" in output.getvalue()
|
||||
|
||||
# Make sure nothing excess was accidentally installed
|
||||
|
||||
#
|
||||
sqlpkgs = _get_sql_package_table(connection)
|
||||
assert len(sqlpkgs) == len(originalsqlpkgs)
|
||||
|
||||
|
@ -148,6 +159,8 @@ def test_upgrade_parameter():
|
|||
|
||||
oldversion = pyexecutor.execute_function_in_sql(check_version)
|
||||
|
||||
# Test installing WITH the upgrade parameter
|
||||
#
|
||||
pkgmanager.install(pkg, upgrade=True, version=second_version)
|
||||
|
||||
afterinstall = _get_sql_package_table(connection)
|
||||
|
@ -164,52 +177,9 @@ def test_upgrade_parameter():
|
|||
finally:
|
||||
_drop_all_ddl_packages(connection, scope)
|
||||
|
||||
|
||||
def test_install_abslpy():
|
||||
def useit():
|
||||
import absl
|
||||
return absl.__file__
|
||||
|
||||
def dontuseit():
|
||||
import pytest
|
||||
with pytest.raises(Exception):
|
||||
import absl
|
||||
|
||||
try:
|
||||
pkgmanager.install("absl-py")
|
||||
|
||||
pyexecutor.execute_function_in_sql(useit)
|
||||
|
||||
pkgmanager.uninstall("absl-py")
|
||||
|
||||
pyexecutor.execute_function_in_sql(dontuseit)
|
||||
|
||||
finally:
|
||||
_drop_all_ddl_packages(connection, scope)
|
||||
|
||||
|
||||
def test_install_theano():
|
||||
pkgmanager.install("Theano")
|
||||
|
||||
def useit():
|
||||
import theano.tensor as T
|
||||
return str(T)
|
||||
|
||||
try:
|
||||
pyexecutor.execute_function_in_sql(useit)
|
||||
|
||||
pkgmanager.uninstall("Theano")
|
||||
|
||||
pkgmanager.install("theano")
|
||||
pyexecutor.execute_function_in_sql(useit)
|
||||
pkgmanager.uninstall("theano")
|
||||
|
||||
finally:
|
||||
_drop_all_ddl_packages(connection, scope)
|
||||
|
||||
|
||||
def test_already_installed_popular_ml_packages():
|
||||
installedpackages = ["numpy", "scipy", "pandas", "matplotlib", "seaborn", "bokeh", "nltk", "statsmodels"]
|
||||
"""Test packages that are preinstalled, make sure they do not install anything extra"""
|
||||
installedpackages = ["numpy", "scipy", "pandas"]
|
||||
|
||||
sqlpkgs = _get_sql_package_table(connection)
|
||||
for package in installedpackages:
|
||||
|
@ -217,9 +187,10 @@ def test_already_installed_popular_ml_packages():
|
|||
newsqlpkgs = _get_sql_package_table(connection)
|
||||
assert len(sqlpkgs) == len(newsqlpkgs)
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform.startswith("linux"), reason="Slow test, don't run on Travis-CI, which uses Linux")
|
||||
def test_installing_popular_ml_packages():
|
||||
newpackages = ["plotly", "gensim"]
|
||||
"""Test a couple of popular ML packages"""
|
||||
newpackages = ["plotly==4.9.0", "gensim==3.8.3"]
|
||||
|
||||
def checkit(pkgname):
|
||||
val = __import__(pkgname)
|
||||
|
@ -232,8 +203,3 @@ def test_installing_popular_ml_packages():
|
|||
finally:
|
||||
_drop_all_ddl_packages(connection, scope)
|
||||
|
||||
|
||||
# TODO: find a bad pypi package to test this scenario
|
||||
def test_install_bad_pypi_package():
|
||||
pass
|
||||
|
||||
|
|
|
@ -27,10 +27,12 @@ set_option("display.max_columns", None)
|
|||
###################
|
||||
|
||||
def test_no_output():
|
||||
"""Test a function without output param/dataset"""
|
||||
def my_func():
|
||||
print("blah blah blah")
|
||||
|
||||
# Test single quotes as well
|
||||
#
|
||||
print('Hello')
|
||||
|
||||
name = "test_no_output"
|
||||
|
@ -49,6 +51,7 @@ def test_no_output():
|
|||
|
||||
|
||||
def test_no_output_mixed_args():
|
||||
"""Test a function without output, with mixed input parameters"""
|
||||
def mixed(val1: int, val2: str, val3: float, val4: bool):
|
||||
print(val1, val2, val3, val4)
|
||||
|
||||
|
@ -67,6 +70,7 @@ def test_no_output_mixed_args():
|
|||
|
||||
|
||||
def test_no_output_mixed_args_in_df():
|
||||
"""Test a function without output, with mixed input parameters and with input data set"""
|
||||
def mixed(val1: int, val2: str, val3: float, val4: bool, val5: DataFrame):
|
||||
# Prevent truncation of DataFrame when printing
|
||||
#
|
||||
|
@ -98,6 +102,7 @@ def test_no_output_mixed_args_in_df():
|
|||
|
||||
|
||||
def test_no_output_mixed_args_in_df_in_params():
|
||||
"""Test a function without output, with input parameters specified (not implicit)"""
|
||||
def mixed(val1: int, val2: str, val3: float, val4: bool, val5: DataFrame):
|
||||
# Prevent truncation of DataFrame when printing
|
||||
#
|
||||
|
@ -134,6 +139,7 @@ def test_no_output_mixed_args_in_df_in_params():
|
|||
################
|
||||
|
||||
def test_out_df_no_params():
|
||||
"""Test a function with output data set but no parameters"""
|
||||
def no_params():
|
||||
df = DataFrame()
|
||||
df["col1"] = [1, 2, 3, 4, 5]
|
||||
|
@ -154,6 +160,7 @@ def test_out_df_no_params():
|
|||
|
||||
|
||||
def test_out_df_with_args():
|
||||
"""Test a function with output data set and input args"""
|
||||
def my_func_with_args(arg1: str, arg2: str):
|
||||
return DataFrame({"arg1": [arg1], "arg2": [arg2]})
|
||||
|
||||
|
@ -178,6 +185,7 @@ def test_out_df_with_args():
|
|||
|
||||
|
||||
def test_out_df_in_df():
|
||||
"""Test a function with input and output data set"""
|
||||
def in_data(in_df: DataFrame):
|
||||
return in_df
|
||||
|
||||
|
@ -198,6 +206,7 @@ def test_out_df_in_df():
|
|||
|
||||
|
||||
def test_out_df_mixed_args_in_df():
|
||||
"""Test a function with input, output data set and input params"""
|
||||
def mixed(val1: int, val2: str, val3: float, val4: DataFrame, val5: bool):
|
||||
# Prevent truncation of DataFrame when printing
|
||||
#
|
||||
|
@ -229,6 +238,7 @@ def test_out_df_mixed_args_in_df():
|
|||
|
||||
|
||||
def test_out_df_mixed_in_params_in_df():
|
||||
"""Test a function with input, output data set and specified input params"""
|
||||
def mixed(val1, val2, val3, val4, val5):
|
||||
# Prevent truncation of DataFrame when printing
|
||||
#
|
||||
|
@ -262,6 +272,7 @@ def test_out_df_mixed_in_params_in_df():
|
|||
|
||||
|
||||
def test_out_of_order_args():
|
||||
"""Test a function with specified input params and out of order named params"""
|
||||
def mixed(val1, val2, val3, val4, val5):
|
||||
return DataFrame({"val1": [val1], "val2": [val2], "val3": [val3], "val5": [val5]})
|
||||
|
||||
|
@ -290,6 +301,7 @@ def test_out_of_order_args():
|
|||
|
||||
|
||||
def test_in_param_out_param():
|
||||
"""Test a function with input and output params"""
|
||||
def in_out(t1, t2, t3):
|
||||
# Prevent truncation of DataFrame when printing
|
||||
#
|
||||
|
@ -319,6 +331,7 @@ def test_in_param_out_param():
|
|||
|
||||
|
||||
def test_in_df_out_df_dict():
|
||||
"""Test a function with input and output data set, but as dictionary not DataFrame"""
|
||||
def func(in_df: DataFrame):
|
||||
return {"out_df": in_df}
|
||||
|
||||
|
@ -342,9 +355,10 @@ def test_in_df_out_df_dict():
|
|||
|
||||
################
|
||||
# Script Tests #
|
||||
#################
|
||||
################
|
||||
|
||||
def test_script_no_params():
|
||||
"""Test a script with no params, with print output"""
|
||||
script = os.path.join(script_dir, "exec_script_no_params.py")
|
||||
|
||||
name = "exec_script_no_params"
|
||||
|
@ -367,6 +381,7 @@ def test_script_no_params():
|
|||
|
||||
|
||||
def test_script_no_out_params():
|
||||
"""Test a script with input params, with print output"""
|
||||
script = os.path.join(script_dir, "exec_script_no_out_params.py")
|
||||
|
||||
name = "exec_script_no_out_params"
|
||||
|
@ -390,6 +405,7 @@ def test_script_no_out_params():
|
|||
|
||||
|
||||
def test_script_out_df():
|
||||
"""Test a script with an output data set"""
|
||||
script = os.path.join(script_dir, "exec_script_sproc_out_df.py")
|
||||
|
||||
name = "exec_script_out_df"
|
||||
|
@ -411,6 +427,7 @@ def test_script_out_df():
|
|||
|
||||
|
||||
def test_script_out_param():
|
||||
"""Test a script with output params"""
|
||||
script = os.path.join(script_dir, "exec_script_out_param.py")
|
||||
|
||||
name = "exec_script_out_param"
|
||||
|
@ -434,6 +451,7 @@ def test_script_out_param():
|
|||
##################
|
||||
|
||||
def test_execute_bad_param_types():
|
||||
"""Test functions with unsupported or mismatched inputs"""
|
||||
def bad_func(input1: bin):
|
||||
pass
|
||||
|
||||
|
@ -452,8 +470,11 @@ def test_execute_bad_param_types():
|
|||
with pytest.raises(RuntimeError):
|
||||
sqlpy.execute_sproc(name, input1="Hello!")
|
||||
|
||||
sqlpy.drop_sproc(name)
|
||||
|
||||
|
||||
def test_create_bad_name():
|
||||
"""Test creating a sproc with an unsupported name"""
|
||||
def foo():
|
||||
return 1
|
||||
with pytest.raises(RuntimeError):
|
||||
|
@ -461,6 +482,7 @@ def test_create_bad_name():
|
|||
|
||||
|
||||
def test_no_output_bad_num_args():
|
||||
"""Test function with incorrect, untyped, or unmatched input params"""
|
||||
def mixed(val1: str, val2, val3, val4):
|
||||
print(val1, val2, val3)
|
||||
print(val4)
|
||||
|
@ -489,6 +511,7 @@ def test_no_output_bad_num_args():
|
|||
|
||||
|
||||
def test_annotation_vs_input_param():
|
||||
"""Test function with annotations that don't match input params"""
|
||||
def foo(val1: str, val2: int, val3: int):
|
||||
print(val1)
|
||||
print(val2)
|
||||
|
@ -503,6 +526,7 @@ def test_annotation_vs_input_param():
|
|||
|
||||
|
||||
def test_bad_script_path():
|
||||
"""Test nonexistent script"""
|
||||
with pytest.raises(FileNotFoundError):
|
||||
sqlpy.create_sproc_from_script(name="badScript", path_to_script="NonexistentScriptPath")
|
||||
|
||||
|
|
|
@ -10,7 +10,9 @@ sudo curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list > /etc/ap
|
|||
|
||||
#Update and install
|
||||
sudo apt-get update
|
||||
sudo apt-get install unixodbc-dev
|
||||
sudo ACCEPT_EULA=Y apt-get install msodbcsql17
|
||||
|
||||
# optional: for bcp and sqlcmd
|
||||
sudo ACCEPT_EULA=Y apt-get install mssql-tools
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче