@@ -154,4 +194,4 @@
-
\ No newline at end of file
+
diff --git a/redi/utils/throttle.py b/redi/utils/throttle.py
new file mode 100644
index 0000000..f67ce82
--- /dev/null
+++ b/redi/utils/throttle.py
@@ -0,0 +1,63 @@
+"""
+Utility module for throttling calls to a function
+"""
+
+import collections
+import datetime
+import time
+
+__author__ = "University of Florida CTS-IT Team"
+__copyright__ = "Copyright 2014, University of Florida"
+__license__ = "BSD 3-Clause"
+
+
+class Throttle(object):
+ """
+ Limits the number of calls to a function to a given rate.
+
+ The rate limit is equal to the max_calls over the interval_in_seconds.
+
+ :param function: function to call after throttling
+ :param max_calls: maximum number of calls allowed
+ :param interval_in_seconds: size of the sliding window
+ """
+ def __init__(self, function, max_calls, interval_in_seconds=60):
+ assert max_calls > 0
+ assert interval_in_seconds > 0
+
+ self._actual = function
+ self._max_requests = max_calls
+ self._interval = datetime.timedelta(seconds=interval_in_seconds)
+ self._timestamps = collections.deque(maxlen=self._max_requests)
+
+ def __call__(self, *args, **kwargs):
+ """ Conditionally delays before calling the function """
+ self._wait()
+ self._actual(*args, **kwargs)
+
+ def _limit_reached(self):
+ """ Returns True if the maximum number of calls has been reached """
+ return len(self._timestamps) == self._max_requests
+
+ @staticmethod
+ def _now():
+ # Used during unit testing
+ return datetime.datetime.now()
+
+ @staticmethod
+ def _sleep(seconds):
+ # Used during unit testing
+ return time.sleep(seconds)
+
+ def _wait(self):
+ """ Sleeps for the remaining interval if the limit has been reached """
+ now = self._now()
+
+ limit_reached = len(self._timestamps) == self._max_requests
+ if limit_reached:
+ lapsed = now - self._timestamps[0]
+ if lapsed <= self._interval:
+ self._sleep((self._interval - lapsed).total_seconds())
+ self._timestamps.clear()
+
+ self._timestamps.append(now)
diff --git a/scripts/create_enrollment_csv.bash b/scripts/create_enrollment_csv.bash
new file mode 100644
index 0000000..78f67ef
--- /dev/null
+++ b/scripts/create_enrollment_csv.bash
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Generates subject enrollment records which can be used with the Sample #
+# Project.
+#
+# Example:
+#
+# bash add_subjects.bash 10 > ../config-example/enrollment_test_data.csv
+#
+
+echo "record_id,redcap_event_name,c2826694,c1301894,c2985782,c0806020,enrollment_complete"
+
+for i in $(seq 1 $1)
+do
+ echo "\"$i\",\"1_arm_1\",\"$i\",\"${i}007\",\"2112-01-03\",\"2113-01-01\",2";
+done
+
diff --git a/scripts/synthetic_data/README.md b/scripts/synthetic_data/README.md
index 6e21ac0..bf1fe36 100644
--- a/scripts/synthetic_data/README.md
+++ b/scripts/synthetic_data/README.md
@@ -1,23 +1,37 @@
# Synthetic Data Tools
-makefakedata.R is a tool to create synthetic clinical lab data from simple template files. These files can be used to create sample input data to be processed by RED-I and loaded into a REDCap system.
-
-Using a file that defines the components of a test panel, normal ranges for their values and typical units, makefakedata can create a file of lab results for that panel with multiple instances of that panel for multiple study subjects, over a range of dates. The number of panels, research subjects, date ranges, input and output file names can all be controlled with parameters of makefakedata.
-
-makefakedata is designed to create sample datasets that are free of identifiers and any clinical history. With no claim to ownership, no research value, and no history of private data, these files can be published as test datasets with any software project.
-
-Test data sets can be tailored to the needs of the individual software project via panel templates and input parameters.
+makefakedata.R is a tool to create synthetic clinical lab data from simple
+template files.
+These files can be used to create sample input data to be processed by RED-I
+and loaded into a REDCap system.
+
+Using a file that defines the components of a test panel, normal ranges for
+their values and typical units, makefakedata can create a file of lab results
+for that panel with multiple instances of that panel for multiple study
+subjects, over a range of dates. The number of panels, research subjects, date
+ranges, input and output file names can all be controlled with parameters of
+makefakedata.
+
+makefakedata is designed to create sample datasets that are free of identifiers
+and any clinical history. With no claim to ownership, no research value, and
+no history of private data, these files can be published as test datasets with
+any software project.
+
+Test data sets can be tailored to the needs of the individual software project
+via panel templates and input parameters.
# Example
-In this example, 3 subjects are created in both the CBC and Chemistry output files. For each subject, 7-20 panels of each test will be created.
+In this example, 3 subjects are created in both the CBC and Chemistry output
+files. For each subject, 7-20 panels of each test will be created.
chem <- makefakedata("chemistry_input.csv", "output-chem.csv", min_panel=7, max_panel=20, subject_count=3)
cbc <- makefakedata("cbc_input.csv", "output-cbc.csv", min_panel=7, max_panel=20, subject_count=3)
# Panel Templates
-A template file is a CSV file containing a header row of column labels and one lab component per row. Typical columns for the panel template are
+A template file is a CSV file containing a header row of column labels and one
+lab component per row. Typical columns for the panel template are
* loinc_component - a name that describe a lab component
* loinc_code - the code for that lab component
@@ -27,7 +41,10 @@ A template file is a CSV file containing a header row of column labels and one l
* panel - a lab panel on which these tests are likely to appear
* loinc_long_common_name - a more descriptive name from LOINC
-The columns _low_ and _high_ define a range from which the result value will random chosen. All other columns are strictly optional, but recommended. The input values _loinc_component, loinc_code, low, high, and units_ in the input will appear in the output file without alteration.
+The columns _low_ and _high_ define a range from which the result value will
+random chosen. All other columns are strictly optional, but recommended.
+The input values _loinc_component, loinc_code, low, high, and units_ in the
+input will appear in the output file without alteration.
A typical panel template looks like this:
@@ -38,7 +55,8 @@ A typical panel template looks like this:
Platelets,26515-7,0.172,0.45,10*3/uL,cbc,Platelets [#/volume] in Blood
Hemoglobin,718-7,12,16,g/dl,cbc,Hemoglobin [Mass/volume] in Blood
-Here two CBC panels for one subject generated by makefakedata using the above panel template as an input.
+Here two CBC panels for one subject generated by makefakedata using the above
+panel template as an input.
"result","loinc_component","loinc_code","low","high","units","date_time_stamp","study_id"
3.813,"Leukocytes","26464-8",3.8,10.8,"10*3/uL",2112-10-27,1
@@ -54,6 +72,9 @@ Here two CBC panels for one subject generated by makefakedata using the above pa
# Usage
+Accepted function arguments
+
+
data <- makefakedata(
input,
output,
@@ -63,6 +84,16 @@ Here two CBC panels for one subject generated by makefakedata using the above pa
start_date=<"Earliest date for a lab panel in YYYY-MM-DD format">,
end_date=<"Latest date for a lab panel in YYYY-MM-DD format">,
subject_count=)
+
+Command line example:
+
+$ r
+$ source('makefakedata.R')
+$ makefakedata('example_input.csv', 'out.csv', subject_count=10)
+
+Notes:
+ - If min_panel = max_panel then 5 sets of data are generated
+ - Currently the code does not support integer numbers generation
diff --git a/setup.cfg b/setup.cfg
index d7d6b6f..b9f857c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,7 +3,7 @@ description-file = README.md
[nosetests]
tests=test.TestSuite
-cover-package=bin
+cover-package=redi
# Erase previously collected coverage statistics before run
cover-erase=TRUE
diff --git a/setup.py b/setup.py
index 5d805bc..1d5f98c 100644
--- a/setup.py
+++ b/setup.py
@@ -18,17 +18,17 @@
setup(
name='redi-py',
- version='0.12.0',
+ version='0.13.0',
author='https://www.ctsi.ufl.edu/research/study-development/informatics-consulting/',
author_email='cts-it-red@ctsi.ufl.edu',
packages=find_packages(exclude=['test']),
include_package_data=True,
package_data={
- 'bin': ['utils/*.xsl', 'utils/*.xsd'],
+ 'redi': ['utils/*.xsl', 'utils/*.xsd'],
'redi': ['README.md'],
},
url='https://github.com/ctsit/redi',
- download_url = 'https://github.com/ctsit/redi/releases/tag/0.12.0',
+ download_url = 'https://github.com/ctsit/redi/releases/tag/0.13.0',
keywords = ['EMR', 'EHR', 'REDCap', 'Clinical Data'],
license='BSD 3-Clause',
description='REDCap Electronic Data Importer',
@@ -42,12 +42,13 @@
],
entry_points={
'console_scripts': [
- 'redi = bin.redi:main',
+ 'redi = redi.redi:main',
],
},
test_suite='test.TestSuite',
tests_require=[
"mock >= 1.0",
+ "sftpserver >= 0.2",
],
setup_requires=[
"nose >= 1.0",
diff --git a/test/TestAddElementsToTree.py b/test/TestAddElementsToTree.py
index 7577bf4..f501dc8 100644
--- a/test/TestAddElementsToTree.py
+++ b/test/TestAddElementsToTree.py
@@ -1,6 +1,6 @@
import unittest
from lxml import etree
-import redi
+from redi import redi
class TestAddElementsToTree(unittest.TestCase):
diff --git a/test/TestConvertComponentIdToLoincCode.py b/test/TestConvertComponentIdToLoincCode.py
index 25bdbd6..05e85db 100644
--- a/test/TestConvertComponentIdToLoincCode.py
+++ b/test/TestConvertComponentIdToLoincCode.py
@@ -8,7 +8,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestCopyDataToPersonFormEventTree.py b/test/TestCopyDataToPersonFormEventTree.py
index cf3dd81..4ef20fe 100644
--- a/test/TestCopyDataToPersonFormEventTree.py
+++ b/test/TestCopyDataToPersonFormEventTree.py
@@ -3,7 +3,7 @@
from lxml import etree
-from bin import redi
+from redi import redi
class TestCopyDataToPersonFormEventTree(unittest.TestCase):
diff --git a/test/TestCreateEmptyEventTreeForStudy.py b/test/TestCreateEmptyEventTreeForStudy.py
index 48b7f33..fe696e6 100644
--- a/test/TestCreateEmptyEventTreeForStudy.py
+++ b/test/TestCreateEmptyEventTreeForStudy.py
@@ -1,7 +1,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
@@ -12,39 +12,36 @@
class TestCreateEmptyEventTreeForStudy(unittest.TestCase):
def setUp(self):
- self.all_form_events = """
-
+
"""
-
self.data_all_form_events= etree.ElementTree(etree.fromstring(self.all_form_events))
-
return()
-
def test_create_empty_event_tree_for_study_for_zero_subjects(self):
redi.configure_logging(DEFAULT_DATA_DIRECTORY)
self.zero_subjects = """
@@ -57,7 +54,7 @@ def test_create_empty_event_tree_for_study_for_one_subjects(self):
redi.configure_logging(DEFAULT_DATA_DIRECTORY)
self.one_subject = """
-
+
TestSubject
123456
123
@@ -71,43 +68,47 @@ def test_create_empty_event_tree_for_study_for_one_subjects(self):
"""
self.data_one_subject= etree.ElementTree(etree.fromstring(self.one_subject))
- self.output_one_subject = """123
+ self.output_one_subject = """
+
+
+ 123
+
+
-
-
-
-
+
+
+
+
"""
self.expect_one_subject = etree.tostring(etree.fromstring(self.output_one_subject))
-
- self.result = etree.tostring(redi.create_empty_event_tree_for_study(self.data_one_subject,self.data_all_form_events))
- self.assertEqual(self.expect_one_subject, self.result)
-
+ self.result = etree.tostring(
+ redi.create_empty_event_tree_for_study(self.data_one_subject,self.data_all_form_events))
+ clean_expected = ''.join(self.expect_one_subject.split())
+ clean_result = ''.join(self.result.split())
+ self.assertEqual(clean_expected, clean_result)
+
def test_create_empty_event_tree_for_study_for_two_subjects(self):
redi.configure_logging(DEFAULT_DATA_DIRECTORY)
self.two_subjects = """
-
+
TestSubject_1
123456
123
@@ -117,7 +118,7 @@ def test_create_empty_event_tree_for_study_for_two_subjects(self):
123
cbccbc_lbdtccbc_completecbc_nximporthemo_lborreshemo_lborresuhemo_lbstat
-
+
TestSubject_2
123456
123
@@ -131,62 +132,68 @@ def test_create_empty_event_tree_for_study_for_two_subjects(self):
"""
self.data_two_subjects= etree.ElementTree(etree.fromstring(self.two_subjects))
- self.output_two_subjects = """1234
+ inr
+
+ 1_arm_1
+ inr_lbdtcinr_completeinr_nximport
+
+
+
+
+
"""
self.expect_two_subjects = etree.tostring(etree.fromstring(self.output_two_subjects))
-
- self.result = etree.tostring(redi.create_empty_event_tree_for_study(self.data_two_subjects,self.data_all_form_events))
- self.assertEqual(self.expect_two_subjects, self.result)
+ self.result = etree.tostring(
+ redi.create_empty_event_tree_for_study(self.data_two_subjects,self.data_all_form_events))
+ clean_expected = ''.join(self.expect_two_subjects.split())
+ clean_result = ''.join(self.result.split())
+ self.assertEqual(clean_expected, clean_result)
def tearDown(self):
return()
diff --git a/test/TestCreateEmptyEventsForOneSubject.py b/test/TestCreateEmptyEventsForOneSubject.py
index 498faf2..f76e1e0 100644
--- a/test/TestCreateEmptyEventsForOneSubject.py
+++ b/test/TestCreateEmptyEventsForOneSubject.py
@@ -1,7 +1,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestCreateImportDataJson.py b/test/TestCreateImportDataJson.py
index e74a77b..d884da1 100755
--- a/test/TestCreateImportDataJson.py
+++ b/test/TestCreateImportDataJson.py
@@ -17,36 +17,24 @@
from lxml import etree
import logging
import os
-import redi
-import redi_lib
-
-file_dir = os.path.dirname(os.path.realpath(__file__))
-goal_dir = os.path.join(file_dir, "../")
-proj_root = os.path.abspath(goal_dir)+'/'
+from redi import redi
+from redi import upload
DEFAULT_DATA_DIRECTORY = os.getcwd()
+
class TestCreateImportDataJson(unittest.TestCase):
def setUp(self):
redi.configure_logging(DEFAULT_DATA_DIRECTORY)
self.CONST_STUDY_ID = 73
-
- global logger
- logger = logging.getLogger('redi')
- logging.basicConfig(filename=proj_root+'log/redi.log',
- format='%(asctime)s - %(levelname)s - \
- %(name)s - %(message)s',
- datefmt='%m/%d/%Y %H:%M:%S',
- filemode='w',
- level=logging.DEBUG)
return()
############################
# == TEST_1
def test_empty_event(self):
redi.configure_logging(DEFAULT_DATA_DIRECTORY)
- logger.info("Running " + __name__
+ logging.info("Running " + __name__
+ "#test_empty_event() using study_id: " + `self.CONST_STUDY_ID`)
# Case 1 input string
string_1_empty_event = """
@@ -55,14 +43,14 @@ def test_empty_event(self):
"""
out_dict_1 = {'study_id':self.CONST_STUDY_ID}
etree_1 = etree.ElementTree(etree.fromstring(string_1_empty_event))
- self.assertRaises(Exception, redi_lib.create_import_data_json, out_dict_1, etree_1)
+ self.assertRaises(Exception, upload.create_import_data_json, out_dict_1, etree_1)
############################
# == TEST_2
def test_empty_event_field_value(self):
- logger.info("Running " + __name__
+ logging.info("Running " + __name__
+ "#test_empty_value() for study_id: " + `self.CONST_STUDY_ID`)
# Case 2 input string
string_2_empty_values = """
@@ -102,14 +90,14 @@ def test_empty_event_field_value(self):
etree_2 = etree.ElementTree(etree.fromstring(string_2_empty_values))
out_dict_2 = {'study_id':self.CONST_STUDY_ID}
expected_result_dict_2 = {'contains_data': False, 'json_data': {'chem_complete': '', 'redcap_event_name': '1_arm_1', 'tbil_lborres': '', 'study_id': 73, 'chem_nximport': '', 'tbil_lborresu': '', 'chem_lbdtc': ''}}
- actual_result = redi_lib.create_import_data_json(out_dict_2, etree_2)
+ actual_result = upload.create_import_data_json(out_dict_2, etree_2)
self.assertEqual(expected_result_dict_2,actual_result)
############################
# == Test_3
def test_mixed_event_field_value(self):
- logger.info("Running " + __name__
+ logging.info("Running " + __name__
+ "#test_mixed_event_field_value() for study_id: " + `self.CONST_STUDY_ID`)
@@ -148,7 +136,7 @@ def test_mixed_event_field_value(self):
"""
etree_3 = etree.ElementTree(etree.fromstring(string_3_mixed))
out_dict_3 = {'study_id':self.CONST_STUDY_ID}
- actual_result = redi_lib.create_import_data_json(out_dict_3, etree_3)
+ actual_result = upload.create_import_data_json(out_dict_3, etree_3)
expected_result = {'contains_data': True, 'json_data': {'chem_complete': '2', 'redcap_event_name': '1_arm_1', 'tbil_lborres': '1.7', 'study_id': 73, 'chem_nximport': 'Y', 'tbil_lborresu': '', 'chem_lbdtc': '1902-12-17'}}
self.assertEqual(actual_result, expected_result)
@@ -156,7 +144,7 @@ def test_mixed_event_field_value(self):
############################
# == TEST_4
def test_empty_event_field_name(self):
- logger.info("Running " + __name__
+ logging.info("Running " + __name__
+ "#test_empty_event_field_name() for study_id: " + `self.CONST_STUDY_ID`)
# Case 4 input string
@@ -186,11 +174,11 @@ def test_empty_event_field_name(self):
"""
etree_4 = etree.ElementTree(etree.fromstring(string_4_blank_name))
out_dict_4 = {'study_id':self.CONST_STUDY_ID}
- self.assertRaises(Exception, redi_lib.create_import_data_json, out_dict_4, etree_4)
+ self.assertRaises(Exception, upload.create_import_data_json, out_dict_4, etree_4)
# Verify if code checks for blank `event/name`
def test_empty_event_name(self):
- logger.info("Running " + __name__
+ logging.info("Running " + __name__
+ "#test_empty_event_name() for study_id: " + `self.CONST_STUDY_ID`)
string_4a_blank_name = """
@@ -202,19 +190,19 @@ def test_empty_event_name(self):
"""
etree_4a = etree.ElementTree(etree.fromstring(string_4a_blank_name))
- self.assertRaises(Exception, redi_lib.create_import_data_json, self.CONST_STUDY_ID, etree_4a)
+ self.assertRaises(Exception, upload.create_import_data_json, self.CONST_STUDY_ID, etree_4a)
############################
# == TEST_5
def test_empty_study_id(self) :
- logger.info("Running " + __name__
+ logging.info("Running " + __name__
+ "#test_empty_study_id() for study_id: ''")
string_1_empty_event = """
"""
string_5_out = "error_study_id_empty"
etree_1 = etree.ElementTree(etree.fromstring(string_1_empty_event))
- self.assertRaises(Exception, redi_lib.create_import_data_json,None, etree_1)
+ self.assertRaises(Exception, upload.create_import_data_json,None, etree_1)
def test_multiple_event(self):
# motivated by bug 5996
@@ -231,7 +219,7 @@ def test_multiple_event(self):
second_event = form.xpath('//event')[1]
out_dict_3 = {'study_id':self.CONST_STUDY_ID}
- output = redi_lib.create_import_data_json(out_dict_3, second_event)
+ output = upload.create_import_data_json(out_dict_3, second_event)
self.assertTrue(output['contains_data'])
self.assertFalse('42_arm_42' in output['json_data']['redcap_event_name'])
self.assertTrue('no_arm' in output['json_data']['redcap_event_name'])
diff --git a/test/TestCreateSummaryReport.py b/test/TestCreateSummaryReport.py
index 239ce7b..64e3388 100644
--- a/test/TestCreateSummaryReport.py
+++ b/test/TestCreateSummaryReport.py
@@ -7,11 +7,8 @@
from lxml import etree
from StringIO import StringIO
import time
-import redi
-
-file_dir = os.path.dirname(os.path.realpath(__file__))
-goal_dir = os.path.join(file_dir, "../")
-proj_root = os.path.abspath(goal_dir)+'/'
+from redi import redi
+from redi import report
DEFAULT_DATA_DIRECTORY = os.getcwd()
@@ -21,8 +18,10 @@ def setUp(self):
redi.configure_logging(DEFAULT_DATA_DIRECTORY)
self.test_report_params = {
'project': 'hcvtarget-uf',
- 'report_file_path': proj_root + 'config/report.xml',
- 'redcap_uri': 'https://hostname.org'}
+ 'report_file_path': os.path.join(DEFAULT_DATA_DIRECTORY, 'unittest_report.xml'),
+ 'redcap_uri': 'https://hostname.org',
+ 'is_sort_by_lab_id': True,
+ }
self.test_report_data = {
'total_subjects': 5,
@@ -31,10 +30,10 @@ def setUp(self):
'Total_cbc_Forms': 53
},
'subject_details': {
- '60': {'cbc_Forms': 1, 'chemistry_Forms': 1},
- '61': {'cbc_Forms': 2, 'chemistry_Forms': 1},
- '63': {'cbc_Forms': 11, 'chemistry_Forms': 4},
- '59': {'cbc_Forms': 39, 'chemistry_Forms': 16}
+ '60': {'cbc_Forms': 1, 'chemistry_Forms': 1, 'lab_id': '999-0060'},
+ '61': {'cbc_Forms': 2, 'chemistry_Forms': 1, 'lab_id': '999-0061'},
+ '63': {'cbc_Forms': 11, 'chemistry_Forms': 4, 'lab_id': '999-0063'},
+ '59': {'cbc_Forms': 39, 'chemistry_Forms': 16, 'lab_id': '999-0059'}
},
'errors' : [],
}
@@ -92,7 +91,8 @@ def setUp(self):
- 59
+
+ 59
cbc_Forms
@@ -103,9 +103,10 @@ def setUp(self):
16
-
-
- 60
+ 999-0059
+
+
+ 60
cbc_Forms
@@ -115,8 +116,10 @@ def setUp(self):
1
-
- 61
+ 999-0060
+
+
+ 61
cbc_Forms
@@ -127,9 +130,10 @@ def setUp(self):
1
-
-
- 63
+ 999-0061
+
+
+ 63
cbc_Forms
@@ -140,7 +144,8 @@ def setUp(self):
4
-
+ 999-0063
+
@@ -148,6 +153,7 @@ def setUp(self):
3
20.0
+ lab_id
'''
self.schema_str = StringIO('''\
@@ -222,10 +228,10 @@ def setUp(self):
-
+
-
+
@@ -240,6 +246,7 @@ def setUp(self):
+
@@ -257,6 +264,7 @@ def setUp(self):
+
@@ -264,40 +272,49 @@ def setUp(self):
return
def test_create_summary_report(self):
-
+ """
+ Validates the summary xml structure using xsd
+ Validate the summary xml content
+ """
sys.path.append('config')
- self.newpath = proj_root+'config'
- self.configFolderCreatedNow = False
- if not os.path.exists(self.newpath):
- self.configFolderCreatedNow = True
- os.makedirs(self.newpath)
- result = redi.create_summary_report(\
- self.test_report_params, \
- self.test_report_data, \
- self.test_alert_summary, \
- self.specimen_taken_time_summary)
+ class MockWriter(object):
+ def __call__(self, *args, **kwargs):
+ #expected call: write(tree, report_file_path)
+ self.result = args[0]
+ writer = MockWriter()
+
+ creator = report.ReportCreator(
+ self.test_report_params['report_file_path'],
+ self.test_report_params['project'],
+ self.test_report_params['redcap_uri'],
+ self.test_report_params['is_sort_by_lab_id'],
+ writer)
+
+ creator.create_report(self.test_report_data, self.test_alert_summary,
+ self.specimen_taken_time_summary)
+
+ result = writer.result
+
result_string = etree.tostring(result)
#print result_string
xmlschema_doc = etree.parse(self.schema_str)
xml_schema = etree.XMLSchema(xmlschema_doc)
# validate the xml against the xsd schema
self.assertEqual(xml_schema.validate(result), True)
+
# validate the actual data in xml but strip the white space first
parser = etree.XMLParser(remove_blank_text=True)
clean_tree = etree.XML(self.expected_xml, parser=parser)
self.expected_xml = etree.tostring(clean_tree)
-
self.assertEqual(self.expected_xml, result_string)
def tearDown(self):
# delete the created xml file
- with open(proj_root + 'config/report.xml'):
- os.remove(proj_root + 'config/report.xml')
-
- if self.configFolderCreatedNow:
- os.rmdir(self.newpath)
- return
+ try:
+ os.remove(self.test_report_params['report_file_path'])
+ except:
+ pass
if __name__ == '__main__':
unittest.main()
diff --git a/test/TestDaysSinceToday.py b/test/TestDaysSinceToday.py
index 885b2e6..0e96317 100644
--- a/test/TestDaysSinceToday.py
+++ b/test/TestDaysSinceToday.py
@@ -1,11 +1,9 @@
import unittest
import datetime
from datetime import timedelta
-import redi_lib
-
+from redi import batch
class TestDaysSinceToday(unittest.TestCase):
-
"""
Verify the difference from a past date
Verify the difference from a future date
@@ -14,10 +12,10 @@ def test(self):
past10 = datetime.datetime.now() - timedelta(days = 10)
future11 = datetime.datetime.now() + timedelta(days = 11)
- diff_past = redi_lib.get_days_since_today( str(past10.strftime('%Y-%m-%d %H:%M:%S') ) )
+ diff_past = batch.get_days_since_today( str(past10.strftime('%Y-%m-%d %H:%M:%S') ) )
self.assertEqual(10, diff_past)
- diff_future = redi_lib.get_days_since_today( str(future11.strftime('%Y-%m-%d %H:%M:%S') ) )
+ diff_future = batch.get_days_since_today( str(future11.strftime('%Y-%m-%d %H:%M:%S') ) )
self.assertEqual(-11, diff_future)
diff --git a/test/TestGenerateOutput.py b/test/TestGenerateOutput.py
index c5e9bb0..1d12544 100755
--- a/test/TestGenerateOutput.py
+++ b/test/TestGenerateOutput.py
@@ -16,9 +16,9 @@
import unittest
import os
from lxml import etree
-import redi
-import redi_lib
-from utils.redcapClient import RedcapClient
+from redi import redi
+from redi import upload
+from redi.utils.redcapClient import RedcapClient
DEFAULT_DATA_DIRECTORY = os.getcwd()
@@ -33,11 +33,9 @@ class dummyClass:
def_field = 'test'
def test_person_form_event(self):
- redi.logger.info("Running " + __name__
- + "#test_person_form_event() using xml: " )
string_1_xml = """
-
+
100
@@ -63,7 +61,7 @@ def test_person_form_event(self):
-
+
99
@@ -112,7 +110,7 @@ def test_person_form_event(self):
-
+
98
@@ -144,9 +142,9 @@ def test_person_form_event(self):
form_details = {'Total_cbc_Forms': 2, 'Total_inr_Forms': 3}
subject_details = {
- '98' : {'Total_cbc_Forms' : 0, 'Total_inr_Forms' : 1 },
- '99' : {'Total_cbc_Forms' : 1, 'Total_inr_Forms' : 1 },
- '100' : {'Total_cbc_Forms' : 1, 'Total_inr_Forms' : 1 }
+ '98' : {'Total_cbc_Forms' : 0, 'Total_inr_Forms' : 1, 'lab_id': "999-0098" },
+ '99' : {'Total_cbc_Forms' : 1, 'Total_inr_Forms' : 1, "lab_id": "999-0099" },
+ '100' : {'Total_cbc_Forms' : 1, 'Total_inr_Forms' : 1, "lab_id": "999-0100" }
}
report_data = {
@@ -156,9 +154,20 @@ def test_person_form_event(self):
'errors' : []
}
- class MockDataRepository(object):
- def store(self, data):
- pass
+ class MockSentEventIndex(object):
+ def __init__(self):
+ self.sent_events = []
+
+ def __len__(self):
+ return len(self.sent_events)
+
+ def mark_sent(self, study_id_key, form_name, event_name):
+ form_event_key = study_id_key, form_name, event_name
+ self.sent_events.append(form_event_key)
+
+ def was_sent(self, study_id_key, form_name, event_name):
+ form_event_key = study_id_key, form_name, event_name
+ return form_event_key in self.sent_events
class MockRedcapClient(RedcapClient):
def __init__(self):
@@ -175,8 +184,8 @@ def send_data_to_redcap(self, data, overwrite=False):
return """Data sent"""
etree_1 = etree.ElementTree(etree.fromstring(string_1_xml))
- result = redi_lib.generate_output(etree_1, MockRedcapClient(), 500,
- MockDataRepository())
+ result = upload.generate_output(etree_1, MockRedcapClient(), 500,
+ MockSentEventIndex())
self.assertEqual(report_data['total_subjects'], result['total_subjects'])
self.assertEqual(report_data['form_details'], result['form_details'])
self.assertEqual(report_data['subject_details'], result['subject_details'])
diff --git a/test/TestGetEMRData.py b/test/TestGetEMRData.py
index 4472aff..9e58d4d 100644
--- a/test/TestGetEMRData.py
+++ b/test/TestGetEMRData.py
@@ -5,8 +5,8 @@
import tempfile
import pysftp
from mock import patch
-import utils.GetEmrData as GetEmrData
-from utils.GetEmrData import EmrFileAccessDetails
+from redi.utils import GetEmrData
+from redi.utils.GetEmrData import EmrFileAccessDetails
import time
from subprocess import Popen
diff --git a/test/TestHandleREDCapResponse.py b/test/TestHandleREDCapResponse.py
index ffa6e2e..7e15a12 100644
--- a/test/TestHandleREDCapResponse.py
+++ b/test/TestHandleREDCapResponse.py
@@ -1,27 +1,27 @@
import unittest
import os
-import redi
-import redi_lib
-
-file_dir = os.path.dirname(os.path.realpath(__file__))
-goal_dir = os.path.join(file_dir, "../")
-proj_root = os.path.abspath(goal_dir)+'/'
+from redi import redi
+from redi import upload
DEFAULT_DATA_DIRECTORY = os.getcwd()
class TestHandleErrorsInREDCapResponse(unittest.TestCase):
+ """ Variables setup """
def setUp(self):
redi.configure_logging(DEFAULT_DATA_DIRECTORY)
return()
def test_handle_errors_in_redcap_xml_response_valid_case(self):
+ """ Test the correctness of function
+ upload.handle_errors_in_redcap_xml_response
+ """
self.redcap_error = """{"error": "There were data validation errors","records": [{"record": "1 (1_arm_1)", "field_name": "wbc_lborres",
"value": "5.4",
"message": "This field is located on a form that is locked. You must first unlock this form for this record"}]}"""
self.report_data = {'errors':[]}
- self.assertTrue(redi_lib.handle_errors_in_redcap_xml_response(self.redcap_error,self.report_data))
+ self.assertTrue(upload.handle_errors_in_redcap_xml_response(self.redcap_error, self.report_data))
# Below code is made obsolete because we are handling errors only in case of exceptions.We are not checking for errors in valid cases anymore.
# def test_handle_errors_in_redcap_xml_response_with_no_error(self):
@@ -36,7 +36,7 @@ def test_handle_errors_in_redcap_xml_response_with_no_errorKey_in_report_data(se
"value": "5.4",
"message": "This field is located on a form that is locked. You must first unlock this form for this record"}]}"""
self.report_data = {}
- self.assertTrue(redi_lib.handle_errors_in_redcap_xml_response(self.redcap_pass,self.report_data))
+ self.assertTrue(upload.handle_errors_in_redcap_xml_response(self.redcap_pass, self.report_data))
def tearDown(self):
diff --git a/test/TestLog.py b/test/TestLog.py
index 7093026..dc55c2b 100644
--- a/test/TestLog.py
+++ b/test/TestLog.py
@@ -11,7 +11,7 @@
import unittest
import os
import sys
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestParseAll.py b/test/TestParseAll.py
index c398b01..d004f61 100644
--- a/test/TestParseAll.py
+++ b/test/TestParseAll.py
@@ -17,33 +17,18 @@
from lxml import etree
import logging
import os
-import redi
-
-file_dir = os.path.dirname(os.path.realpath(__file__))
-goal_dir = os.path.join(file_dir, "../")
-proj_root = os.path.abspath(goal_dir)+'/'
+from redi import redi
DEFAULT_DATA_DIRECTORY = os.getcwd()
class TestParseAll(unittest.TestCase):
def setUp(self):
- global logger
- logger = logging.getLogger('redi')
- logging.basicConfig(filename=DEFAULT_DATA_DIRECTORY,
- format='%(asctime)s - %(levelname)s - \
- %(name)s - %(message)s',
- datefmt='%m/%d/%Y %H:%M:%S',
- filemode='w',
- level=logging.DEBUG)
- return()
+ redi.configure_logging(DEFAULT_DATA_DIRECTORY)
############################
# == TEST_1 - config/formEvents.xml
def test_parse_form_events(self):
- redi.configure_logging(DEFAULT_DATA_DIRECTORY)
- logger.info("Running " + __name__
- + "#test_person_form_event() using xml: " )
string_1_xml = """
Project
diff --git a/test/TestParseRawXml.py b/test/TestParseRawXml.py
index 2e9c766..fb40d70 100644
--- a/test/TestParseRawXml.py
+++ b/test/TestParseRawXml.py
@@ -8,7 +8,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestParseTranslationTable.py b/test/TestParseTranslationTable.py
index feaa353..83c33a4 100644
--- a/test/TestParseTranslationTable.py
+++ b/test/TestParseTranslationTable.py
@@ -8,7 +8,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestPersonFormEventsRepository.py b/test/TestPersonFormEventsRepository.py
index b59fdc5..850d8c4 100644
--- a/test/TestPersonFormEventsRepository.py
+++ b/test/TestPersonFormEventsRepository.py
@@ -3,7 +3,7 @@
from lxml import etree
-from bin.redi import PersonFormEventsRepository
+from redi.redi import PersonFormEventsRepository
class TestPersonFormEventsRepository(unittest.TestCase):
diff --git a/test/TestReadConfig.py b/test/TestReadConfig.py
index a03e4c4..9e64dc2 100644
--- a/test/TestReadConfig.py
+++ b/test/TestReadConfig.py
@@ -3,8 +3,8 @@
import shutil
import os
-from bin import redi
-from bin.utils import SimpleConfigParser
+from redi import redi
+from redi.utils import SimpleConfigParser
class TestReadConfig(unittest.TestCase):
diff --git a/test/TestRediEmail.py b/test/TestRediEmail.py
index b106a8d..dc1e40f 100644
--- a/test/TestRediEmail.py
+++ b/test/TestRediEmail.py
@@ -1,15 +1,16 @@
import unittest
import smtplib
from mock import patch, call
-import redi
-from utils import redi_email
+from redi import redi
+from redi.utils.rawxml import RawXml
+from redi.utils import redi_email
class TestRediEmail(unittest.TestCase):
"""
Check functions in the `utils/redi_email` module
To run individually:
- $ PYTHONPATH=bin python test/TestRediEmail.py
+ $ PYTHONPATH=redi python test/TestRediEmail.py
"""
def setUp(self):
@@ -26,6 +27,8 @@ def setUp(self):
}
self.settings = type("", (), settings)()
self.email_settings = redi.get_email_settings(self.settings)
+ self.raw_xml = RawXml('', __file__)
+
def test_get_email_settings(self):
"""Check if we picked proper values from the global settings"""
@@ -57,14 +60,14 @@ def test_success(self):
""" Verify return true when email is sent"""
ese = self.email_settings
self.assertTrue(redi_email.send_email_redcap_connection_error(ese))
- self.assertTrue(redi_email.send_email_input_data_unchanged(ese))
+ self.assertTrue(redi_email.send_email_input_data_unchanged(ese, self.raw_xml))
@patch.multiple(redi_email, send_email=dummy_send_failed)
def test_failed(self):
""" Verify return false when email is not sent"""
ese = self.email_settings
self.assertFalse(redi_email.send_email_redcap_connection_error(ese))
- self.assertFalse(redi_email.send_email_input_data_unchanged(ese))
+ self.assertFalse(redi_email.send_email_input_data_unchanged(ese, self.raw_xml))
@patch("smtplib.SMTP")
def test_mime_email(self, mock_smtp):
@@ -82,7 +85,8 @@ def test_mime_email_exception(self, mock_smtp):
ese = self.email_settings
instance = mock_smtp.return_value
instance.sendmail.side_effect = smtplib.SMTPRecipientsRefused({})
- self.assertRaises(smtplib.SMTPRecipientsRefused, redi_email.send_email_data_import_completed, ese)
+ self.assertRaises(smtplib.SMTPRecipientsRefused,\
+ redi_email.send_email_data_import_completed, ese)
self.assertEqual(instance.sendmail.call_count, 1)
def tearDown(self):
diff --git a/test/TestResearchIdToRedcapId.py b/test/TestResearchIdToRedcapId.py
index be0cd93..6af20ae 100644
--- a/test/TestResearchIdToRedcapId.py
+++ b/test/TestResearchIdToRedcapId.py
@@ -3,10 +3,10 @@
import os
from lxml import etree
from mock import patch
-import redi
-from utils import redi_email
-from utils.redcapClient import RedcapClient
-import utils.SimpleConfigParser as SimpleConfigParser
+from redi import redi
+from redi.utils import redi_email
+from redi.utils.redcapClient import RedcapClient
+from redi.utils import SimpleConfigParser
from requests import RequestException
file_dir = os.path.dirname(os.path.realpath(__file__))
@@ -19,7 +19,7 @@ class TestResearchIdToRedcapId(unittest.TestCase):
def setUp(self):
self.sortedData = """
-
+
HEMOGLOBIN
1534435
@@ -28,8 +28,9 @@ def setUp(self):
16.0
g/dL
- 999-0059
- cbccbc_lbdtccbc_completecbc_nximporthemo_lborreshemo_lborresuhemo_lbstat
+ 999-0001
+ cbccbc_lbdtccbc_completecbc_nximporthemo_lborreshemo_lborresuhemo_lbstat
+
WBC
999
@@ -38,39 +39,19 @@ def setUp(self):
- 999-0059
- cbccbc_lbdtccbc_completecbc_nximportwbc_lborreswbc_lborresuwbc_lbstat
-
- PLATELET COUNT
- 1009
- 92
-
-
-
-
- 999-0059
- cbccbc_lbdtccbc_completecbc_nximportplat_lborresplat_lborresuplat_lbstat
-
- HEMOGLOBIN
- 1534435
- 9.5
- 12.0
- 16.0
- g/dL
-
- 999-0059
- cbccbc_lbdtccbc_completecbc_nximporthemo_lborreshemo_lborresuhemo_lbstat
- """
+ 999-0002
+ cbccbc_lbdtccbc_completecbc_nximportwbc_lborreswbc_lborresuwbc_lbstat
+
+"""
self.data = etree.ElementTree(etree.fromstring(self.sortedData))
- self.serverResponse = """
-
+ self.serverResponse = """
+
- """
-
+"""
self.output = """
-
+
HEMOGLOBIN
1534435
10.5
@@ -79,8 +60,9 @@ def setUp(self):
g/dL
1
- cbccbc_lbdtccbc_completecbc_nximporthemo_lborreshemo_lborresuhemo_lbstat
-
+ cbccbc_lbdtccbc_completecbc_nximporthemo_lborreshemo_lborresuhemo_lbstat
+
+
WBC
999
5.4
@@ -88,38 +70,20 @@ def setUp(self):
- 1
- cbccbc_lbdtccbc_completecbc_nximportwbc_lborreswbc_lborresuwbc_lbstat
-
- PLATELET COUNT
- 1009
- 92
-
-
-
-
- 1
- cbccbc_lbdtccbc_completecbc_nximportplat_lborresplat_lborresuplat_lbstat
-
- HEMOGLOBIN
- 1534435
- 9.5
- 12.0
- 16.0
- g/dL
-
- 1
- cbccbc_lbdtccbc_completecbc_nximporthemo_lborreshemo_lborresuhemo_lbstat
- """
+ 2
+ cbccbc_lbdtccbc_completecbc_nximportwbc_lborreswbc_lborresuwbc_lbstat
+
+"""
self.expect = etree.tostring(etree.fromstring(self.output))
self.configuration_directory = tempfile.mkdtemp('/')
self.research_id_to_redcap_id = "research_id_to_redcap_id_map.xml"
try:
f = open(os.path.join(self.configuration_directory, self.research_id_to_redcap_id), "w+")
- f.write("""
- dm_subjid
- dm_usubjid
+ f.write("""
+
+ dm_subjid
+ dm_usubjid
""")
f.close()
except:
@@ -131,7 +95,16 @@ def dummy_redcapClient_initializer(self, redcap_uri, token, verify_ssl):
def dummy_get_data_from_redcap(self,records_to_fecth=[],events_to_fetch=[], fields_to_fetch=[], forms_to_fetch=[], return_format='xml'):
dummy_output = """
-
+ -
+
+
+
+
+ -
+
+
+
+
"""
return dummy_output
diff --git a/test/TestResume.py b/test/TestResume.py
index 491008c..df255c5 100644
--- a/test/TestResume.py
+++ b/test/TestResume.py
@@ -14,8 +14,8 @@ def delete(self):
class FileDeleted():
pass
- import bin.redi
- redi = reload(bin.redi)
+ import redi.redi
+ redi = reload(redi.redi)
redi._person_form_events_service = MockPersonFormEvents()
redi._check_input_file = lambda *args: None
@@ -24,7 +24,8 @@ class FileDeleted():
redi._run(config_file=None, configuration_directory='',
do_keep_gen_files=None, dry_run=True, get_emr_data=False,
settings=MockSettings(), data_folder=None,
- database_path=None, redcap_client=None)
+ database_path=None, redcap_client=None,
+ report_courier=None, report_creator=None)
def test_no_resume_stores(self):
class MockPersonFormEvents(object):
@@ -37,8 +38,8 @@ def store(self, ignored):
class FileStored():
pass
- import bin.redi
- redi = reload(bin.redi)
+ import redi.redi
+ redi = reload(redi.redi)
redi._person_form_events_service = MockPersonFormEvents()
redi._check_input_file = lambda *args: None
@@ -52,7 +53,8 @@ class FileStored():
redi._run(config_file=None, configuration_directory='',
do_keep_gen_files=None, dry_run=True, get_emr_data=False,
settings=MockSettings(), data_folder=None,
- database_path=None, redcap_client=None)
+ database_path=None, redcap_client=None,
+ report_courier=None, report_creator=None)
def test_resume_fetches_data_from_last_run(self):
class MockPersonFormEvents(object):
@@ -62,8 +64,8 @@ def fetch(self):
class DataFetched():
pass
- import bin.redi
- redi = reload(bin.redi)
+ import redi.redi
+ redi = reload(redi.redi)
redi._person_form_events_service = MockPersonFormEvents()
redi._check_input_file = lambda *args: None
@@ -72,7 +74,8 @@ class DataFetched():
redi._run(config_file=None, configuration_directory='',
do_keep_gen_files=None, dry_run=True, get_emr_data=False,
settings=MockSettings(), data_folder=None,
- database_path=None, resume=True, redcap_client=None)
+ database_path=None, resume=True, redcap_client=None,
+ report_courier=None, report_creator=None)
class MockSettings(object):
diff --git a/test/TestSendDatatoRedcap.py b/test/TestSendDatatoRedcap.py
index 730ed19..9cec4f7 100644
--- a/test/TestSendDatatoRedcap.py
+++ b/test/TestSendDatatoRedcap.py
@@ -9,7 +9,7 @@
import unittest
import os
from wsgiref.simple_server import make_server
-import redi
+from redi import redi
import thread
file_dir = os.path.dirname(os.path.realpath(__file__))
diff --git a/test/TestSentEventIndex.py b/test/TestSentEventIndex.py
new file mode 100644
index 0000000..df2b3e2
--- /dev/null
+++ b/test/TestSentEventIndex.py
@@ -0,0 +1,44 @@
+"""
+Verifies the functionality of bin.redi.SentEventIndex
+"""
+import unittest
+
+from redi import redi
+
+
+class TestSentEventIndex(unittest.TestCase):
+
+ def test_len(self):
+ index = redi.SentEvents("", writer=lambda o, f: None,
+ reader=lambda f: [])
+ self.assertEqual(0, len(index))
+
+ index.mark_sent("007", "new_hire", "1_arm_1")
+ index.mark_sent("007", "new_hire", "2_arm_1")
+
+ self.assertEqual(2, len(index))
+
+ def test_was_sent(self):
+ index = redi.SentEvents("", writer=lambda o, f: None,
+ reader=lambda f: [])
+
+ index.mark_sent("007", "new_hire", "1_arm_1")
+
+ self.assertTrue(index.was_sent("007", "new_hire", "1_arm_1"))
+
+ def test_mark_sent(self):
+ self.__tally = 0
+
+ index = redi.SentEvents("", self.__dummy_writer,
+ reader=lambda f: [])
+ index.mark_sent("007", "new_hire", "1_arm_1")
+ index.mark_sent("007", "new_hire", "2_arm_1")
+
+ self.assertEqual(2, self.__tally)
+
+ def __dummy_writer(self, obj, filename):
+ self.__tally += 1
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/TestSortElementTree.py b/test/TestSortElementTree.py
index cf31106..7508ea2 100644
--- a/test/TestSortElementTree.py
+++ b/test/TestSortElementTree.py
@@ -1,7 +1,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestSuite.py b/test/TestSuite.py
index eb86b04..cd296d6 100755
--- a/test/TestSuite.py
+++ b/test/TestSuite.py
@@ -38,9 +38,11 @@
from TestCopyDataToPersonFormEventTree import TestCopyDataToPersonFormEventTree
from TestGetEMRData import TestGetEMRData
from TestResume import TestResume
+from TestThrottle import TestThrottle
from TestPersonFormEventsRepository import TestPersonFormEventsRepository
from TestVerifyAndCorrectCollectionDate import TestVerifyAndCorrectCollectionDate
from TestRediEmail import TestRediEmail
+from TestSentEventIndex import TestSentEventIndex
class redi_suite(unittest.TestSuite):
@@ -68,6 +70,7 @@ def suite(self):
redi_test_suite.addTest(TestCreateEmptyEventTreeForStudy)
redi_test_suite.addTest(TestVerifyAndCorrectCollectionDate)
redi_test_suite.addTest(TestParseAll)
+ redi_test_suite.addTest(TestSentEventIndex)
# The redesign functions May 2014
redi_test_suite.addTest(TestCreateImportDataJson)
@@ -79,8 +82,10 @@ def suite(self):
redi_test_suite.addTest(TestCopyDataToPersonFormEventTree)
redi_test_suite.addTest(TestGetEMRData)
redi_test_suite.addTest(TestResume)
+ redi_test_suite.addTest(TestThrottle)
redi_test_suite.addTest(TestPersonFormEventsRepository)
redi_test_suite.addTest(TestRediEmail)
+ redi_test_suite.addTest(TestDaysSinceToday)
# return the suite
return unittest.TestSuite([redi_test_suite])
diff --git a/test/TestThrottle.py b/test/TestThrottle.py
new file mode 100644
index 0000000..a816b51
--- /dev/null
+++ b/test/TestThrottle.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+import datetime
+import unittest
+
+from redi.utils import throttle
+
+
+class TestThrottle(unittest.TestCase):
+
+ def test_throttle(self):
+ class Clock(object):
+ def __init__(self):
+ self.now = datetime.datetime.now()
+
+ def __call__(self):
+ return self.now
+
+ def add_seconds(self, seconds):
+ self.now += datetime.timedelta(seconds=seconds)
+
+ clock = Clock()
+ throttle.Throttle._now = clock
+ throttle.Throttle._sleep = clock.add_seconds
+
+ call = throttle.Throttle(lambda: None, max_calls=3,
+ interval_in_seconds=5)
+
+ call() # t=0
+ clock.add_seconds(1)
+ call() # t=1
+ clock.add_seconds(2)
+ call() # t=3
+ clock.add_seconds(1)
+ call() # t=4
+ self.assertEquals(1, len(call._timestamps))
+ clock.add_seconds(1)
+ call() # t=5
+ self.assertEquals(2, len(call._timestamps))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/TestUpdateDataFromLookup.py b/test/TestUpdateDataFromLookup.py
index 477daf9..fccef21 100644
--- a/test/TestUpdateDataFromLookup.py
+++ b/test/TestUpdateDataFromLookup.py
@@ -1,7 +1,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestUpdateEventName.py b/test/TestUpdateEventName.py
index 1114218..1f3fff5 100644
--- a/test/TestUpdateEventName.py
+++ b/test/TestUpdateEventName.py
@@ -1,7 +1,7 @@
import unittest
from lxml import etree
import os
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestUpdateFormCompletedFieldName.py b/test/TestUpdateFormCompletedFieldName.py
index a32998c..4f9af79 100644
--- a/test/TestUpdateFormCompletedFieldName.py
+++ b/test/TestUpdateFormCompletedFieldName.py
@@ -1,7 +1,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestUpdateFormDateField.py b/test/TestUpdateFormDateField.py
index 3792776..c2f6957 100644
--- a/test/TestUpdateFormDateField.py
+++ b/test/TestUpdateFormDateField.py
@@ -9,7 +9,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestUpdateFormImported.py b/test/TestUpdateFormImported.py
index 3d335ec..e97abeb 100644
--- a/test/TestUpdateFormImported.py
+++ b/test/TestUpdateFormImported.py
@@ -1,7 +1,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestUpdateRedcapFieldNameValueAndUnits.py b/test/TestUpdateRedcapFieldNameValueAndUnits.py
index 9e05a2e..a71330d 100644
--- a/test/TestUpdateRedcapFieldNameValueAndUnits.py
+++ b/test/TestUpdateRedcapFieldNameValueAndUnits.py
@@ -1,7 +1,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestUpdateRedcapForm.py b/test/TestUpdateRedcapForm.py
index e8b4642..56e7b8e 100644
--- a/test/TestUpdateRedcapForm.py
+++ b/test/TestUpdateRedcapForm.py
@@ -1,6 +1,6 @@
import unittest
from lxml import etree
-import redi
+from redi import redi
class TestUpdateRedcapForm(unittest.TestCase):
diff --git a/test/TestUpdateStatusField.py b/test/TestUpdateStatusField.py
index cab6a94..0e0a010 100644
--- a/test/TestUpdateStatusField.py
+++ b/test/TestUpdateStatusField.py
@@ -1,18 +1,18 @@
import unittest
from lxml import etree
import os
-import redi
-
-file_dir = os.path.dirname(os.path.realpath(__file__))
-goal_dir = os.path.join(file_dir, "../")
-proj_root = os.path.abspath(goal_dir)+'/'
+from redi import redi
DEFAULT_DATA_DIRECTORY = os.getcwd()
class TestUpdateStatusField(unittest.TestCase):
- def test_update_status_field_value_when_one_subject_with_two_forms_with_one_event_in_each_form(self):
+ def setUp(self):
redi.configure_logging(DEFAULT_DATA_DIRECTORY)
+
+
+ def test_update_status_field_value_when_one_subject_with_two_forms_with_one_event_in_each_form(self):
+
self.source = """
99
@@ -153,7 +153,6 @@ def test_update_status_field_value_when_one_subject_with_two_forms_with_one_even
self.assertEqual(self.expect, result)
def test_update_status_field_value_when_one_subject_with_two_forms_with_two_events_in_each_form(self):
- redi.configure_logging(proj_root+'log/redi.log')
self.source = """
99
@@ -370,7 +369,6 @@ def test_update_status_field_value_when_one_subject_with_two_forms_with_two_even
self.assertEqual(self.expect, result)
def test_update_status_field_value_when_two_subjects_with_two_forms_with_one_event_in_each_form(self):
- redi.configure_logging(proj_root+'log/redi.log')
self.source = """
99
@@ -609,7 +607,6 @@ def test_update_status_field_value_when_two_subjects_with_two_forms_with_one_eve
self.assertEqual(self.expect, result)
def test_update_status_field_value_when_one_subject_with_no_form(self):
- redi.configure_logging(proj_root+'log/redi.log')
self.source = """
99
@@ -662,7 +659,6 @@ def test_update_status_field_value_when_one_subject_with_no_form(self):
self.assertEqual(self.expect, result)
def test_update_status_field_value_when_one_subject_with_two_forms_event_missing_in_one_of_the_forms(self):
- redi.configure_logging(proj_root+'log/redi.log')
self.source = """
99
@@ -757,7 +753,6 @@ def test_update_status_field_value_when_one_subject_with_two_forms_event_missing
self.assertEqual(self.expect, result)
def test_update_status_field_value_when_one_subject_with_one_form_one_event_value_tag_missing(self):
- redi.configure_logging(proj_root+'log/redi.log')
self.source = """
99
diff --git a/test/TestUpdateTimestamp.py b/test/TestUpdateTimestamp.py
index d68c44d..aa0ecfd 100644
--- a/test/TestUpdateTimestamp.py
+++ b/test/TestUpdateTimestamp.py
@@ -12,7 +12,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestValidateXmlFleAndExtractData.py b/test/TestValidateXmlFleAndExtractData.py
index d50b161..a9d6aea 100644
--- a/test/TestValidateXmlFleAndExtractData.py
+++ b/test/TestValidateXmlFleAndExtractData.py
@@ -8,7 +8,7 @@
import unittest
import os
from lxml import etree
-import redi
+from redi import redi
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
diff --git a/test/TestVerifyAndCorrectCollectionDate.py b/test/TestVerifyAndCorrectCollectionDate.py
index c7b7777..1ff67b9 100644
--- a/test/TestVerifyAndCorrectCollectionDate.py
+++ b/test/TestVerifyAndCorrectCollectionDate.py
@@ -11,9 +11,9 @@
file_dir = os.path.dirname(os.path.realpath(__file__))
goal_dir = os.path.join(file_dir, "../")
proj_root = os.path.abspath(goal_dir)+'/'
-sys.path.append(proj_root + 'bin/')
+sys.path.append(proj_root + 'redi/')
from lxml import etree
-import redi
+from redi import redi
DEFAULT_DATA_DIRECTORY = os.getcwd()
diff --git a/test/TestWriteToFile.py b/test/TestWriteToFile.py
index 32d5a74..57fd929 100755
--- a/test/TestWriteToFile.py
+++ b/test/TestWriteToFile.py
@@ -1,23 +1,13 @@
-'''
-@author : Radha
-email : rkandula@ufl.edu
-
-This file is to test the function writeElementTreetoFile of bin/redi.py
-This file should be run from the project level folder (one level up from /bin)
-
-'''
import unittest
import os
from lxml import etree
-import redi
-
-file_dir = os.path.dirname(os.path.realpath(__file__))
-goal_dir = os.path.join(file_dir, "../")
-proj_root = os.path.abspath(goal_dir)+'/'
+from redi import redi
DEFAULT_DATA_DIRECTORY = os.getcwd()
class TestWriteToFile(unittest.TestCase):
+
+ """ Variables setup """
def setUp(self):
redi.configure_logging(DEFAULT_DATA_DIRECTORY)
self.test_raw_xml = """
@@ -36,32 +26,16 @@ def setUp(self):
5.0
3.9
-
- 001-0001
- 09/11/18
- 04/18/19
- 11:57
- Y
- 04/14/20
- ALKALINE PHOSPHATASE
- 1525848
- U/L
- 35
- 129
- 112
-
"""
- ''' '''
-
- # this is a function to test the writeElementTreetoFile function.
- # we called it with input file and tried to write the element tree to an XML file
- def testWriteElementTreetoFile(self):
- import xml.etree.ElementTree as ET
+ def test_write_element_tree_to_file(self):
+ """ Test the correctness of function
+ redi.write_element_tree_to_file()
+ """
tree = etree.ElementTree(etree.fromstring(self.test_raw_xml))
root = tree.getroot()
- redi.write_element_tree_to_file(tree,'testWriteFile.xml')
+ redi.write_element_tree_to_file(tree, 'testWriteFile.xml')
assert os.path.exists('testWriteFile.xml') == 1
os.remove('testWriteFile.xml')
diff --git a/vagrant/Makefile b/vagrant/Makefile
index d892098..5747bb3 100644
--- a/vagrant/Makefile
+++ b/vagrant/Makefile
@@ -19,7 +19,7 @@ ifneq ("$(wildcard $(MAKE_CONFIG_FILE))", "")
REDCAP_API_URI := $(shell cat ${CONFIG_FILE} | sed -e 's/ //g' | grep -v '^\#' | grep 'redcap_uri=' | cut -d '=' -f2)
REDCAP_VM_URI := $(subst api/,,$(REDCAP_API_URI))
REDCAP_VM_TOKEN := $(shell cat ${CONFIG_FILE} | sed -e 's/ //g' | grep -v '^\#' | grep 'token=' | cut -d '=' -f2)
- REDCAP_RECORDS_CMD:=../bin/utils/redcap_records.py --token=$(REDCAP_VM_TOKEN) --url=$(REDCAP_API_URI)
+ REDCAP_RECORDS_CMD:=../redi/utils/redcap_records.py --token=$(REDCAP_VM_TOKEN) --url=$(REDCAP_API_URI)
REDCAP_PROJECT_ID := $(shell cat ${MAKE_CONFIG_FILE} | sed -e 's/ //g' | grep -v '^\#' | grep 'redcap_project_id=' | cut -d '=' -f2)
REDCAP_PROJECT_FORMS := $(shell cat ${MAKE_CONFIG_FILE} | sed -e 's/ //g' | grep -v '^\#' | grep 'redcap_project_forms=' | cut -d '=' -f2)
REDCAP_PROJECT_ENROLLMENT_FORM := $(shell cat ${MAKE_CONFIG_FILE} | sed -e 's/ //g' | grep -v '^\#' | grep 'redcap_project_enrollment_form=' | cut -d '=' -f2)
@@ -114,7 +114,7 @@ rc_enrollment: check_config
$(REDCAP_RECORDS_CMD) -i $(ENROLLMENT_CSV_FILE)
rc_post:
- python ../bin/redi.py -c $(CONFIG_FOLDER)
+ python ../redi/redi.py -c $(CONFIG_FOLDER)
rc_get: check_config
$(REDCAP_RECORDS_CMD) -f "$(REDCAP_PROJECT_FORMS)"
@@ -125,6 +125,9 @@ rc_get_json: check_config
rc_get_enrollment: check_config
$(REDCAP_RECORDS_CMD) -f "$(REDCAP_PROJECT_ENROLLMENT_FORM)" -t csv
+rc_get_enrollment_meta:
+ @curl -X POST http://localhost:8998/redcap/api/ -d token=$(REDCAP_VM_TOKEN) -d content=metadata -d format=csv -d forms[]=enrollment
+
rc_fresh:
make copy_project_data
make rc_clean
diff --git a/vagrant/README.md b/vagrant/README.md
deleted file mode 100644
index 54fec6b..0000000
--- a/vagrant/README.md
+++ /dev/null
@@ -1,105 +0,0 @@
-# Testing RED-I with a sample REDCap Project
-
-## Purpose
-
-The "vagrant" folder was created with the goal of making testing [RED-I software](https://github.com/ctsit/redi) as easy as possible.
-It contains the [Vagrantfile](../vagrant/Vagrantfile) which allows to start a virtual machine capable of running the
-[REDCap software](http://http://www.project-redcap.org) -- which means that during virtual machine creation the Apache and MySQL
-software is installed without any user intervention.
-
-There are a few important things to note before proceeding with running RED-I to import data into a sample REDCap project:
-
-- You have to install the **vagrant** and **virtual box** software
-- You have to obtain the closed-source REDCap software from http://project-redcap.org/
-- You have to obtain a **Makefile.ini** file in order to be able to execute tasks from the **Makefile**
-
-## Steps
-
-### 1. Install vagrant and virtual box
-
-On a linux machine run:
-
-* sudo apt-get install vagrant
-* sudo apt-get install virtualbox
-
-
-On a mac machine:
-
-* Download and install vagrant from https://www.vagrantup.com/downloads.html
-* Download and install the latest virtual box from http://download.virtualbox.org/virtualbox/
-
-For more details about Vagrant software you can go to [why-vagrant](https://docs.vagrantup.com/v2/why-vagrant/) page.
-
-
-### 2. Configure the VM
-
-As mentioned above you have to obtain a copy of the REDCap software from http://project-redcap.org/
-and save it as "**redcap.zip**" file in the "**config-example/vagrant-data**" folder.
-This ensures that in the later steps the [bootstrap.sh](../vagrant/bootstrap.sh) script can extract the files to the
-virtual machine path "**/var/www/redcap**".
-
-Now execute the following commands to complete the configuration:
-
-
-make copy_config_example
-make copy_redcap_code
-make copy_project_data
-make show_config
-
-
-Please verify that the output from "show_config" matheches your expectations.
-
-### 3. Start the VM
-
-To use the vagrant VM you will need to install Vagrant and Virtual Box.
-
-With these packages installed, follow this procedure to use a VM template:
-
- cd ./vagrant
- vagrant up
-
-Vagrant will instantiate and provision the new VM. The REDCap web application should be accessible in the browser at
-
- http://localhost:8998/redcap/
-
-If port 8998 is already in use vagrant will choose a different port automatically.
-Read the log of "vagrant up" and note the port to be used.
-
-### 4. Verify the VM is running
-
-Verify that the virtual machine is working properly by accessing it using:
-
-
-vagrant ssh
-
-
-### 5. Import Enrollment Data using RED-I
-
-Import the [sample subject list](../config-example/vagrant-data/enrollment_test_data.csv) into REDCap by executing:
-
-
-make rc_enrollment
-
-
-Note: This step is necessary because in order to associate data with subjects the list of subjects needs to exist in the REDCap database.
-
-
-### 6. Import Electronic Health Records using RED-I
-
-Import the [sample electronic health records](../config-example/vagrant-data/redi_sample_project_v5.7.4.sql) into REDCap by executing:
-
-
-make rc_post
-
-
-Verify that the output of this command ends with:
-
-You can review the summary report by opening: report.html in your browser
-
-
-If this step succeded you have verified that RED-I can be used to save time by automating EHR data imports into REDCap.
-
-
-Congratulations! You can now [add your own REDCap project](../doc/add_new_redcap_project.md)
-and start using RED-I to move data.
-
diff --git a/vagrant/aliases b/vagrant/aliases
index d7a53d6..1339306 100644
--- a/vagrant/aliases
+++ b/vagrant/aliases
@@ -1,4 +1,45 @@
alias db='mysql --prompt="(\u@\h) [\d]> " --pager="less -niSFX" -uroot -ppassword redcap'
alias check_redcap="curl -s http://localhost/redcap/ | grep -i 'Welcome\|Critical Error' "
alias restart_httpd='sudo /etc/init.d/apache2 reload '
-alias lsa='ls -al --color=auto'
+alias restart_mysql='sudo service mysql restart'
+
+alias cdapi='cd /var/www/redcap/redcap_v5.7.4/API && pwd && ls -al'
+alias ls='ls --color=auto'
+alias lsa='ls -al'
+alias lss='ls -ltr'
+alias dua='du -hcs'
+alias cdd='cd ..'
+
+alias gst='git status'
+alias glog='git log'
+alias gdiff='git diff'
+alias gdif='git diff --cached'
+alias gb='git branch'
+alias gp='git remote -v'
+alias gf='git show --name-status'
+
+alias gan='git blame'
+alias gin='git fetch && git log ..origin/master'
+alias gout='git fetch && git log origin/master..'
+
+alias grepp="nice ack-grep --php --python -i"
+
+function venv() {
+ CMD="X$1"
+
+ if [ "$CMD" = "Xup" ]; then
+ . venv/bin/activate
+ elif [ "$CMD" = "Xdown" ]; then
+ deactivate
+ elif [ "$CMD" = "Xrestart" ]; then
+ rm -rf venv
+ virtualenv venv
+ . venv/bin/activate
+ which redi
+ elif [ "$CMD" = "Xdestroy" ]; then
+ deactivate
+ rm -rf venv
+ else
+ echo "Supported commands: venv [up | down | restart | destroy]"
+ fi
+}
diff --git a/vagrant/downloading_redcap_code.rst b/vagrant/downloading_redcap_code.rst
new file mode 100644
index 0000000..2ba2414
--- /dev/null
+++ b/vagrant/downloading_redcap_code.rst
@@ -0,0 +1,17 @@
+Downloading REDCap Source Code
+==============================
+
+REDCap code is available only through Vanderbilt University.
+
+Possession and use of REDCap code and workflow methodology is strictly
+limited to institutions and organizations who have finalized an End-User
+License Agreement with Vanderbilt University.
+
+https://redcap.vanderbilt.edu/consortium/
+
+Long Term Support URLs
+----------------------
+
+* https://iwg.devguard.com/trac/redcap/browser/zips_redcap/6.0.5/redcap6.0.5.zip
+* https://iwg.devguard.com/trac/redcap/browser/zips_redcap/6.0.5/redcap6.0.5_upgrade.zip
+
|