Repository: pybel/pybel

Issue:
Add BEP-0001 Support
http://bep.bel.bio/published/BEP-0001.html

Comments:

Patch:
diff --git a/src/pybel/constants.py b/src/pybel/constants.py
index 7cc2e93a..08b381e6 100644
--- a/src/pybel/constants.py
+++ b/src/pybel/constants.py
@@ -205,6 +205,9 @@ BIOPROCESS = 'BiologicalProcess'
 #: Represents the BEL function, pathology()
 PATHOLOGY = 'Pathology'
 
+#: Represents the BEL function, populationAbundance()
+POPULATION = 'Population'
+
 #: Represents the BEL abundance, compositeAbundance()
 COMPOSITE = 'Composite'
 
@@ -226,6 +229,7 @@ PYBEL_NODE_FUNCTIONS = {
     COMPOSITE,
     COMPLEX,
     REACTION,
+    POPULATION,
 }
 
 #: The mapping from PyBEL node functions to BEL strings
@@ -239,6 +243,7 @@ rev_abundance_labels = {
     PATHOLOGY: 'path',
     COMPLEX: 'complex',
     COMPOSITE: 'composite',
+    POPULATION: 'pop',
 }
 
 # Internal edge data keys
diff --git a/src/pybel/dsl/__init__.py b/src/pybel/dsl/__init__.py
index ccf03b32..e96f4add 100644
--- a/src/pybel/dsl/__init__.py
+++ b/src/pybel/dsl/__init__.py
@@ -10,7 +10,7 @@ from .node_classes import (
     Abundance, BaseAbundance, BaseEntity, BiologicalProcess, CentralDogma, ComplexAbundance, CompositeAbundance, Entity,
     EnumeratedFusionRange, Fragment, FusionBase, FusionRangeBase, Gene, GeneFusion, GeneModification, Hgvs,
     HgvsReference, HgvsUnspecified, ListAbundance, MicroRna, MissingFusionRange, NamedComplexAbundance, Pathology,
-    Protein, ProteinFusion, ProteinModification, ProteinSubstitution, Reaction, Rna, RnaFusion, Variant,
+    Population, Protein, ProteinFusion, ProteinModification, ProteinSubstitution, Reaction, Rna, RnaFusion, Variant,
 )
 
 entity = Entity
diff --git a/src/pybel/dsl/constants.py b/src/pybel/dsl/constants.py
index aa14194c..28d4c87d 100644
--- a/src/pybel/dsl/constants.py
+++ b/src/pybel/dsl/constants.py
@@ -4,10 +4,9 @@
 
 from .node_classes import (
     Abundance, BiologicalProcess, ComplexAbundance, CompositeAbundance, Gene, GeneFusion, MicroRna,
-    NamedComplexAbundance,
-    Pathology, Protein, ProteinFusion, Rna, RnaFusion,
+    NamedComplexAbundance, Pathology, Population, Protein, ProteinFusion, Rna, RnaFusion,
 )
