Exemple #1
0
 def test_list_wrapping(self):
     # Ensure that at least certain properties handle automatic list
     # wrapping and are typed to do so.
     # See https://github.com/SynBioDex/pySBOL3/issues/301
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     source_uri = 'https://example.org/source'
     derived_from_uri = 'https://example.org/derived_from'
     statute_mile = sbol3.OM_NS + 'mile-Statute'
     comp1_type = sbol3.SBO_DNA
     comp1_role = sbol3.SO_PROMOTER
     comp1_seq1 = sbol3.Sequence('seq1')
     comp1_model = sbol3.Model('model1',
                               source=source_uri,
                               language='https://example.org/language',
                               framework='https://example.org/framework')
     comp1_attachment = sbol3.Attachment('att1', source=source_uri)
     comp1_measure = sbol3.Measure(value=26.2, unit=statute_mile)
     comp1_activity = sbol3.Activity('activity1')
     comp1 = sbol3.Component('comp1',
                             types=comp1_type,
                             sequences=comp1_seq1,
                             roles=comp1_role,
                             models=comp1_model,
                             attachments=comp1_attachment,
                             derived_from=derived_from_uri,
                             measures=comp1_measure,
                             generated_by=comp1_activity)
     self.assertEqual([comp1_type], comp1.types)
     self.assertEqual([comp1_seq1.identity], comp1.sequences)
     self.assertEqual([comp1_role], comp1.roles)
     self.assertEqual([comp1_model.identity], comp1.models)
     self.assertEqual([comp1_attachment.identity], comp1.attachments)
     self.assertEqual([derived_from_uri], comp1.derived_from)
     self.assertEqual([comp1_measure], comp1.measures)
     self.assertEqual([comp1_activity.identity], comp1.generated_by)
Exemple #2
0
 def test_cloning(self):
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     c1 = sbol3.Component('c1', sbol3.SBO_DNA)
     new_identity = 'c2'
     c2 = c1.clone(new_identity)
     self.assertEqual(posixpath.join(sbol3.get_namespace(), new_identity),
                      c2.identity)
Exemple #3
0
    def test_timed_small_protocol(self):
        #############################################
        # set up the document
        print('Setting up document')
        doc = sbol3.Document()
        sbol3.set_namespace('https://bbn.com/scratch/')

        #############################################
        # Create the Protocol
        print('Creating Protocol')
        protocol = paml.Protocol('test_protocol')

        # Protocol starts at time zero
        start = pamlt.startTime(protocol, 0, units=tyto.OM.hour)

        # Protocol lasts 10 - 15 hours
        duration = pamlt.duration(protocol, [10, 15], units=tyto.OM.hour)

        time_constraints = pamlt.TimeConstraints("small_protocol_constraints",
                                                 constraints=pamlt.And(
                                                     [start, duration]),
                                                 protocols=[protocol])
        doc.add(protocol)
        doc.add(time_constraints)

        ########################################
        # Validate and write the document
        print('Validating and writing time')
        v = doc.validate()
        assert not v.errors and not v.warnings, "".join(
            str(e) for e in doc.validate().errors)
Exemple #4
0
 def test_copy_is_deprecated(self):
     namespace = 'https://github.com/synbiodex/pysbol3'
     sbol3.set_namespace(namespace)
     name = 'c1'
     c1 = sbol3.Component(name, types=[sbol3.SBO_DNA])
     with self.assertWarns(DeprecationWarning):
         c1.copy()
Exemple #5
0
    def test_expressions(self):
        #############################################
        # set up the document
        print('Setting up document')
        doc = sbol3.Document()
        sbol3.set_namespace('https://bbn.com/scratch/')

        #############################################
        # Create the Expressions
        print('Creating Protocol')

        # expression e1: 60s * duration(a1)
        a1 = paml.Primitive("a1")
        d1 = uml.Duration(observation=uml.DurationObservation(event=[a1]))
        m1 = pamlt.TimeMeasure(expr=sbol3.Measure(60, tyto.OM.second))
        e1 = uml.Expression(symbol="*", is_ordered=False, operand=[m1, d1])
        #doc.add(e1)

        # expression lt1: e1 < e2
        e2 = pamlt.TimeMeasure(expr=sbol3.Measure(120, tyto.OM.second))
        lt1 = uml.Expression(symbol="<", is_ordered=True, operand=[e1, e2])
        #doc.add(lt1)

        # c1: Not(lt1)
        c1 = pamlt.Not(constrained_elements=lt1)
        #  doc.add(c1)

        ########################################
        # Validate and write the document
        print('Validating and writing time')
        v = doc.validate()
        assert not v.errors and not v.warnings, "".join(
            str(e) for e in doc.validate().errors)
