- 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:
Jonathan Zhu 2020-08-07 16:17:27 -07:00 коммит произвёл GitHub
Родитель b0d8c1b456
Коммит 53f42ab253
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 174 добавлений и 181 удалений

Просмотреть файл

@ -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

Двоичные данные
Python/dist/sqlmlutils-1.0.1.zip → Python/dist/sqlmlutils-1.0.2.zip поставляемый

Двоичный файл не отображается.

Просмотреть файл

@ -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