Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0b34cef
Add check login test
JonathanZhu11 Jul 13, 2020
1f0bcdc
fix filter
JonathanZhu11 Jul 13, 2020
b7252e0
Update packages
JonathanZhu11 Jul 13, 2020
2861815
Add Python
JonathanZhu11 Jul 14, 2020
4035979
Try python only
JonathanZhu11 Jul 14, 2020
747b100
Try adding unixodbc-dev
JonathanZhu11 Jul 14, 2020
f2c7ebc
Try adding unixodbc-dev
JonathanZhu11 Jul 14, 2020
2e5dfbb
overwrite script key
JonathanZhu11 Jul 14, 2020
9ec1818
Add install of sqlmlutils
JonathanZhu11 Jul 14, 2020
7125e0d
fix conftest
JonathanZhu11 Jul 14, 2020
920d9d0
syntax
JonathanZhu11 Jul 14, 2020
eb137a9
Move samples out
JonathanZhu11 Jul 14, 2020
34e469c
target pytest
JonathanZhu11 Jul 14, 2020
ac47a49
skip a slow test
JonathanZhu11 Jul 14, 2020
26368ba
Update to use AirlineUser and Wheel for pep425 tags
JonathanZhu11 Jul 21, 2020
fcebeec
bug fixes
JonathanZhu11 Jul 21, 2020
2967aea
import sys
JonathanZhu11 Jul 21, 2020
2a61600
Add AirlineUserdbowner
JonathanZhu11 Jul 22, 2020
a09da13
Update to 1.0.2 version
JonathanZhu11 Jul 22, 2020
320580c
testing some build processes
JonathanZhu11 Jul 22, 2020
2321fb6
skip absl test
JonathanZhu11 Jul 23, 2020
dcea76f
Add R tests back
JonathanZhu11 Jul 23, 2020
d7ddbf1
Change display max_colwidth back
JonathanZhu11 Jul 23, 2020
e171e7f
fix up tests
JonathanZhu11 Jul 23, 2020
adb45e2
merge
JonathanZhu11 Jul 23, 2020
54a6eb9
dependency resolution
JonathanZhu11 Jul 24, 2020
8b07441
remove unixodbc install
JonathanZhu11 Jul 24, 2020
0db2716
comments
JonathanZhu11 Jul 24, 2020
e8878e5
fix name
JonathanZhu11 Jul 24, 2020
ae673f1
fix test
JonathanZhu11 Jul 24, 2020
e969af1
Address comments (#76)
JonathanZhu11 Aug 5, 2020
edd5320
renew zips with changed code
JonathanZhu11 Aug 7, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions Python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion Python/buildandinstall.cmd
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions Python/buildandinstall.sh
Original file line number Diff line number Diff line change
@@ -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
Binary file not shown.
3 changes: 2 additions & 1 deletion Python/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 5 additions & 4 deletions Python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
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',
Expand All @@ -19,8 +19,9 @@
'pyodbc',
'dill',
'pkginfo',
'requirements-parser',
'pandas'
'requirements-parser',
'pandas',
'wheel'
],
python_requires='>=3.5'
)
22 changes: 8 additions & 14 deletions Python/sqlmlutils/packagemanagement/dependencyresolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import operator

from pkg_resources import Requirement
from distutils.version import LooseVersion

class DependencyResolver:
Comment thread
JonathanZhu11 marked this conversation as resolved.
Expand All @@ -28,9 +29,8 @@ def get_required_installs(self, target_requirements):
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))
Expand All @@ -39,26 +39,20 @@ def get_required_installs(self, target_requirements):
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]
Comment thread
JonathanZhu11 marked this conversation as resolved.
return Requirement.parse(requirement.line).specifier.contains(installed_version)
2 changes: 1 addition & 1 deletion Python/sqlmlutils/packagemanagement/download_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
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
Expand Down
12 changes: 10 additions & 2 deletions Python/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@
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)
83 changes: 31 additions & 52 deletions Python/tests/package_management_file_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import io
import os
import sys
import subprocess
import tempfile
from contextlib import redirect_stdout
Expand All @@ -13,7 +14,7 @@
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"
Expand All @@ -24,6 +25,7 @@
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
Expand All @@ -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)
Expand All @@ -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))}
Expand All @@ -83,36 +73,29 @@ 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"

_remove_all_new_packages(pkgmanager)

_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")
Comment thread
JonathanZhu11 marked this conversation as resolved.


def test_install_whl_files():
packages = ["webencodings-0.5.1-py2.py3-none-any.whl", "html5lib-1.0.1-py2.py3-none-any.whl",
Comment thread
JonathanZhu11 marked this conversation as resolved.
"""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)

Expand All @@ -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"]
Expand All @@ -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:
Expand All @@ -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)

Expand All @@ -175,6 +149,7 @@ def check_version():
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)
Expand All @@ -184,6 +159,7 @@ def check_version():
assert version == "0.0.1"

# With upgrade
#
pkgmanager.install(package, upgrade=True)

version = pyexecutor.execute_function_in_sql(check_version)
Expand All @@ -192,9 +168,8 @@ def check_version():
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")
Expand All @@ -203,13 +178,13 @@ def get_location():
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)

Expand All @@ -219,6 +194,8 @@ def get_location():

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)

Expand All @@ -227,5 +204,7 @@ def get_location():

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)
Loading