Exemple #6
0
    def test_two_behaviors(self):
        #############################################
        # set up the document
        print('Setting up document')
        doc = sbol3.Document()
        sbol3.set_namespace('https://bbn.com/scratch/')

        #############################################
        # Create the behavior and constraints
        print('Creating Constraints')

        a = paml.Primitive("a")
        b = paml.Primitive("b")

        # Constrain start of b to follow end of a by [10, 15]
        follows_constraint = pamlt.precedes(a, [10, 15], b, units=tyto.OM.hour)

        doc.add(a)
        # doc.add(follows_constraint)
        ########################################
        # Validate and write the document
        print('Validating and writing time')
        v = doc.validate()
        assert not v.errors and not v.warnings, "".join(
            str(e) for e in doc.validate().errors)
Exemple #7
0
 def test_initial_value(self):
     # See https://github.com/SynBioDex/pySBOL3/issues/208
     # Use `alt_symbols` to test setting with the empty string.
     # This isn't the actual bug reported in #208. It is a possibly
     # related issue, so we add a unit test just in case.
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     display_id = 'litre'
     symbol = display_id
     label = display_id
     unit = 'https://sbolstandard.org/examples/litre'
     factor = 0.001
     alt_symbols = ['']
     sunit = sbol3.SingularUnit(display_id,
                                symbol,
                                label,
                                alternative_symbols=alt_symbols,
                                unit=unit,
                                factor=factor)
     self.assertIsNotNone(sunit)
     self.assertIsInstance(sunit, sbol3.SingularUnit)
     self.assertEqual(factor, sunit.factor)
     self.assertEqual(unit, sunit.unit)
     self.assertEqual(symbol, sunit.symbol)
     self.assertEqual(label, sunit.label)
     self.assertCountEqual(alt_symbols, sunit.alternative_symbols)
Exemple #8
0
    def test_single_behavior(self):
        #############################################
        # set up the document
        print('Setting up document')
        doc = sbol3.Document()
        sbol3.set_namespace('https://bbn.com/scratch/')

        #############################################
        # Create the behavior and constraints
        print('Creating Constraints')

        a = paml.Primitive("a")
        # Constrain start time of a to [0, 10]
        start_a = pamlt.startTime(a, [0, 10], units=tyto.OM.hour)

        # Constrain end time of a to [10, 15]
        end_a = pamlt.endTime(a, [10, 15], units=tyto.OM.hour)

        # Constrain duration of a to [1, 5]
        duration_a = pamlt.duration(a, [1, 5], units=tyto.OM.hour)

        constraint = pamlt.And([start_a, end_a, duration_a])
        time_constraints = pamlt.TimeConstraints("small_protocol_constraints",
                                                 constraints=[constraint])

        doc.add(a)
        doc.add(time_constraints)
        ########################################
        # Validate and write the document
        print('Validating and writing time')
        v = doc.validate()
        assert not v.errors and not v.warnings, "".join(
            str(e) for e in doc.validate().errors)
