Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions CHANGE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
LabKey Python Client API News
+++++++++++

What's New in the LabKey 1.4.1 package
==============================

*Release date: 09/08/2020*

- Add integration tests
- NOTE: The next planned release will be 2.0.0 and is expected to drop support for Python 2.x, we plan to introduce
code that will only be compatible with Python 3.6 and beyond. Python 2.x is no longer supported by the PSF as of
January 1st, 2020.

What's New in the LabKey 1.4.0 package
==============================

Expand Down
2 changes: 1 addition & 1 deletion labkey/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
from labkey import domain, query, experiment, security, utils

__title__ = 'labkey'
__version__ = '1.4.0'
__version__ = '1.4.1'
__author__ = 'LabKey'
__license__ = 'Apache License 2.0'
File renamed without changes.
70 changes: 70 additions & 0 deletions test/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import os
from configparser import ConfigParser

import pytest

from labkey.utils import create_server_context
from labkey import container
from labkey.exceptions import QueryNotFoundError

DEFAULT_HOST = 'localhost'
DEFAULT_PORT = '8080'
DEFAULT_CONTEXT_PATH = 'labkey'
PROJECT_NAME = 'PythonIntegrationTests'


@pytest.fixture(scope='session')
def server_context_vars():
properties_file_path = os.getenv('TEAMCITY_BUILD_PROPERTIES_FILE')
host = DEFAULT_HOST
port = DEFAULT_PORT
context_path = DEFAULT_CONTEXT_PATH

if properties_file_path is not None:
with open(properties_file_path) as f:
contents = f.read()
# .properties files are ini files without any sections, so we need to inject one
contents = '[config]\n' + contents
parser = ConfigParser()
parser.read_string(contents)
parsed_config = parser['config']
host = parsed_config.get('labkey.server', DEFAULT_HOST)
port = parsed_config.get('tomcat.port', DEFAULT_PORT)
context_path = parsed_config.get('labkey.contextpath', DEFAULT_CONTEXT_PATH)

if host.startswith('http://'):
host = host.replace('http://', '')

if context_path.startswith('/'):
context_path = context_path[1:]

return f'{host}:{port}', context_path


@pytest.fixture(scope="session")
def server_context(server_context_vars):
"""
Use this fixture by adding an argument called "server_context" to your test function. It assumes you have a server
running at localhost:8080, a project name "PythonIntegrationTest", and a context path of "labkey". You will need
a netrc file configured with a valid username and password in order for API requests to work.

:return: ServerContext
"""
server, context_path = server_context_vars
return create_server_context(server, PROJECT_NAME, context_path, use_ssl=False)


@pytest.fixture(autouse=True, scope="session")
def project(server_context_vars):
server, context_path = server_context_vars
context = create_server_context(server, '', context_path, use_ssl=False)

try:
container.delete(context, PROJECT_NAME)
except QueryNotFoundError:
# The project may not exist, and that is ok.
pass

project_ = container.create(context, PROJECT_NAME, folderType='study')
yield project_
container.delete(context, PROJECT_NAME)
238 changes: 238 additions & 0 deletions test/integration/test_domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import pytest

from labkey.query import QueryFilter

from labkey.domain import conditional_format, create, drop, get, save

pytestmark = pytest.mark.integration # Mark all tests in this module as integration tests
LISTS_SCHEMA = 'lists'
LIST_NAME = 'testlist'
CONDITIONAL_FORMAT = [{
'filter': 'format.column~gte=25',
'textcolor': 'ff0000',
'backgroundcolor': 'ffffff',
'bold': True,
'italic': False,
'strikethrough': False
}]
SERIALIZED_QUERY_FILTER = QueryFilter('formatted', 35, QueryFilter.Types.LESS_THAN)
SERIALIZED_CONDITIONAL_FORMAT = conditional_format(
query_filter=SERIALIZED_QUERY_FILTER,
bold=False, text_color="ffff00"
).to_json()
LIST_DEFINITION = {
'kind': 'IntList',
'domainDesign': {
'name': LIST_NAME,
'fields': [
{
'name': 'rowId',
'rangeURI': 'int'
},
{
'name': 'formatted',
'rangeURI': 'int',
'conditionalFormats': CONDITIONAL_FORMAT
}
]
},
'options': {
'keyName': 'rowId',
'keyType': 'AutoIncrementInteger'
}
}


@pytest.fixture(scope="function")
def list_fixture(server_context):
create(server_context, LIST_DEFINITION)
created_list = get(server_context, LISTS_SCHEMA, LIST_NAME)
yield created_list
# clean up
drop(server_context, LISTS_SCHEMA, LIST_NAME)


def test_add_conditional_format(server_context, list_fixture):
new_conditional_format = conditional_format(
query_filter='format.column~lte=7',
text_color='ff0055',
background_color='ffffff',
bold=True,
italic=False,
strike_through=False
)