-from ..constants import ABUNDANCE, BIOPROCESS, COMPLEX, COMPOSITE, GENE, MIRNA, PATHOLOGY, PROTEIN, RNA
+from ..constants import ABUNDANCE, BIOPROCESS, COMPLEX, COMPOSITE, GENE, MIRNA, PATHOLOGY, POPULATION, PROTEIN, RNA
 
 __all__ = [
     'FUNC_TO_DSL',
@@ -24,6 +23,7 @@ FUNC_TO_DSL = {
     BIOPROCESS: BiologicalProcess,
     COMPLEX: NamedComplexAbundance,
     ABUNDANCE: Abundance,
+    POPULATION: Population,
 }
 
 FUNC_TO_FUSION_DSL = {
diff --git a/src/pybel/dsl/node_classes.py b/src/pybel/dsl/node_classes.py
index dec3f612..cbffbcdd 100644
--- a/src/pybel/dsl/node_classes.py
+++ b/src/pybel/dsl/node_classes.py
@@ -12,7 +12,7 @@ from ..constants import (
     ABUNDANCE, BEL_DEFAULT_NAMESPACE, BIOPROCESS, COMPLEX, COMPOSITE, CONCEPT, FRAGMENT, FRAGMENT_DESCRIPTION,
     FRAGMENT_MISSING, FRAGMENT_START, FRAGMENT_STOP, FUNCTION, FUSION, FUSION_MISSING, FUSION_REFERENCE, FUSION_START,
     FUSION_STOP, GENE, GMOD, HGVS, KIND, MEMBERS, MIRNA, PARTNER_3P, PARTNER_5P, PATHOLOGY, PMOD, PMOD_CODE, PMOD_ORDER,
-    PMOD_POSITION, PRODUCTS, PROTEIN, RANGE_3P, RANGE_5P, REACTANTS, REACTION, RNA, VARIANTS, XREFS,
+    PMOD_POSITION, POPULATION, PRODUCTS, PROTEIN, RANGE_3P, RANGE_5P, REACTANTS, REACTION, RNA, VARIANTS, XREFS,
     rev_abundance_labels,
 )
 from ..language import Entity
@@ -40,6 +40,7 @@ __all__ = [
     'CompositeAbundance',
     'BiologicalProcess',
     'Pathology',
+    'Population',
     'NamedComplexAbundance',
     'Reaction',
 
@@ -214,6 +215,17 @@ class Pathology(BaseAbundance):
     function = PATHOLOGY
 
 
+class Population(BaseAbundance):
+    """Builds a popuation node.
+
+    Example:
+    >>> Population(namespace='uberon', name='blood')
+
+    """
+
+    function = POPULATION
+
+
 class Variant(dict, metaclass=ABCMeta):
     """The superclass for variant dictionaries."""
 
diff --git a/src/pybel/parser/parse_bel.py b/src/pybel/parser/parse_bel.py
index 2ba93c9b..e42da96d 100644
--- a/src/pybel/parser/parse_bel.py
+++ b/src/pybel/parser/parse_bel.py
@@ -33,9 +33,9 @@ from ..constants import (
     ABUNDANCE, ACTIVITY, ASSOCIATION, BEL_DEFAULT_NAMESPACE, BIOPROCESS, CAUSES_NO_CHANGE, CELL_SECRETION,
     CELL_SURFACE_EXPRESSION, COMPLEX, COMPOSITE, CONCEPT, DECREASES, DEGRADATION, DIRECTLY_DECREASES,
     DIRECTLY_INCREASES, DIRTY, EFFECT, EQUIVALENT_TO, FROM_LOC, FUNCTION, FUSION, GENE, INCREASES, IS_A, LINE, LOCATION,
-    MEMBERS, MIRNA, MODIFIER, NAME, NAMESPACE, NEGATIVE_CORRELATION, OBJECT, PART_OF, PATHOLOGY, POSITIVE_CORRELATION,
-    PRODUCTS, PROTEIN, REACTANTS, REACTION, REGULATES, RELATION, RNA, SUBJECT, TARGET, TO_LOC, TRANSCRIBED_TO,
-    TRANSLATED_TO, TRANSLOCATION, TWO_WAY_RELATIONS, VARIANTS, belns_encodings,
+    MEMBERS, MIRNA, MODIFIER, NAME, NAMESPACE, NEGATIVE_CORRELATION, OBJECT, PART_OF, PATHOLOGY, POPULATION,
+    POSITIVE_CORRELATION, PRODUCTS, PROTEIN, REACTANTS, REACTION, REGULATES, RELATION, RNA, SUBJECT, TARGET, TO_LOC,
+    TRANSCRIBED_TO, TRANSLATED_TO, TRANSLOCATION, TWO_WAY_RELATIONS, VARIANTS, belns_encodings,
 )
 from ..dsl import BaseEntity, cell_surface_expression, secretion
 from ..tokens import parse_result_to_dsl
@@ -110,6 +110,8 @@ biological_process_tag = one_of_tags(['bp', 'biologicalProcess'], BIOPROCESS, FU
 #: 2.3.2 http://openbel.org/language/version_2.0/bel_specification_version_2.0.html#_pathology_path
 pathology_tag = one_of_tags(['o', 'path', 'pathology'], PATHOLOGY, FUNCTION)
 
+population_tag = one_of_tags(['pop', 'populationAbundance'], POPULATION, FUNCTION)
+
 #: 2.3.3 http://openbel.org/language/version_2.0/bel_specification_version_2.0.html#Xactivity
 activity_tag = one_of_tags(['act', 'activity'], ACTIVITY, MODIFIER)
 
@@ -422,7 +424,9 @@ class BELParser(BaseParser):
         #: `2.3.2 <http://openbel.org/language/version_2.0/bel_specification_version_2.0.html#_pathology_path>`_
         self.pathology = pathology_tag + nest(concept)
 
-        self.bp_path = self.biological_process | self.pathology
+        self.population = population_tag + nest(concept)
+
+        self.bp_path = self.biological_process | self.pathology | self.population
         self.bp_path.setParseAction(self.check_function_semantics)
 
         self.activity_standard = activity_tag + nest(


Test patch:
diff --git a/tests/test_parse/test_parse_bel.py b/tests/test_parse/test_parse_bel.py
index 44442106..b6bfbd46 100644
--- a/tests/test_parse/test_parse_bel.py
+++ b/tests/test_parse/test_parse_bel.py
@@ -10,12 +10,12 @@ from pybel.constants import (
     ABUNDANCE, ACTIVITY, BEL_DEFAULT_NAMESPACE, BIOPROCESS, COMPLEX, COMPOSITE, CONCEPT, DEGRADATION,
     DIRECTLY_INCREASES, DIRTY, EFFECT, FRAGMENT, FROM_LOC, FUNCTION, FUSION, FUSION_MISSING, FUSION_REFERENCE,
     FUSION_START, FUSION_STOP, GENE, HAS_VARIANT, HGVS, IDENTIFIER, KIND, LOCATION, MEMBERS, MIRNA, MODIFIER, NAME,
-    NAMESPACE, OBJECT, PARTNER_3P, PARTNER_5P, PART_OF, PATHOLOGY, PRODUCTS, PROTEIN, RANGE_3P, RANGE_5P, REACTANTS,
-    REACTION, RELATION, RNA, SUBJECT, TARGET, TO_LOC, TRANSLOCATION, VARIANTS,
+    NAMESPACE, OBJECT, PARTNER_3P, PARTNER_5P, PART_OF, PATHOLOGY, POPULATION, PRODUCTS, PROTEIN, RANGE_3P, RANGE_5P,
+    REACTANTS, REACTION, RELATION, RNA, SUBJECT, TARGET, TO_LOC, TRANSLOCATION, VARIANTS,
 )
 from pybel.dsl import (
-    Fragment, abundance, bioprocess, cell_surface_expression, complex_abundance, composite_abundance, fragment,
-    fusion_range, gene, gene_fusion, gmod, hgvs, mirna, named_complex_abundance, pathology, pmod, protein,
+    Fragment, Population, abundance, bioprocess, cell_surface_expression, complex_abundance, composite_abundance,
+    fragment, fusion_range, gene, gene_fusion, gmod, hgvs, mirna, named_complex_abundance, pathology, pmod, protein,
     protein_fusion, reaction, rna, rna_fusion, secretion, translocation,
 )
 from pybel.dsl.namespaces import hgnc
@@ -1499,6 +1499,32 @@ class TestPathology(TestTokenParserBase):
         self.assertEqual('path(MESH:adenocarcinoma)', self.graph.node_to_bel(expected_node))
 
 
+class TestPopulation(TestTokenParserBase):
+    def setUp(self):
+        self.parser.clear()
+        self.parser.population.setParseAction(self.parser.handle_term)
+
+    def test_parse_population(self):
+        statement = 'pop(uberon:blood)'
+        result = self.parser.population.parseString(statement)
+
+        expected_dict = {
+            FUNCTION: POPULATION,
+            CONCEPT: {
+                NAMESPACE: 'uberon',
+                NAME: 'blood',
+            },
+        }
+        self.assertEqual(expected_dict, result.asDict())
+
+        expected_node = Population('uberon', 'blood')
+        self.assert_has_node(expected_node)
+
+        self.assertEqual('pop(uberon:blood)',
+                         self.graph.node_to_bel(expected_node),
+                         msg='Nodes: {}'.format(list(self.graph)))
+
+
 class TestActivity(TestTokenParserBase):
     """Tests for molecular activity terms."""