Exemple #9
0
    def test_custom_conversion(self):
        """Test if conversion works correctly when the config us used to change expected sheet structure"""
        wb = openpyxl.load_workbook(os.path.join(
            TESTFILE_DIR, 'nonstandard_simple_library.xlsx'),
                                    data_only=True)
        sbol3.set_namespace('http://sbolstandard.org/testfiles')
        config = {
            'basic_parts_name': 'C2',
            'basic_parts_description': 'A12',
            'basic_first_row': 21,
            'basic_role_col': 2,
            'basic_notes_col': 3,
            'basic_description_col': 5,
            'basic_source_prefix_col': 6,
            'basic_source_id_col': 7,
            'basic_final_col': 10,
            'basic_circular_col': 11,
            'basic_length_col': 12,
            'basic_sequence_col': 13,
            'composite_first_part_col': 8
        }
        doc = sbol_utilities.excel_to_sbol.excel_to_sbol(wb, config)

        assert not doc.validate().errors and not doc.validate().warnings
        assert len(doc.find('BasicParts').members) == 26
        assert len(doc.find('CompositeParts').members) == 6
        assert len(doc.find('LinearDNAProducts').members) == 2
        assert len(doc.find('FinalProducts').members) == 2

        temp_name = tempfile.mkstemp(suffix='.nt')[1]
        doc.write(temp_name, sbol3.SORTED_NTRIPLES)
        assert_files_identical(temp_name,
                               os.path.join(TESTFILE_DIR, 'simple_library.nt'))
Exemple #10
0
 def test_namespace_set(self):
     # Test that namespace is properly set on an object after
     # using set_namespace()
     namespace = 'https://github.com/synbiodex/pysbol3'
     sbol3.set_namespace(namespace)
     collection = sbol3.Collection('collection1')
     self.assertEqual(namespace, collection.namespace)
Exemple #11
0
 def test_activity_bad_inflows(self):
     """Test whether validator can detect error due to excess or missing inflows"""
     # set up the document
     print('Setting up document')
     doc = sbol3.Document()
     sbol3.set_namespace('https://bbn.com/scratch/')
     # Create the protocol
     print('Creating protocol')
     protocol = paml.Protocol('broken')
     doc.add(protocol)
     # call order backwards, to make an edge from the final to the initial
     protocol.order(protocol.final(), protocol.initial())
     # access a parameter node and order it backwards too
     p = uml.ActivityParameterNode()
     protocol.nodes.append(p)
     protocol.order(protocol.final(), p)
     # Validate the document, which should produce two errors
     print('Validating and writing protocol')
     v = doc.validate()
     assert len(v) == 3, f'Expected 3 validation issues, but found {len(v)}'
     expected = [
         'https://bbn.com/scratch/broken/ActivityParameterNode1: Too few values for property parameter. Expected 1, found 0',
         'https://bbn.com/scratch/broken/InitialNode1: InitialNode must have no incoming edges, but has 1',
         'https://bbn.com/scratch/broken/FlowFinalNode1: Node has no incoming edges, so cannot be executed'
     ]
     observed = [str(e) for e in v]
     assert observed == expected, f'Unexpected error content: {observed}'
Exemple #12
0
 def test_copy_stability(self):
     # Test the stability of naming of objects across copies.
     # See https://github.com/SynBioDex/pySBOL3/issues/231
     #
     # Strategy: create an object with 10+ children of the same
     # type. Add to a document and serialize the document. Load the
     # serialized document. Copy the object to a new document.
     # Serialize the new document. Compare the serializations. If we
     # use sorted ntriples, the serializations should be the same.
     # This will demonstrate that we maintain names properly despite
     # the inherently unordered nature of SBOL.
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     c1 = sbol3.Component('c1', types=[sbol3.SBO_DNA])
     # Create a double-digit number of children to test sort of 10, 11, 1, etc.
     for i in range(12):
         instance_of_uri = f'https://example.com/instance/i{i}'
         c1.features.append(sbol3.SubComponent(instance_of=instance_of_uri))
     doc1 = sbol3.Document()
     doc1.add(c1)
     # Serialize to string
     doc1_string = doc1.write_string(sbol3.SORTED_NTRIPLES)
     self.assertIsNotNone(doc1_string)
     # Load the serialized document into a new document
     tmp_doc = sbol3.Document()
     tmp_doc.read_string(doc1_string, sbol3.SORTED_NTRIPLES)
     # Locate the top level to copy
     tmp_c1 = tmp_doc.find('c1')
     self.assertIsNotNone(tmp_c1)
     self.assertIsInstance(tmp_c1, sbol3.TopLevel)
     # Copy the top level into a new document
     doc2 = sbol3.Document()
     sbol3.copy([tmp_c1], into_document=doc2)
     doc2_string = doc2.write_string(sbol3.SORTED_NTRIPLES)
     # Verify that the serializations are identical
     self.assertEqual(doc1_string, doc2_string)
Exemple #13
0
 def test_copy(self):
     namespace = 'https://github.com/synbiodex/pysbol3'
     sbol3.set_namespace(namespace)
     test_path = os.path.join(SBOL3_LOCATION, 'multicellular',
                              'multicellular.ttl')
     doc = sbol3.Document()
     doc.read(test_path)
     copies1 = sbol3.copy(doc)
     self.assertEqual(len(doc), len(copies1))
     document_checker = self.make_document_checker(None)
     for obj in copies1:
         obj.traverse(document_checker)
     # Verify that the copies get the new namespace
     copies2 = sbol3.copy(doc, into_namespace=namespace)
     document_checker = self.make_document_checker(None)
     namespace_checker = self.make_namespace_checker(namespace)
     for obj in copies2:
         obj.traverse(document_checker)
         obj.traverse(namespace_checker)
     # Verify new namespace AND new document
     namespace3 = 'https://github.com/synbiodex/pysbol3/copytest'
     doc3 = sbol3.Document()
     copies3 = sbol3.copy(doc,
                          into_namespace=namespace3,
                          into_document=doc3)
     document_checker = self.make_document_checker(doc3)
     namespace_checker = self.make_namespace_checker(namespace3)
     for obj in copies3:
         obj.traverse(document_checker)
         obj.traverse(namespace_checker)
Exemple #14
0
    def test_round_trip(self):
        # Test the int list property, which is not used by the
        # core SBOL 3 data model
        sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
        obj = CustomIdentifiedClass()
        self.assertEqual([], obj.foo_int)
        obj.foo_int.append(7)
        obj.foo_int.append(14)
        self.assertEqual([7, 14], obj.foo_int)

        tl_name = 'my_obj'
        tl = CustomTopClass(tl_name)
        tl.children.append(obj)
        doc = sbol3.Document()
        doc.add(tl)
        doc2 = sbol3.Document()
        # Round trip the document
        with tempfile.TemporaryDirectory() as tmpdirname:
            test_file = os.path.join(tmpdirname, 'custom.nt')
            doc.write(test_file, sbol3.NTRIPLES)
            doc2.read(test_file, sbol3.NTRIPLES)
        tl2 = doc2.find(tl_name)
        obj2 = tl2.children[0]
        # The lists are necessarily unordered because of RDF
        # Compare specially
        self.assertCountEqual([7, 14], obj2.foo_int)
Exemple #15
0
 def test_create(self):
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     agent = sbol3.Agent('agent')
     usage = sbol3.Usage(agent.identity)
     self.assertIsNotNone(usage)
     self.assertEqual(agent.identity, usage.entity)
     self.assertEqual([], usage.roles)
Exemple #16
0
    def test_annotation(self):
        sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
        # Create custom annotation
        annotation_uri = 'http://example.org/boolean_property'
        annotation_value = 'foo'
        c = sbol3.Component('c1', sbol3.SBO_DNA)
        c.annotation = sbol3.TextProperty(c, annotation_uri,
                                          0, 1, [])
        c.annotation = annotation_value
        self.assertEqual(annotation_value, c.annotation)

        doc = sbol3.Document()
        doc.add(c)
        doc2 = sbol3.Document()
        with tempfile.TemporaryDirectory() as tmpdirname:
            test_file = os.path.join(tmpdirname, 'annotation.xml')
            doc.write(test_file, sbol3.RDF_XML)
            # Roundtrip
            doc2.read(test_file, sbol3.RDF_XML)

        # Recover annotation
        c = doc2.find('c1')
        c.annotation = sbol3.TextProperty(c, annotation_uri,
                                          0, 1, [])
        self.assertEqual(annotation_value, c.annotation)
Exemple #17
0
 def test_copy_properties(self):
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     root = sbol3.Component('root', sbol3.SBO_DNA)
     root.name = 'foo'
     objects = sbol3.copy([root])
     root_copy = objects[0]
     self.assertEqual(root_copy.name, 'foo')
 def test_create2(self):
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     c1 = sbol3.Component('c1', [sbol3.SBO_DNA])
     implementation = sbol3.Implementation('impl1', built=c1)
     self.assertIsNotNone(implementation)
     self.assertEqual(c1.identity, implementation.built)
     self.assertEqual(sbol3.SBOL_IMPLEMENTATION, implementation.type_uri)
Exemple #19
0
 def test_singleton_wrapping_urls(self):
     # See https://github.com/SynBioDex/pySBOL3/issues/301
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     process1 = 'https://example.com/thing'
     thing = sbol3.Sequence('thing1', derived_from=process1)
     self.assertEqual(1, len(thing.derived_from))
     self.assertEqual(process1, thing.derived_from[0])
Exemple #20
0
 def test_create(self):
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     base_url = 'https://github.com/SynBioDex/pySBOL3/'
     source_uri = base_url + 'blob/master/sbol3/attachment.py'
     att = sbol3.Attachment('attachment1', source_uri)
     self.assertEqual(source_uri, att.source)
     self.assertEqual(sbol3.SBOL_ATTACHMENT, att.type_uri)
Exemple #21
0
    def test_ordered_behavior_parameters(self):
        doc = sbol3.Document()
        sbol3.set_namespace('https://bbn.com/scratch/')

        parameter1 = uml.OrderedPropertyValue(
            index=0,
            property_value=uml.Parameter(
                direction="in",
                default_value=uml.LiteralInteger(value=0),
                is_unique=True,
                is_ordered=True,
                lower_value=uml.LiteralInteger(value=0),
                upper_value=uml.LiteralInteger(value=10)))
        parameter2 = uml.OrderedPropertyValue(
            index=1,
            property_value=uml.Parameter(
                direction="in",
                default_value=uml.LiteralInteger(value=0),
                is_unique=True,
                is_ordered=True,
                lower_value=uml.LiteralInteger(value=0),
                upper_value=uml.LiteralInteger(value=10)))
        behavior = uml.Behavior("b", parameters=[parameter1, parameter2])
        assert parameter1 in behavior.parameters and parameter2 in behavior.parameters

        doc.add(behavior)
        v = doc.validate()
        assert not v.errors and not v.warnings, "".join(
            str(e) for e in doc.validate().errors)
    def test_expansion(self):
        """Test basic expansion of combinatorial derivations"""
        doc = sbol3.Document()
        doc.read(os.path.join(TESTFILE_DIR, 'simple_library.nt'))
        sbol3.set_namespace('http://sbolstandard.org/testfiles')
        roots = list(
            sbol_utilities.expand_combinatorial_derivations.
            root_combinatorial_derivations(doc))
        assert len(
            roots) == 1, f'Unexpected roots: {[r.identity for r in roots]}'
        derivative_collections = sbol_utilities.expand_combinatorial_derivations.expand_derivations(
            roots)
        assert not len(doc.validate())
        assert len(doc.find('Round_1_order_collection').members) == 24

        output_doc = sbol3.Document()
        for c in derivative_collections:
            copy_toplevel_and_dependencies(output_doc, c)
        assert not len(output_doc.validate())

        temp_name = tempfile.mkstemp(suffix='.nt')[1]
        output_doc.write(temp_name, sbol3.SORTED_NTRIPLES)
        assert_files_identical(
            temp_name, os.path.join(TESTFILE_DIR,
                                    'expanded_simple_library.nt'))
Exemple #23
0
 def test_boolean_property(self):
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     c = sbol3.Component('c1', sbol3.SBO_DNA)
     c.boolean_attribute = sbol3.BooleanProperty(c, 'http://example.org#foo',
                                                 0, 1, [])
     c.boolean_attribute = True
     self.assertEqual(type(c.boolean_attribute), bool)
Exemple #24
0
    def test_slice_assignment(self):
        sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
        c = sbol3.Component('c1', sbol3.SBO_DNA)
        self.assertEqual([], c.roles)
        c.roles.append(sbol3.SO_PROMOTER)
        self.assertEqual([sbol3.SO_PROMOTER], c.roles)
        expected = [rdflib.URIRef(item) for item in c.roles]
        self.assertEqual(expected, c._properties[sbol3.SBOL_ROLE])

        c.roles.append(sbol3.SO_CDS)
        self.assertEqual([sbol3.SO_PROMOTER, sbol3.SO_CDS], c.roles)
        expected = [rdflib.URIRef(item) for item in c.roles]
        self.assertEqual(expected, c._properties[sbol3.SBOL_ROLE])

        c.roles[1] = sbol3.CHEBI_EFFECTOR
        self.assertEqual([sbol3.SO_PROMOTER, sbol3.CHEBI_EFFECTOR], c.roles)
        # Make sure the underlying representation is correct
        expected = [rdflib.URIRef(item) for item in c.roles]
        self.assertEqual(expected, c._properties[sbol3.SBOL_ROLE])

        c.roles.append(sbol3.SO_RBS)
        expected = [sbol3.SO_PROMOTER, sbol3.CHEBI_EFFECTOR, sbol3.SO_RBS]
        self.assertEqual(expected, c.roles)
        expected = [rdflib.URIRef(item) for item in c.roles]
        self.assertEqual(expected, c._properties[sbol3.SBOL_ROLE])

        # Replace the first two elements by slice replacement
        c.roles[0:2] = [sbol3.SO_OPERATOR, sbol3.SO_MRNA]
        expected = [sbol3.SO_OPERATOR, sbol3.SO_MRNA, sbol3.SO_RBS]
        self.assertEqual(expected, c.roles)
        expected = [rdflib.URIRef(item) for item in c.roles]
        self.assertEqual(expected, c._properties[sbol3.SBOL_ROLE])
Exemple #25
0
 def test_create(self):
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     display_id = 'seq1'
     seq = sbol3.Sequence(display_id)
     self.assertIsNotNone(seq)
     self.assertEqual(display_id, seq.display_id)
     self.assertIsNone(seq.elements)
     self.assertIsNone(seq.encoding)
Exemple #26
0
 def test_create(self):
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     display_id = 'exp_data'
     exp_data = sbol3.ExperimentalData(display_id)
     self.assertIsNotNone(exp_data)
     self.assertEqual(display_id, exp_data.display_id)
     self.assertTrue(hasattr(exp_data, 'attachments'))
     self.assertEqual(sbol3.SBOL_EXPERIMENTAL_DATA, exp_data.type_uri)
Exemple #27
0
 def test_trailing_slash(self):
     # A trailing slash on an object's identity should automatically be removed
     sbol3.set_namespace('http://example.org/sbol3')
     slash_identity = posixpath.join(sbol3.get_namespace(), 'c1', '')
     self.assertTrue(slash_identity.endswith(posixpath.sep))
     c = sbol3.Component(slash_identity, sbol3.SBO_DNA)
     identity = slash_identity.strip(posixpath.sep)
     self.assertEqual(identity, c.identity)
Exemple #28
0
 def test_not_iterable(self):
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     c1 = sbol3.Component('c1', sbol3.SBO_DNA)
     self.assertIsInstance(c1.types, Iterable)
     with self.assertRaises(TypeError):
         c1.types = sbol3.SBO_PROTEIN
     with self.assertRaises(TypeError):
         c1.types = object()
Exemple #29
0
 def test_create(self):
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     template = 'https://github.com/synbiodex/pysbol3/component'
     cd1 = sbol3.CombinatorialDerivation('cd1', template)
     self.assertEqual(template, cd1.template)
     comp1 = sbol3.Component('comp1', sbol3.SBO_DNA)
     cd2 = sbol3.CombinatorialDerivation('cd2', comp1)
     self.assertEqual(comp1.identity, cd2.template)
Exemple #30
0
 def test_create(self):
     sbol3.set_namespace('https://github.com/synbiodex/pysbol3')
     display_id = 'plan'
     plan = sbol3.Plan(display_id)
     self.assertIsNotNone(plan)
     self.assertEqual(display_id, plan.display_id)
     # attachments come from TopLevel
     self.assertEqual([], plan.attachments)