for field in list_fixture.fields:
if field.name == 'formatted':
field.conditional_formats.append(new_conditional_format)

save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)

for field in saved_domain.fields:
if field.name == "formatted":
assert len(field.conditional_formats) == 2


def test_add_conditional_format_with_multiple_filters(server_context, list_fixture):
new_conditional_formats = [
conditional_format(
query_filter=[
QueryFilter(column='column', value=10, filter_type=QueryFilter.Types.LESS_THAN),
QueryFilter(column='column', value=100, filter_type=QueryFilter.Types.GREATER_THAN)
],
text_color='ff0055',
background_color='ffffff',
bold=True,
italic=False,
strike_through=False
)
]

for field in list_fixture.fields:
if field.name == 'formatted':
field.conditional_formats = []
field.conditional_formats = new_conditional_formats

save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)

for field in saved_domain.fields:
if field.name == "formatted":
assert field.conditional_formats.__len__() == 1
assert field.conditional_formats[0].filter == 'format.column~lt=10&format.column~gt=100'


@pytest.mark.xfail # this reproduces https://www.labkey.org/home/Developer/issues/issues-details.view?issueId=41318
def test_add_malformed_query_filter(server_context, list_fixture):
new_conditional_format = conditional_format(
query_filter='this-is-a-badly-formed-filter',
text_color='ff0055',
background_color='ffffff',
bold=True,
italic=False,
strike_through=False
)

for field in list_fixture.fields:
if field.name == 'formatted':
field.conditional_formats = []
field.conditional_formats = [new_conditional_format]

save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)

for field in saved_domain.fields:
if field.name == "formatted":
assert field.conditional_formats[0].filter != 'this-is-a-badly-formed-filter', \
"api should discard meaningless filters"


def test_add_conditional_format_with_missing_filter(server_context, list_fixture):
missing_filter_type_filter = QueryFilter('formatted', 13)
new_conditional_format = conditional_format(
query_filter=missing_filter_type_filter,
text_color='ff0055',
background_color='ffffff',
bold=True,
italic=False,
strike_through=False
)

for field in list_fixture.fields:
if field.name == 'formatted':
field.conditional_formats = []
field.conditional_formats = [new_conditional_format]

save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)

for field in saved_domain.fields:
if field.name == "formatted":
assert field.conditional_formats[0].filter == 'format.column~eq=13'


def test_remove_conditional_format(server_context, list_fixture):
for field in list_fixture.fields:
if field.name == 'formatted':
field.conditional_formats = []

save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)

for field in saved_domain.fields:
if field.name == "formatted":
assert len(field.conditional_formats) == 0


def test_update_conditional_format_serialize_filter(server_context, list_fixture):
from labkey.query import QueryFilter
new_filter = QueryFilter('formatted', 15, QueryFilter.Types.GREATER_THAN_OR_EQUAL)
cf = conditional_format(new_filter, text_color="ff00ff")

for field in list_fixture.fields:
if field.name == 'formatted':
field.conditional_formats[0] = cf

save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)

for field in saved_domain.fields:
if field.name == 'formatted':
assert field.conditional_formats[0].filter == 'format.column~gte=15'


def test_update_conditional_format_plain_text(server_context, list_fixture):
new_filter = "formatted~gte=15"

for field in list_fixture.fields:
if field.name == 'formatted':
field.conditional_formats[0].filter = new_filter

save(server_context, LISTS_SCHEMA, LIST_NAME, list_fixture)
saved_domain = get(server_context, LISTS_SCHEMA, LIST_NAME)

for field in saved_domain.fields:
if field.name == 'formatted':
assert field.conditional_formats[0].filter == new_filter


def test_create_list_with_conditional_formatted_field(server_context):
composed_list_definition = {
'kind': 'IntList',
'domainDesign': {
'name': 'composed_list_name',
'fields': [
{
'name': 'rowId',
'rangeURI': 'int'
},
{
'name': 'formatted',
'rangeURI': 'int',
'conditionalFormats': [
SERIALIZED_CONDITIONAL_FORMAT,
{
'filter': 'format.column~gte=25',
'textcolor': 'ff0000',
'backgroundcolor': 'ffffff',
'bold': True,
'italic': False,
'strikethrough': False
}
]
}
]
},
'options': {
'keyName': 'rowId',
'keyType': 'AutoIncrementInteger'
}
}
create(server_context, composed_list_definition)
created_list = get(server_context, LISTS_SCHEMA, 'composed_list_name')
for field in created_list.fields:
if field.name == 'formatted':
assert len(field.conditional_formats) == 2

drop(server_context, LISTS_SCHEMA, 'composed_list_name')
Loading