Skip to content
Snippets Groups Projects
Commit 0fa2b659 authored by kaloyane's avatar kaloyane
Browse files

[LMS-2332] not fully functional version of the new Sanofi dropbox. 'sanofi' project created.

SVN: 21895
parent 33dbebe3
No related branches found
No related tags found
No related merge requests found
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="source/java"/>
<classpathentry kind="src" path="sourceTest/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry combineaccessrules="false" kind="src" path="/screening"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
<classpathentry kind="lib" path="/libraries/testng/testng-jdk15.jar" sourcepath="/libraries/testng/src.zip"/>
<classpathentry kind="lib" path="/libraries/cisd-base/cisd-base-test.jar" sourcepath="/libraries/cisd-base/cisd-base-src.zip"/>
<classpathentry kind="lib" path="/libraries/cisd-base/cisd-base.jar" sourcepath="/libraries/cisd-base/cisd-base-src.zip"/>
<classpathentry kind="lib" path="/libraries/jmock/hamcrest/hamcrest-core.jar"/>
<classpathentry kind="lib" path="/libraries/jmock/hamcrest/hamcrest-library.jar"/>
<classpathentry kind="lib" path="/libraries/jmock/objenesis/objenesis-1.0.jar"/>
<classpathentry kind="lib" path="/libraries/jmock/jmock.jar"/>
<classpathentry kind="lib" path="/libraries/log4j/log4j.jar" sourcepath="/libraries/log4j/src.zip"/>
<classpathentry kind="lib" path="/libraries/mail/mail.jar"/>
<classpathentry kind="lib" path="/libraries/activation/activation.jar"/>
<classpathentry kind="lib" path="/libraries/jaxb/jaxb-api.jar" sourcepath="/libraries/jaxb/jaxb-api-src.zip"/>
<classpathentry kind="lib" path="/libraries/jaxb/jsr173_1.0_api.jar"/>
<classpathentry kind="lib" path="/libraries/gwt2.0/gwt-isserializable.jar"/>
<classpathentry kind="lib" path="/libraries/spring/spring.jar" sourcepath="/libraries/spring/src.jar"/>
<classpathentry kind="lib" path="/libraries/cisd-args4j/cisd-args4j.jar" sourcepath="/libraries/cisd-args4j/cisd-args4j-src.zip"/>
<classpathentry kind="lib" path="/libraries/poi/poi-3.7-20101029.jar"/>
<classpathentry kind="lib" path="/libraries/jython/standalone/jython.jar" sourcepath="/Users/gpawel/Downloads/jython_installer-2.2.1.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/datastore_server"/>
<classpathentry combineaccessrules="false" kind="src" path="/openbis"/>
<classpathentry kind="lib" path="/libraries/eodsql/eodsql.jar" sourcepath="/libraries/eodsql/eodsql_src.zip"/>
<classpathentry kind="output" path="targets/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>sanofi</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.python.pydev.PyDevBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.python.pydev.pythonNature</nature>
</natures>
</projectDescription>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?>
<pydev_project>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
</pydev_project>
#! /bin/bash
ME="$0"
MYDIR=${ME%/*}
cd $MYDIR
ant -lib ../../build_resources/lib/ecj.jar "$@"
<project name="sanofi" default="ci" basedir="..">
<import file="../../datastore_server/build/build.xml" />
<project-classpath name="ecp" classes="${classes}" />
<property name="original.dist" value="dist" />
<property name="mainfolder" value="sanofi" />
<target name="compile" depends="build-common.compile, clean" />
<target name="run-tests">
<antcall target="build-common.run-tests">
<param name="test.suite" value="tests.xml" />
</antcall>
</target>
<!--
No jar files produced by the module yet.
<target name="jar" depends="compile">
<mkdir dir="${dist}" />
<build-info revision="revision.number" version="version.number" clean="clean.flag" />
<echo file="${build.info.file}">${version.number}:${revision.number}:${clean.flag}</echo>
<jar destfile="${plugin-jar.file}">
<fileset dir="${classes}">
<include name="ch/**/*.class" />
<include name="${build.info.filename}" />
</fileset>
<fileset dir="source">
<include name="**/*.sql" />
</fileset>
<manifest>
<attribute name="Version" value="${version.number}" />
<attribute name="Build-Number"
value="${version.number} (r${revision.number},${clean.flag})" />
</manifest>
</jar>
</target>
-->
<!--
// Task for creating distributions
<target name="dist" depends="jar" />
-->
<!--
// Task for continuous integration server.
-->
<target name="ci" depends="build-common.ci, check-dependencies" />
</project>
\ No newline at end of file
import ch.systemsx.cisd.etlserver.registrator.api.v1.MaterialIdentifierCollection as MaterialIdentifierCollection
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria as SearchCriteria
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria.MatchClause as MatchClause
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria.MatchClauseAttribute as MatchClauseAttribute
PLATE_TYPE = "PLATE"
DATA_SET_TYPE = "HCS_IMAGE_RAW"
DATA_SET_BATCH_PROPNAME = "ACQUISITION_BATCH"
def findPlateByCode(code):
"""
Finds a plate (openBIS sample) matching a specified bar code.
"""
criteria = SearchCriteria()
criteria.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.TYPE, PLATE_TYPE))
criteria.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE, code))
searchService = transaction.getSearchService()
platesFound = list(searchService.searchForSamples(criteria))
if len(platesFound) > 0:
return platesFound[0]
else:
return None
def parseIncomingDirname(dirName):
"""
Parses the name of an incoming dataset folder from the format
'AcquisitionBatch_BarCode_Timestamp' to a tuple (acquisitionBatch, barCode)
"""
tokens = dirName.split("_")
assert len(tokens) >= 2, "Data set directory name does not match the pattern 'AcquisitionBatch_BarCode_Timestamp': " + dirName
acquisitionBatch = tokens[0]
barCode = tokens[1].split('.')[0]
return (acquisitionBatch, barCode)
def removeDuplicates(list):
dict = {}
for item in list:
dict[item] = item
return dict.keys()
class SanofiMaterial:
"""
A data structure class holding compound materials as they exist in the Abase (Sanofi) database.
"""
def __init__(self, wellCode, materialCode, sanofiId, sanofiBatchId):
self.wellCode = wellCode
self.materialCode = materialCode
self.sanofiId = sanofiId
self.sanofiBatchId = sanofiBatchId
class PlateInitializer:
ABASE_DATA_SOURCE = "abase-datasource"
ABASE_QUERY = "TODO: this a query provided by Matt"
LIBRARY_TEMPLATE_PROPNAME = "LIBRARY_TEMPLATE"
WELL_TYPE = "COMPOUND_WELL"
WELL_CONCENTRATION_PROPNAME = "CONCENTRATION"
WELL_MATERIAL_PROPNAME = "COMPOUND_BATCH"
MATERIAL_TYPE = "COMPOUND_BATCH"
MATERIAL_ID_PROPNAME = "COMPOUND_ID"
MATERIAL_BATCH_ID_PROPNAME = "COMPOUND_BATCH_ID"
def __init__(self, transaction):
self.transaction = transaction
self.plate = plate
def getWellCode(self, x, y):
return chr(ord('A') + x) + str(y)
def getPlateDimensions(self):
# TODO KE: implement me
return (2, 2)
def validateLibraryDimensions(self, csvLists):
(plateWidth, plateHeight) = self.getPlateDimensions()
assert plateHeight == len(csvLists), \
"Plate geometry (height=%i) does not agree with LIBRARY_TEMPLATE (height=%i)." % (plateHeight, len(csvLists))
for i in range(0, len(csvLists)):
assert plateWidth == len(csvLists[i]), \
"Plate geometry (width=%i) does not agree with LIBRARY_TEMPLATE (line=%i,width=%i)." % (plateWidth, i, len(csvLists[i]))
def parseLibraryTemplate(self):
experimentId = plate.getExperiment().getExperimentIdentifier()
experiment = transaction.getExperiment(experimentId)
template = experiment.getPropertyValue(self.LIBRARY_TEMPLATE_PROPNAME)
csvLists = [ line.split(",") for line in template.splitlines() ]
self.validateLibraryDimensions(csvLists)
library = {}
for x in range(0, len(csvLists)):
for y in range(0, len(csvLists[0])):
wellCode = self.getWellCode(x,y)
library[wellCode] = csvLists[x][y].strip()
return library
def fetchPlateCompounds(self):
"""
Fetch well metadata from the Abase database.
@return: a list of tuples (one per well) in the form
(wellCode, openBisCompoundCode, abaseCompoundBatchId, abaseCompoundId).
In case the plate is not found in Abase return None.
"""
queryService = service.getDataSourceQueryService()
queryResult = queryService.select(self.ABASE_DATA_SOURCE, self.ABASE_QUERY, [plate.code])
sanofiMaterials = []
for materialMap in list(queryResult):
wellCode = str(materialMap['WELLCODE'])
materialCode = str(materialMap['MATERIALCODE'])
sanofiId = str(materialMap['ABASE_COMPOUND_ID'])
sanofiBatchId = str(materialMap['ABASE_COMPOUND_BATCH_ID'])
material = SanofiMaterial(wellCode, materialCode, sanofiId, sanofiBatchId)
sanofiMaterials.append(material)
queryResult.close()
return sanofiMaterials
def createMaterial(self, sanofiMaterial):
material = transaction.createNewMaterial(sanofiMaterial.materialCode, self.MATERIAL_TYPE)
material.setPropertyValue(self.MATERIAL_ID_PROPNAME, sanofiMaterial.sanofiId)
material.setPropertyValue(self.MATERIAL_BATCH_ID_PROPNAME, sanofiMaterial.sanofiBatchId)
return material
def getOrCreateMaterials(self, library, sanofiMaterials):
materialCodes = [sanofiMaterial.materialCode for sanofiMaterial in sanofiMaterials]
materialCodes = removeDuplicates(materialCodes)
materialIdentifiers = MaterialIdentifierCollection()
for materialCode in materialCodes:
materialIdentifiers.addIdentifier(self.MATERIAL_TYPE, materialCode)
searchService = transaction.getSearchService()
preExistingMaterials = list(searchService.listMaterials(materialIdentifiers))
materialsByCode = {}
for material in preExistingMaterials:
materialsByCode[ material.getCode() ] = material
for materialCode in materialCodes:
if not materialCode in materialsByCode:
sanofiMaterial = self.getByMaterialCode(materialCode, sanofiMaterials)
openbisMaterial = self.createMaterial(sanofiMaterial)
materialsByCode[materialCode] = openbisMaterial
return materialsByCode
def getByMaterialCode(self, materialCode, sanofiMaterials):
for sanofiMaterial in sanofiMaterials:
if materialCode == sanofiMaterial.materialCode:
return sanofiMaterial
raise RuntimeError("No material found for materialCode " + wellCode)
def getByWellCode(self, wellCode, sanofiMaterials):
for sanofiMaterial in sanofiMaterials:
if wellCode == sanofiMaterial.wellCode:
return sanofiMaterial
raise RuntimeError("No material found for wellCode " + wellCode)
def createWells(self, library, sanofiMaterials, openbisMaterials):
controlWellTypes = { "H" : "posti", "L" : "negative"};
for wellCode in library:
libraryValue = library[wellCode].uppercase()
if libraryValue in controlWellTypes:
pass
else:
try
except ValueError:
raise RuntimeError("TODO...")
well = transaction.createNewSample(plate.code +":" + wellCode, self.WELL_TYPE)
well.setContainer(plate)
concentration = library[wellCode]
well.setPropertyValue(self.WELL_CONCENTRATION_PROPNAME, concentration)
materialCode = self.getByWellCode(wellCode, sanofiMaterials).materialCode
material = openbisMaterials[materialCode]
well.setMaterialPropertyValue(self.WELL_MATERIAL_PROPNAME, material)
def createWellsAndMaterials(self):
library = self.parseLibraryTemplate()
sanofiMaterials = self.fetchPlateCompounds()
# TODO KE: validate that library and sanofiMaterials data agrees
openbisMaterials = self.getOrCreateMaterials(library, sanofiMaterials)
self.createWells(library, sanofiMaterials, openbisMaterials)
transaction = service.transaction()
dataSet = transaction.createNewDataSet(DATA_SET_TYPE)
(batchName, barCode) = parseIncomingDirname(incoming.getName())
dataSet.setPropertyValue(DATA_SET_BATCH_PROPNAME, batchName)
plate = findPlateByCode(barCode)
if len(plate.getContainedSamples()) == 0:
plateInitializer = PlateInitializer(plate)
plateInitializer.createWellsAndMaterials()
dataSet.setSample(plate)
transaction.moveFile(incoming.getAbsolutePath(), dataSet)
# TODO KE: send emails on ERROR/SUCCESS
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%t] %c - %m%n"/>
</layout>
</appender>
<appender name="NULL" class="org.apache.log4j.varia.NullAppender" />
<root>
<priority value ="info" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
File moved
/*
* Copyright 2011 ETH Zuerich, CISD
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.systemsx.cisd.sanofi.dss.test;
import static ch.systemsx.cisd.common.Constants.IS_FINISHED_PREFIX;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.jmock.Expectations;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import ch.systemsx.cisd.common.eodsql.MockDataSet;
import ch.systemsx.cisd.common.filesystem.FileUtilities;
import ch.systemsx.cisd.common.test.RecordingMatcher;
import ch.systemsx.cisd.etlserver.registrator.AbstractJythonDataSetHandlerTest;
import ch.systemsx.cisd.etlserver.registrator.api.v1.IDataSourceQueryService;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria.MatchClause;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria.MatchClauseAttribute;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListMaterialCriteria;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.ExperimentBuilder;
import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.SampleBuilder;
import ch.systemsx.cisd.openbis.generic.shared.dto.AtomicEntityOperationResult;
import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory;
import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFactory;
/**
* <pre>
* Things not tested
* - skip well creation when plate library already exists
* - skip material creation for preexisting materials
* - error cases
* </pre>
*
* @author Kaloyan Enimanev
*/
public class SanofiDropboxJythonTest extends AbstractJythonDataSetHandlerTest
{
private static final String PLATE_CODE = "plateCode";
private static final String LIBRARY_TEMPLATE_PROPNAME = "LIBRARY_TEMPLATE";
private static final String MATERIAL_TYPE = "COMPOUND_BATCH";
private static final String DATASET_DIR_NAME = "batchNr_plateCode.variant_2011.06.28";
private static final String DATA_SET_CODE = "data-set-code";
private static final DataSetType DATA_SET_TYPE = new DataSetType("DATA_SET_TYPE");
private static final String EXPERIMENT_IDENTIFIER = "/SANOFI/PROJECT/EXP";
private static final String PLATE_IDENTIFIER = "/SANOFI/TEST-PLATE";
private IDataSourceQueryService queryService;
@BeforeMethod
@Override
public void setUp() throws IOException
{
super.setUp();
queryService = context.mock(IDataSourceQueryService.class);
}
@Test(enabled = false)
public void testSimpleTransaction() throws IOException
{
setUpHomeDataBaseExpectations();
Properties properties = createThreadPropertiesRelativeToScriptsFolder("sanofi-dropbox.py");
createHandler(properties, false, true, queryService);
createData();
final String libraryTemplate = "1.45, H\n0.12, L";
final Sample plate = createPlate(libraryTemplate);
setUpPlateSearchExpectations(plate);
setUpLibraryTemplateExpectations(plate);
final MockDataSet<Map<String, Object>> queryResult = new MockDataSet<Map<String, Object>>();
queryResult.add(createQueryResult("A0"));
queryResult.add(createQueryResult("B0"));
final RecordingMatcher<ch.systemsx.cisd.openbis.generic.shared.dto.AtomicEntityOperationDetails> atomicatOperationDetails =
new RecordingMatcher<ch.systemsx.cisd.openbis.generic.shared.dto.AtomicEntityOperationDetails>();
final RecordingMatcher<ListMaterialCriteria> materialCriteria =
new RecordingMatcher<ListMaterialCriteria>();
context.checking(new Expectations()
{
{
one(queryService).select(with(any(String.class)), with(any(String.class)),
with(anything()));
will(returnValue(queryResult));
one(openBisService).listMaterials(with(materialCriteria), with(equal(true)));
will(returnValue(Collections.emptyList()));
one(openBisService).createPermId();
will(returnValue("A0-permId"));
one(openBisService).createPermId();
will(returnValue("B0-permId"));
one(openBisService).createDataSetCode();
will(returnValue(DATA_SET_CODE));
one(dataSetValidator).assertValidDataSet(DATA_SET_TYPE,
new File(new File(stagingDirectory, DATA_SET_CODE), DATASET_DIR_NAME));
SampleIdentifier sampleIdentifier =
SampleIdentifierFactory.parse(plate.getIdentifier());
one(openBisService).tryGetSampleWithExperiment(sampleIdentifier);
will(returnValue(plate));
one(openBisService).getPropertiesOfTopSampleRegisteredFor(sampleIdentifier);
will(returnValue(new IEntityProperty[0]));
one(openBisService).performEntityOperations(with(atomicatOperationDetails));
will(returnValue(new AtomicEntityOperationResult()));
}
});
handler.handle(markerFile);
assertEquals(MATERIAL_TYPE, materialCriteria.recordedObject().tryGetMaterialType());
assertEquals(true, queryResult.hasCloseBeenInvoked());
assertEquals(1, atomicatOperationDetails.recordedObject().getDataSetRegistrations().size());
NewExternalData dataSet =
atomicatOperationDetails.recordedObject().getDataSetRegistrations().get(0);
assertEquals(DATA_SET_CODE, dataSet.getCode());
assertEquals(DATA_SET_TYPE, dataSet.getDataSetType());
context.assertIsSatisfied();
}
private void setUpPlateSearchExpectations(final Sample plate)
{
context.checking(new Expectations()
{
{
SearchCriteria sc = new SearchCriteria();
sc.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.TYPE,
"PLATE"));
sc.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE,
PLATE_CODE));
oneOf(openBisService).searchForSamples(sc);
will(returnValue(Arrays.asList(plate)));
}
});
}
private void setUpLibraryTemplateExpectations(final Sample plate)
{
context.checking(new Expectations()
{
{
final String identifierString = plate.getExperiment().getIdentifier();
ExperimentIdentifier identifier =
ExperimentIdentifierFactory.parse(identifierString);
oneOf(openBisService).tryToGetExperiment(identifier);
will(returnValue(plate.getExperiment()));
}
});
}
private void createData() throws IOException
{
File dataDirectory = new File("./sourceTest/examples/" + DATASET_DIR_NAME);
FileUtils.copyDirectoryToDirectory(dataDirectory, workingDirectory);
incomingDataSetFile = new File(workingDirectory, dataDirectory.getName());
markerFile = new File(workingDirectory, IS_FINISHED_PREFIX + dataDirectory.getName());
FileUtilities.writeToFile(markerFile, "");
}
private Sample createPlate(String libraryTemplate)
{
ExperimentBuilder experimentBuilder =
new ExperimentBuilder().identifier(EXPERIMENT_IDENTIFIER).property(
LIBRARY_TEMPLATE_PROPNAME,
libraryTemplate);
SampleBuilder sampleBuilder =
new SampleBuilder().identifier(PLATE_IDENTIFIER).experiment(
experimentBuilder.getExperiment());
final Sample plate = sampleBuilder.getSample();
return plate;
}
private Map<String, Object> createQueryResult(String wellCode)
{
Map<String, Object> result = new HashMap<String, Object>();
result.put("WELL_CODE", wellCode);
result.put("MATERIAL_CODE", wellCode + "_material_code");
result.put("ABASE_COMPOUND_ID", wellCode + "_compound_id");
result.put("ABASE_COMPOUND_BATCH_ID", wellCode + "_compound_batch_id");
return result;
}
@Override
protected String getRegistrationScriptsFolderPath()
{
return "dist/etc/sanofi-dropbox/";
}
}
\ No newline at end of file
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
<suite name="All" verbose="1">
<test name="All">
<groups>
<run>
<exclude name="broken" />
</run>
</groups>
<packages>
<package name="eu.basynthec.cisd.dss.*" />
</packages>
</test>
</suite>
import unittest
class DropboxUnitTests(unittest.TestCase):
def test_parse_incoming_dir_name(self):
self.assertEqual(("batchName", "barCode"), parse_incoming_dirname("batchName barCode 2011-06-28"))
self.assertEqual(("batchName", "barCode"), parse_incoming_dirname("batchName barCode 2011-06-28"))
def test_parse_incoming_dir_name(self):
self.assertEqual(("batchName", "barCode"), parse_incoming_dirname("batchName barCode 2011-06-28"))
self.assertEqual(("batchName", "barCode"), parse_incoming_dirname("batchName barCode 2011-06-28"))
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment