Repository: pybel/pybel
Pull request #453 (2020-07-04 15:12:05)

Update parser and data structure
Parser updates:

- [x] Auto-upgrade molecular activities (b9a289e5)
- [x] Auto-upgrade translocations (b9a289e5)
- [x] Normalize citation nomenclature to also use an `Entity` (48fb182a)
- [x] Normalize the usage of "u/v" and "subject/object" to "source/target" (88a162be)

This PR also:
- Reorganize exceptions
- Reorganize parser output structure (remove TARGET constant)
- Automatically add GO namespace to concept parser
- Adds top-level parse function for easy use: `pybel.parse()`
- Adds an example flask app wrapping the parser at `python -m pybel.apps.parser`.

codecov[bot]: # [Codecov](https://codecov.io/gh/pybel/pybel/pull/453?src=pr&el=h1) Report
> Merging [#453](https://codecov.io/gh/pybel/pybel/pull/453?src=pr&el=desc) into [master](https://codecov.io/gh/pybel/pybel/commit/7413d7a3989838ac3365184648a5a4bfd145fdeb&el=desc) will **decrease** coverage by `0.23%`.
> The diff coverage is `83.16%`.

[![Impacted file tree graph](https://codecov.io/gh/pybel/pybel/pull/453/graphs/tree.svg?width=650&height=150&src=pr&token=J7joRTRygG)](https://codecov.io/gh/pybel/pybel/pull/453?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #453      +/-   ##
==========================================
- Coverage   79.16%   78.93%   -0.24%     
==========================================
  Files         172      171       -1     
  Lines        8987     9033      +46     
  Branches     1298     1302       +4     
==========================================
+ Hits         7115     7130      +15     
- Misses       1593     1618      +25     
- Partials      279      285       +6     
```


| [Impacted Files](https://codecov.io/gh/pybel/pybel/pull/453?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/pybel/constants.py](https://codecov.io/gh/pybel/pybel/pull/453/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2NvbnN0YW50cy5weQ==) | `100.00% <ø> (ø)` | |
| [src/pybel/dsl/edges.py](https://codecov.io/gh/pybel/pybel/pull/453/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2RzbC9lZGdlcy5weQ==) | `78.72% <44.44%> (-21.28%)` | :arrow_down: |
| [src/pybel/canonicalize.py](https://codecov.io/gh/pybel/pybel/pull/453/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2Nhbm9uaWNhbGl6ZS5weQ==) | `87.68% <66.66%> (-5.08%)` | :arrow_down: |
| [src/pybel/parser/parse\_bel.py](https://codecov.io/gh/pybel/pybel/pull/453/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL3BhcnNlci9wYXJzZV9iZWwucHk=) | `90.98% <74.50%> (-3.94%)` | :arrow_down: |
| [src/pybel/parser/parse\_concept.py](https://codecov.io/gh/pybel/pybel/pull/453/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL3BhcnNlci9wYXJzZV9jb25jZXB0LnB5) | `94.73% <83.33%> (-1.16%)` | :arrow_down: |
| [src/pybel/exceptions.py](https://codecov.io/gh/pybel/pybel/pull/453/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2V4Y2VwdGlvbnMucHk=) | `94.84% <94.79%> (-5.16%)` | :arrow_down: |
| [src/pybel/\_\_init\_\_.py](https://codecov.io/gh/pybel/pybel/pull/453/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL19faW5pdF9fLnB5) | `100.00% <100.00%> (ø)` | |
| [src/pybel/io/jgif.py](https://codecov.io/gh/pybel/pybel/pull/453/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2lvL2pnaWYucHk=) | `71.51% <100.00%> (ø)` | |
| [src/pybel/io/line\_utils.py](https://codecov.io/gh/pybel/pybel/pull/453/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2lvL2xpbmVfdXRpbHMucHk=) | `68.51% <100.00%> (ø)` | |
| [src/pybel/language.py](https://codecov.io/gh/pybel/pybel/pull/453/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2xhbmd1YWdlLnB5) | `93.84% <100.00%> (+0.29%)` | :arrow_up: |
| ... and [8 more](https://codecov.io/gh/pybel/pybel/pull/453/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pybel/pybel/pull/453?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pybel/pybel/pull/453?src=pr&el=footer). Last update [7413d7a...ab595b8](https://codecov.io/gh/pybel/pybel/pull/453?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.rst b/README.rst
index cab997ad..080ef3b0 100644
--- a/README.rst
+++ b/README.rst
@@ -190,6 +190,21 @@ After installing ``jinja2`` and ``ipython``, BEL graphs can be displayed in Jupy
    >>> from pybel.io.jupyter import to_jupyter
    >>> to_jupyter(sialic_acid_graph)
 
+Using the Parser
+~~~~~~~~~~~~~~~~
+If you don't want to use the ``pybel.BELGraph`` data structure and just want to turn BEL statements into JSON
+for your own purposes, you can directly use the ``pybel.parse()`` function.
+
+.. code-block:: python
+
+    >>> import pybel
+    >>> pybel.parse('p(hgnc:4617 ! GSK3B) regulates p(hgnc:6893 ! MAPT)')
+    {'source': {'function': 'Protein', 'concept': {'namespace': 'hgnc', 'identifier': '4617', 'name': 'GSK3B'}}, 'relation': 'regulates', 'target': {'function': 'Protein', 'concept': {'namespace': 'hgnc', 'identifier': '6893', 'name': 'MAPT'}}}
+
+This functionality can also be exposed through a Flask-based web application with ``python -m pybel.apps.parser`` after
+installing ``flask`` with ``pip install flask``. Note that the first run requires about a ~2 second delay to generate
+the parser, after which each parse is very fast.
+
 Using the CLI
 ~~~~~~~~~~~~~
 PyBEL also installs a command line interface with the command :code:`pybel` for simple utilities such as data
diff --git a/docs/source/reference/logging.rst b/docs/source/reference/logging.rst
index c9e131f6..d070f1da 100644
--- a/docs/source/reference/logging.rst
+++ b/docs/source/reference/logging.rst
@@ -1,11 +1,4 @@
 Logging Messages
 ================
-Errors
-------
 .. automodule:: pybel.exceptions
     :members:
-
-Parse Exceptions
-----------------
-.. automodule:: pybel.parser.exc
-    :members:
diff --git a/src/pybel/__init__.py b/src/pybel/__init__.py
index 0c09a8c8..d096ef40 100644
--- a/src/pybel/__init__.py
+++ b/src/pybel/__init__.py
@@ -41,6 +41,7 @@ from .io.spia import to_spia_dfs, to_spia_excel, to_spia_tsvs
 from .io.triples import to_edgelist, to_triples, to_triples_file
 from .io.umbrella_nodelink import to_umbrella_nodelink, to_umbrella_nodelink_file, to_umbrella_nodelink_gz
 from .manager import Manager, from_database, to_database
+from .parser.parse_bel import parse
 from .struct import BELGraph, Pipeline, Query
 from .struct.operations import union
 from .version import get_version
diff --git a/src/pybel/apps/parser.py b/src/pybel/apps/parser.py
new file mode 100644
index 00000000..4b176648
--- /dev/null
+++ b/src/pybel/apps/parser.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+"""A simple web-based BEL parser implemented with Flask.
+
+Run from the command line with ``python -m pybel.apps.parser``.
+"""
+
+import flask
+import pyparsing
+
+import pybel
+import pybel.exceptions
+
+app = flask.Flask(__name__)
+
+
+@app.route('/parse', methods=['POST'])
+def parse():
+    """Parse the BEL in the `text` JSON field.
+
+    Example usage:
+
+    >>> import requests
+    >>> requests.post('http://localhost:5000/parse', json={'text': 'p(HGNC:123) increases p(HGNC:456)'}).json()
+    {'input': 'p(HGNC:123) increases p(HGNC:456)', 'output': {'object': {'concept': {'name': '456', 'namespace': 'HGNC'}, 'function': 'Protein'}, 'relation': 'increases', 'subject': {'concept': {'name': '123', 'namespace': 'HGNC'}, 'function': 'Protein'}}, 'success': True}
+    """
+    text = flask.request.json.get('text')
+    if text is None:
+        return flask.jsonify(success=False, message='missing `text`')
+
+    try:
+        rv = pybel.parse(text)
+    except (pyparsing.ParseException, pybel.exceptions.PyBELWarning) as e:
+        return flask.jsonify(success=False, input=text, exception=str(e))
+    else:
+        return flask.jsonify(success=True, input=text, output=rv)
+
+
+if __name__ == '__main__':
+    app.run()
diff --git a/src/pybel/canonicalize.py b/src/pybel/canonicalize.py
index d830dd97..a9d339b7 100644
--- a/src/pybel/canonicalize.py
+++ b/src/pybel/canonicalize.py
@@ -6,6 +6,7 @@ import gzip
 import itertools as itt
 import logging
 import time
+import warnings
 from typing import Iterable, List, Mapping, Optional, TextIO, Tuple, Union
 
 from networkx.utils import open_file
@@ -13,12 +14,13 @@ from networkx.utils import open_file
 import bel_resources.constants
 from bel_resources import make_knowledge_header
 from .constants import (
-    ACTIVITY, ANNOTATIONS, BEL_DEFAULT_NAMESPACE, CELL_SURFACE, CITATION, CITATION_DB, CITATION_IDENTIFIER,
-    CITATION_TYPE_PUBMED, DEGRADATION, EFFECT, EVIDENCE, EXTRACELLULAR, FROM_LOC, INTRACELLULAR, LOCATION, MODIFIER,
-    NAME, NAMESPACE, OBJECT, PYBEL_AUTOEVIDENCE, PYBEL_PUBMED, RELATION, SET_CITATION_FMT, SUBJECT, TO_LOC,
-    TRANSLOCATION, UNQUALIFIED_EDGES, VARIANTS,
+    ACTIVITY, ANNOTATIONS, BEL_DEFAULT_NAMESPACE, CELL_SURFACE, CITATION, CITATION_TYPE_PUBMED, DEGRADATION, EFFECT,
+    EVIDENCE, EXTRACELLULAR, FROM_LOC, INTRACELLULAR, LOCATION, MODIFIER, NAME, NAMESPACE, PYBEL_AUTOEVIDENCE,
+    PYBEL_PUBMED, RELATION, SET_CITATION_FMT, SOURCE_MODIFIER, TARGET_MODIFIER, TO_LOC, TRANSLOCATION,
+    UNQUALIFIED_EDGES, VARIANTS,
 )
 from .dsl import BaseAbundance, BaseEntity, FusionBase, ListAbundance, Reaction
+from .language import Entity
 from .typing import EdgeData
 from .utils import ensure_quotes
 from .version import VERSION
@@ -118,89 +120,78 @@ def _decanonicalize_edge_node(
         return node_str
 
     if DEGRADATION == modifier:
-        return "deg({})".format(node_str)
+        warnings.warn('degradation is deprecated', DeprecationWarning)
+        return f'deg({node_str})'
 
     effect = node_edge_data.get(EFFECT)
 
     if ACTIVITY == modifier:
         if effect is None:
-            return "act({})".format(node_str)
-
-        if effect[NAMESPACE] == BEL_DEFAULT_NAMESPACE:
-            return "act({}, ma({}))".format(node_str, effect[NAME])
-
-        return "act({}, ma({}:{}))".format(node_str, effect[NAMESPACE], ensure_quotes(effect[NAME]))
+            return f'act({node_str})'
+        return f'act({node_str}, ma({effect}))'
 
     if TRANSLOCATION == modifier:
         if effect is None:
-            return 'tloc({})'.format(node_str)
+            return f'tloc({node_str})'
 
-        to_loc_data = effect[TO_LOC]
-        from_loc_data = effect[FROM_LOC]
+        to_loc_data: Entity = effect[TO_LOC]
+        from_loc_data: Entity = effect[FROM_LOC]
 
         if from_loc_data[NAMESPACE] == BEL_DEFAULT_NAMESPACE and from_loc_data[NAME] == INTRACELLULAR:
             if to_loc_data[NAMESPACE] == BEL_DEFAULT_NAMESPACE and to_loc_data[NAME] == EXTRACELLULAR:
-                return 'sec({})'.format(node_str)
+                warnings.warn('deprecated output of BEL default namespace', DeprecationWarning)
+                return f'sec({node_str})'
             if to_loc_data[NAMESPACE] == BEL_DEFAULT_NAMESPACE and to_loc_data[NAME] == CELL_SURFACE:
-                return 'surf({})'.format(node_str)
-
-        from_loc = _get_tloc_terminal('fromLoc', from_loc_data)
-        to_loc = _get_tloc_terminal('toLoc', to_loc_data)
+                warnings.warn('deprecated output of BEL default namespace', DeprecationWarning)
+                return f'surf({node_str})'
+            raise ValueError
 
-        return "tloc({}, {}, {})".format(node_str, from_loc, to_loc)
+        return f"tloc({node_str}, fromLoc({from_loc_data}), toLoc({to_loc_data}))"
 
     raise ValueError('invalid modifier: {}'.format(modifier))
 
 
-def _get_tloc_terminal(side, data):
-    return "{}({}:{})".format(
-        side,
-        data[NAMESPACE],
-        ensure_quotes(data[NAME]),
-    )
-
-
 def edge_to_tuple(
-    u: BaseEntity,
-    v: BaseEntity,
+    source: BaseEntity,
+    target: BaseEntity,
     data: EdgeData,
     use_identifiers: bool = True,
 ) -> Tuple[str, str, str]:
     """Take two nodes and gives back a BEL string representing the statement.
 
-    :param u: The edge's source's PyBEL node data dictionary
-    :param v: The edge's target's PyBEL node data dictionary
+    :param source: The edge's source's PyBEL node data dictionary
+    :param target: The edge's target's PyBEL node data dictionary
     :param data: The edge's data dictionary
     :param use_identifiers: Enables extended `BEP-0008 <http://bep.bel.bio/published/BEP-0008.html>`_ syntax
     """
-    u_str = _decanonicalize_edge_node(u, data, node_position=SUBJECT, use_identifiers=use_identifiers)
-    v_str = _decanonicalize_edge_node(v, data, node_position=OBJECT, use_identifiers=use_identifiers)
+    u_str = _decanonicalize_edge_node(source, data, node_position=SOURCE_MODIFIER, use_identifiers=use_identifiers)
+    v_str = _decanonicalize_edge_node(target, data, node_position=TARGET_MODIFIER, use_identifiers=use_identifiers)
     return u_str, data[RELATION], v_str
 
 
 def edge_to_bel(
-    u: BaseEntity,
-    v: BaseEntity,
+    source: BaseEntity,
+    target: BaseEntity,
     data: EdgeData,
     sep: Optional[str] = None,
     use_identifiers: bool = True,
 ) -> str:
     """Take two nodes and gives back a BEL string representing the statement.
 
-    :param u: The edge's source's PyBEL node data dictionary
-    :param v: The edge's target's PyBEL node data dictionary
+    :param source: The edge's source's PyBEL node data dictionary
+    :param target: The edge's target's PyBEL node data dictionary
     :param data: The edge's data dictionary
     :param sep: The separator between the source, relation, and target. Defaults to ' '
     :param use_identifiers: Enables extended `BEP-0008 <http://bep.bel.bio/published/BEP-0008.html>`_ syntax
     """
     sep = sep or ' '
-    return sep.join(edge_to_tuple(u=u, v=v, data=data, use_identifiers=use_identifiers))
+    return sep.join(edge_to_tuple(source=source, target=target, data=data, use_identifiers=use_identifiers))
 
 
 def _sort_qualified_edges_helper(t: EdgeTuple) -> Tuple[str, str, str]:
     return (
-        t[3][CITATION][CITATION_DB],
-        t[3][CITATION][CITATION_IDENTIFIER],
+        t[3][CITATION].namespace,
+        t[3][CITATION].identifier,
         t[3][EVIDENCE],
     )
 
@@ -220,7 +211,7 @@ def sort_qualified_edges(graph) -> Iterable[EdgeTuple]:
 
 def _citation_sort_key(t: EdgeTuple) -> Tuple[str, str]:
     """Make a confusing 4 tuple sortable by citation."""
-    return t[3][CITATION][CITATION_DB], t[3][CITATION][CITATION_IDENTIFIER]
+    return t[3][CITATION].namespace, t[3][CITATION].identifier
 
 
 def _evidence_sort_key(t: EdgeTuple) -> str:
diff --git a/src/pybel/cli.py b/src/pybel/cli.py
index 15446374..835a17d2 100644
--- a/src/pybel/cli.py
+++ b/src/pybel/cli.py
@@ -28,6 +28,7 @@ from tqdm import tqdm
 from .canonicalize import to_bel_script
 from .constants import get_cache_connection
 from .examples import braf_graph, egf_graph, homology_graph, sialic_acid_graph, statin_graph
+from .exceptions import BELParserWarning
 from .io import (
     from_bel_script, from_pickle, load, to_bel_commons, to_edgelist, to_graphml, to_gsea, to_neo4j,
     to_nodelink_file, to_pickle, to_sif, to_triples_file,
@@ -36,7 +37,6 @@ from .io.bel_commons_client import _get_host, _get_password, _get_user
 from .manager import Manager
 from .manager.database_io import to_database
 from .manager.models import Edge, Namespace, Node
-from .parser.exc import BELParserWarning
 from .struct import get_unused_annotations, get_unused_list_annotation_values, get_unused_namespaces
 from .struct.graph import BELGraph, WarningTuple
 from .utils import get_corresponding_pickle_path
@@ -117,6 +117,15 @@ def main(ctx, connection):
     ctx.obj.bind()  # add the engine to the metadata and query property to the session
 
 
+@main.command()
+@click.argument('text')
+@click.option('--pprint', is_flag=True)
+def parse(text: str, pprint: bool):
+    """Parse a single BEL statement and pring JSON output."""
+    from .parser.parse_bel import parse as _parse
+    click.echo(json.dumps(_parse(text), indent=2 if pprint else None))
+
+
 @main.command()
 @click.argument('path')
 @click.option('--allow-naked-names', is_flag=True, help="Enable lenient parsing for naked names")
diff --git a/src/pybel/constants.py b/src/pybel/constants.py
index 5787cd38..a78cf9ce 100644
--- a/src/pybel/constants.py
+++ b/src/pybel/constants.py
@@ -60,13 +60,6 @@ NAMESPACE_DOMAIN_TYPES = {
     NAMESPACE_DOMAIN_OTHER,
 }
 
-#: Represents the key for the citation type in a citation dictionary
-CITATION_DB = 'db'
-#: Represents the key for the citation reference in a citation dictionary
-CITATION_IDENTIFIER = 'db_id'
-
-#: Represents the key for the optional PyBEL citation title entry in a citation dictionary
-CITATION_DB_NAME = 'db_name'
 #: Represents the key for the citation date in a citation dictionary
 CITATION_DATE = 'date'
 #: Represents the key for the citation authors in a citation dictionary
@@ -88,7 +81,6 @@ CITATION_LAST_AUTHOR = 'last'
 
 MODIFIER = 'modifier'
 EFFECT = 'effect'
-TARGET = 'target'
 FROM_LOC = 'fromLoc'
 TO_LOC = 'toLoc'
 
@@ -258,10 +250,12 @@ EVIDENCE = 'evidence'
 ANNOTATIONS = 'annotations'
 #: The key for free annotations
 FREE_ANNOTATIONS = 'free_annotations'
-#: The key for an internal edge data dictionary for the subject modifier dictionary
-SUBJECT = 'subject'
-#: The key for an internal edge data dictionary for the object modifier dictionary
-OBJECT = 'object'
+SOURCE = 'source'
+TARGET = 'target'
+#: The key for an internal edge data dictionary for the source modifier dictionary
+SOURCE_MODIFIER = 'source_modifier'
+#: The key for an internal edge data dictionary for the target modifier dictionary
+TARGET_MODIFIER = 'target_modifier'
 #: The key or an internal edge data dictionary for the line number
 LINE = 'line'
 #: The key representing the hash of the other
@@ -273,8 +267,8 @@ PYBEL_EDGE_DATA_KEYS = {
     CITATION,
     EVIDENCE,
     ANNOTATIONS,
-    SUBJECT,
-    OBJECT,
+    SOURCE_MODIFIER,
+    TARGET_MODIFIER,
 }
 
 #: The group of all PyBEL-specific keys for edge data dictionaries, not used for hashing.
diff --git a/src/pybel/dsl/edges.py b/src/pybel/dsl/edges.py
index f4e1670a..0e5e594d 100644
--- a/src/pybel/dsl/edges.py
+++ b/src/pybel/dsl/edges.py
@@ -2,13 +2,14 @@
 
 """Internal DSL functions for edges."""
 
+import warnings
 from typing import Dict, Optional, Union
 
 from ..constants import (
     ACTIVITY, BEL_DEFAULT_NAMESPACE, CELL_SURFACE, DEGRADATION, EFFECT, EXTRACELLULAR, FROM_LOC, INTRACELLULAR,
-    LOCATION, MODIFIER, TO_LOC, TRANSLOCATION,
+    LOCATION, MODIFIER, NAME, NAMESPACE, TO_LOC, TRANSLOCATION,
 )
-from ..language import Entity
+from ..language import Entity, activity_mapping, compartment_mapping
 
 __all__ = [
     'activity',
@@ -30,7 +31,7 @@ def _modifier_helper(
     """Make a modifier dictionary.
 
     :param modifier: The name of the modifier
-    :param Optional[dict] location: An entity from :func:`pybel.dsl.entity`
+    :param location: An entity from :func:`pybel.dsl.entity`
     """
     rv = {
         MODIFIER: modifier,
@@ -57,13 +58,16 @@ def activity(
     """
     rv = _modifier_helper(ACTIVITY, location=location)
 
-    if name or (namespace and identifier):
+    if namespace == BEL_DEFAULT_NAMESPACE or (name and not namespace):
+        rv[EFFECT] = activity_mapping[name]
+    elif not name and not namespace and not identifier:
+        pass
+    else:
         rv[EFFECT] = Entity(
-            namespace=(namespace or BEL_DEFAULT_NAMESPACE),
+            namespace=namespace,
             name=name,
             identifier=identifier,
         )
-
     return rv
 
 
@@ -87,15 +91,19 @@ def translocation(
     """
     rv = _modifier_helper(TRANSLOCATION)
     if isinstance(from_loc, str):
-        from_loc = Entity(
-            namespace=BEL_DEFAULT_NAMESPACE,
-            name=from_loc,
-        )
+        from_loc = compartment_mapping[from_loc]
+    elif from_loc[NAMESPACE] == BEL_DEFAULT_NAMESPACE:
+        warnings.warn('Deprecated usage of translocation()')
+        from_loc = compartment_mapping[from_loc[NAME]]
+    if not isinstance(from_loc, Entity):
+        raise TypeError
     if isinstance(to_loc, str):
-        to_loc = Entity(
-            namespace=BEL_DEFAULT_NAMESPACE,
-            name=to_loc,
-        )
+        to_loc = compartment_mapping[to_loc]
+    elif to_loc[NAMESPACE] == BEL_DEFAULT_NAMESPACE:
+        warnings.warn('Deprecated usage of translocation()')
+        to_loc = compartment_mapping[to_loc[NAME]]
+    if not isinstance(to_loc, Entity):
+        raise TypeError
 
     rv[EFFECT] = {
         FROM_LOC: from_loc,
@@ -147,7 +155,7 @@ def location(identifier: Entity) -> LocationDict:
             target,
             citation=...,
             evidence=...,
-            object_modifier=location(entity(namespace='GO', name='cytosol', identifier='GO:0005829')),
+            target_modifier=location(entity(namespace='GO', name='cytosol', identifier='GO:0005829')),
         )
 
     X increases the kinase activity of Y in the cytoplasm. In this case, the :func:`activity` function takes a location as
@@ -167,7 +175,7 @@ def location(identifier: Entity) -> LocationDict:
             target,
             citation=...,
             evidence=...,
-            object_modifier=activity('kin', location=entity(namespace='GO', name='cytosol', identifier='GO:0005829')),
+            target_modifier=activity('kin', location=entity(namespace='GO', name='cytosol', identifier='GO:0005829')),
         )
     """
     return {
diff --git a/src/pybel/examples/braf_example.py b/src/pybel/examples/braf_example.py
index 9c19725d..401d26e9 100644
--- a/src/pybel/examples/braf_example.py
+++ b/src/pybel/examples/braf_example.py
@@ -63,7 +63,7 @@ braf_graph.add_increases(
     braf,
     evidence=evidence,
     citation='11283246',
-    object_modifier=activity(name='kin'),
+    target_modifier=activity(name='kin'),
     annotations={'Species': '9606'},
 )
 
@@ -72,7 +72,7 @@ braf_graph.add_increases(
     raf1,
     evidence=evidence,
     citation='11283246',
-    object_modifier=activity(name='kin'),
+    target_modifier=activity(name='kin'),
     annotations={'Species': '9606'},
 )
 
@@ -81,7 +81,7 @@ braf_graph.add_increases(
     elk1,
     evidence=evidence,
     citation='11283246',
-    subject_modifier=activity(name='kin'),
-    object_modifier=activity(name='tscript'),
+    source_modifier=activity(name='kin'),
+    target_modifier=activity(name='tscript'),
     annotations={'Species': '9606'},
 )
diff --git a/src/pybel/examples/egf_example.py b/src/pybel/examples/egf_example.py
index 5a17cad4..25b27fa3 100644
--- a/src/pybel/examples/egf_example.py
+++ b/src/pybel/examples/egf_example.py
@@ -94,7 +94,7 @@ egf_graph.add_increases(
     evidence='This induction was not seen either when LNCaP cells were treated with flutamide or conditioned medium '
              'were pretreated with antibody to the epidermal growth factor (EGF)',
     annotations={'Species': '9606'},
-    subject_modifier=activity('tscript'),
+    source_modifier=activity('tscript'),
 )
 
 egf_graph.add_decreases(
@@ -122,7 +122,7 @@ egf_graph.add_increases(
     evidence='Although found predominantly in the cytoplasm and, less abundantly, in the nucleus, VCP can be '
              'translocated from the nucleus after stimulation with epidermal growth factor.',
     annotations={'Species': '9606'},
-    object_modifier=translocation(
+    target_modifier=translocation(
         from_loc=nucleus,
         to_loc=cytoplasm,
     ),
@@ -135,8 +135,8 @@ egf_graph.add_increases(
     evidence="Valosin-containing protein (VCP; also known as p97) has been shown to be associated with antiapoptotic"
              " function and metastasis via activation of the nuclear factor-kappaB signaling pathway.",
     annotations={'Species': '9606'},
-    subject_modifier=activity('cat'),
-    object_modifier=activity('tscript'),
+    source_modifier=activity('cat'),
+    target_modifier=activity('tscript'),
 )
 
 egf_graph.add_decreases(
@@ -146,5 +146,5 @@ egf_graph.add_decreases(
     evidence="Valosin-containing protein (VCP; also known as p97) has been shown to be associated with antiapoptotic "
              "function and metastasis via activation of the nuclear factor-kappaB signaling pathway.",
     annotations={'Species': '9606'},
-    subject_modifier=activity('tscript'),
+    source_modifier=activity('tscript'),
 )
diff --git a/src/pybel/examples/homology_example.py b/src/pybel/examples/homology_example.py
index 36924e05..88456155 100644
--- a/src/pybel/examples/homology_example.py
+++ b/src/pybel/examples/homology_example.py
@@ -89,11 +89,11 @@ homology_graph.add_orthology(human_mapk1_gene, fly_mapk1)
 
 
 homology_graph.add_increases(
-    u=mouse_csf1_protein,
-    v=mouse_mapk1_protein,
+    source=mouse_csf1_protein,
+    target=mouse_mapk1_protein,
     citation='10452980',
     evidence='M-CSF triggers the activation of extracellular signal-regulated protein kinases (ERK)-1/2.',
-    object_modifier=activity('kin'),
+    target_modifier=activity('kin'),
     annotations={'Species': '10090'},
 )
 
diff --git a/src/pybel/examples/sialic_acid_example.py b/src/pybel/examples/sialic_acid_example.py
index a4a9c157..77981f25 100644
--- a/src/pybel/examples/sialic_acid_example.py
+++ b/src/pybel/examples/sialic_acid_example.py
@@ -95,7 +95,7 @@ sialic_acid_graph.add_increases(
     citation=citation,
     annotations={'Species': '9606', 'Confidence': 'High'},
     evidence=evidence_1,
-    object_modifier=activity(),
+    target_modifier=activity(),
 )
 
 sialic_acid_graph.add_increases(
@@ -104,7 +104,7 @@ sialic_acid_graph.add_increases(
     citation=citation,
     annotations={'Species': '9606', 'Confidence': 'High'},
     evidence=evidence_1,
-    subject_modifier=activity(),
+    source_modifier=activity(),
 )
 
 sialic_acid_graph.add_directly_increases(
@@ -113,8 +113,8 @@ sialic_acid_graph.add_directly_increases(
     citation=citation,
     evidence=evidence_1,
     annotations={'Species': '9606', 'Confidence': 'High'},
-    subject_modifier=activity(),
-    object_modifier=activity('phos'),
+    source_modifier=activity(),
+    target_modifier=activity('phos'),
 )
 
 sialic_acid_graph.add_directly_increases(
@@ -123,8 +123,8 @@ sialic_acid_graph.add_directly_increases(
     citation=citation,
     evidence=evidence_1,
     annotations={'Species': '9606', 'Confidence': 'High'},
-    subject_modifier=activity(),
-    object_modifier=activity('phos'),
+    source_modifier=activity(),
+    target_modifier=activity('phos'),
 )
 
 sialic_acid_graph.add_directly_decreases(
@@ -133,8 +133,8 @@ sialic_acid_graph.add_directly_decreases(
     citation=citation,
     evidence=evidence_2,
     annotations={'Species': '9606', 'Confidence': 'High'},
-    subject_modifier=activity(),
-    object_modifier=activity(),
+    source_modifier=activity(),
+    target_modifier=activity(),
 )
 
 sialic_acid_graph.add_directly_decreases(
@@ -143,8 +143,8 @@ sialic_acid_graph.add_directly_decreases(
     citation=citation,
     evidence=evidence_2,
     annotations={'Species': '9606', 'Confidence': 'High'},
-    subject_modifier=activity(),
-    object_modifier=activity(),
+    source_modifier=activity(),
+    target_modifier=activity(),
 )
 
 sialic_acid_graph.add_increases(
@@ -153,8 +153,8 @@ sialic_acid_graph.add_increases(
     citation=citation,
     evidence=evidence_2,
     annotations={'Species': '9606', 'Confidence': 'Low'},
-    subject_modifier=activity(),
-    object_modifier=activity(),
+    source_modifier=activity(),
+    target_modifier=activity(),
 )
 
 sialic_acid_graph.add_increases(
@@ -163,6 +163,6 @@ sialic_acid_graph.add_increases(
     citation=citation,
     evidence=evidence_2,
     annotations={'Species': '9606', 'Confidence': 'Low'},
-    subject_modifier=activity(),
-    object_modifier=activity(),
+    source_modifier=activity(),
+    target_modifier=activity(),
 )
diff --git a/src/pybel/examples/tloc_example.py b/src/pybel/examples/tloc_example.py
index 54907686..fa83ccb5 100644
--- a/src/pybel/examples/tloc_example.py
+++ b/src/pybel/examples/tloc_example.py
@@ -49,7 +49,7 @@ ras_tloc_graph.add_directly_increases(
     ras_farn,
     evidence=evidence,
     citation=pmid,
-    subject_modifier=activity('cat'),
+    source_modifier=activity('cat'),
 )
 
 ras_tloc_graph.add_directly_increases(
@@ -57,7 +57,7 @@ ras_tloc_graph.add_directly_increases(
     ras,
     evidence=evidence,
     citation=pmid,
-    object_modifier=translocation(
+    target_modifier=translocation(
         from_loc=Entity(namespace='GO', name='intracellular', identifier='GO:0005622'),
         to_loc=Entity(namespace='GO', name='plasma membrane', identifier='GO:0005886'),
     ),
diff --git a/src/pybel/exceptions.py b/src/pybel/exceptions.py
index a72b3fab..73f4ff30 100644
--- a/src/pybel/exceptions.py
+++ b/src/pybel/exceptions.py
@@ -1,7 +1,408 @@
 # -*- coding: utf-8 -*-
 
-"""This module contains base exceptions that are shared through the package."""
+"""This module contains base exceptions that are shared through the package.
+
+A message for "General Parser Failure" is displayed when a problem was caused due to an unforeseen error. The line
+number and original statement are printed for the user to debug.
+"""
+
+from .utils import ensure_quotes
 
 
 class PyBELWarning(Exception):
     """The base class for warnings during compilation from which PyBEL can recover."""
+
+
+class BELParserWarning(PyBELWarning):
+    """The base PyBEL parser exception, which holds the line and position where a parsing problem occurred."""
+
+    def __init__(self, line_number: int, line: str, position: int, *args):
+        """Initialize the BEL parser warning.
+
+        :param line_number: The line number on which this warning occurred
+        :param line: The content of the line
+        :param position: The position within the line where the warning occurred
+        """
+        super().__init__(line_number, line, position, *args)
+        self.line_number = line_number
+        self.line = line
+        self.position = position
+
+    def __str__(self):
+        return 'General Parser Failure on line {} at pos {}: {}'.format(self.line_number, self.position, self.line)
+
+
+class BELSyntaxError(BELParserWarning, SyntaxError):
+    """For general syntax errors."""
+
+
+class InconsistentDefinitionError(BELParserWarning):
+    """Base PyBEL error for redefinition."""
+
+    def __init__(self, line_number: int, line: str, position: int, definition: str):
+        super(InconsistentDefinitionError, self).__init__(line_number, line, position, definition)
+        self.definition = definition
+
+    def __str__(self):
+        return 'Tried to redefine {} with: {}'.format(self.definition, self.line)
+
+
+class RedefinedNamespaceError(InconsistentDefinitionError):
+    """Raised when a namespace is redefined."""
+
+
+class RedefinedAnnotationError(InconsistentDefinitionError):
+    """Raised when an annotation is redefined."""
+
+
+# Naming Warnings
+
+class NameWarning(BELParserWarning):
+    """The base class for errors related to nomenclature."""
+
+    def __init__(self, line_number: int, line: str, position: int, name: str, *args):
+        """Build a warning wrapping a given name."""
+        super().__init__(line_number, line, position, name, *args)
+        self.name = name
+
+
+class NakedNameWarning(NameWarning):
+    """Raised when there is an identifier without a namespace. Enable lenient mode to suppress."""
+
+    def __str__(self):
+        return '"{}" should be qualified with a valid namespace'.format(self.name)
+
+
+class MissingDefaultNameWarning(NameWarning):
+    """Raised if reference to value not in default namespace."""
+
+    def __str__(self):
+        return '"{}" is not in the default namespace'.format(self.name)
+
+
+class NamespaceIdentifierWarning(NameWarning):
+    """The base class for warnings related to namespace:name identifiers."""
+
+    def __init__(self, line_number: int, line: str, position: int, namespace: str, name: str):
+        """Initialize the namespace identifier warning.
+
+        :param line_number: The line number of the line that caused the exception
+        :param line: The line that caused the exception
+        :param position: The line's position of the exception
+        :param namespace: The namespace of the identifier
+        :param name: The name of the identifier
+        """
+        super(NamespaceIdentifierWarning, self).__init__(line_number, line, position, name, namespace)
+        self.namespace = namespace
+
+
+class UndefinedNamespaceWarning(NamespaceIdentifierWarning):
+    """Raised if reference made to undefined namespace."""
+
+    def __str__(self):
+        return '"{}" is not a defined namespace'.format(self.namespace)
+
+
+class MissingNamespaceNameWarning(NamespaceIdentifierWarning):
+    """Raised if reference to value not in namespace."""
+
+    def __str__(self):
+        return '"{}" is not in the {} namespace'.format(self.name, self.namespace)
+
+
+class MissingNamespaceRegexWarning(NamespaceIdentifierWarning):
+    """Raised if reference not matching regex."""
+
+    def __str__(self):
+        return '''"{}" doesn't match the regex for {} namespace'''.format(self.name, self.namespace)
+
+
+class AnnotationWarning(BELParserWarning):
+    """Base exception for annotation warnings."""
+
+    def __init__(self, line_number, line, position, annotation, *args):
+        """Build an AnnotationWarning.
+
+        :param int line_number: The line number on which the warning occurred
+        :param str line: The line on which the warning occurred
+        :param int position: The position in the line that caused the warning
+        :param str annotation: The annotation name that caused the warning
+        """
+        super(AnnotationWarning, self).__init__(line_number, line, position, annotation, *args)
+        self.annotation = annotation
+
+
+class UndefinedAnnotationWarning(AnnotationWarning):
+    """Raised when an undefined annotation is used."""
+
+    def __str__(self):
+        return '''"{}" is not defined'''.format(self.annotation)
+
+
+class MissingAnnotationKeyWarning(AnnotationWarning):
+    """Raised when trying to unset an annotation that is not set."""
+
+    def __str__(self):
+        return '''"{}" is not set, so it can't be unset'''.format(self.annotation)
+
+
+class AnnotationIdentifierWarning(AnnotationWarning):
+    """Base exception for annotation:value pairs."""
+
+    def __init__(self, line_number, line, position, annotation, value):
+        super(AnnotationIdentifierWarning, self).__init__(line_number, line, position, annotation, value)
+        self.value = value
+
+
+class IllegalAnnotationValueWarning(AnnotationIdentifierWarning):
+    """Raised when an annotation has a value that does not belong to the original set of valid annotation values."""
+
+    def __str__(self):
+        return '"{}" is not defined in the {} annotation'.format(self.value, self.annotation)
+
+
+class MissingAnnotationRegexWarning(AnnotationIdentifierWarning):
+    """Raised if annotation doesn't match regex."""
+
+    def __str__(self):
+        return '''"{}" doesn't match the regex for {} annotation'''.format(self.value, self.annotation)
+
+
+# Provenance Warnings
+
+class VersionFormatWarning(BELParserWarning):
+    """Raised if the version string doesn't adhere to semantic versioning or ``YYYYMMDD`` format."""
+
+    def __init__(self, line_number, line, position, version_string):
+        super(VersionFormatWarning, self).__init__(line_number, line, position, version_string)
+        self.version_string = version_string
+
+    def __str__(self):
+        return (
+            'Version string "{}" neither is a date like YYYYMMDD nor adheres to semantic versioning.'
+            ' See http://semver.org/'.format(self.version_string)
+        )
+
+
+class MetadataException(BELParserWarning):
+    """Base exception for issues with document metadata."""
+
+    def __str__(self):
+        return 'Invalid metadata - "{}"'.format(self.line)
+
+
+class MalformedMetadataException(MetadataException):
+    """Raised when an invalid metadata line is encountered."""
+
+
+class InvalidMetadataException(BELParserWarning):
+    """Raised when an incorrect document metadata key is used.
+
+    .. hint:: Valid document metadata keys are:
+
+        - ``Authors``
+        - ``ContactInfo``
+        - ``Copyright``
+        - ``Description``
+        - ``Disclaimer``
+        - ``Licenses``
+        - ``Name``
+        - ``Version``
+
+    .. seealso:: BEL specification on the `properties section <http://openbel.org/language/web/version_1.0/
+                 bel_specification_version_1.0.html#_properties_section>`_
+    """
+
+    def __init__(self, line_number, line, position, key, value):
+        super(InvalidMetadataException, self).__init__(line_number, line, position, key, value)
+        self.key = key
+        self.value = value
+
+    def __str__(self):
+        return 'Invalid document metadata key: {}'.format(self.key)
+
+
+class MissingMetadataException(BELParserWarning):
+    """Raised when a BEL Script is missing critical metadata."""
+
+    def __init__(self, line_number, line, position, key):
+        super(MissingMetadataException, self).__init__(line_number, line, position, key)
+        self.key = key
+
+    def __str__(self):
+        return 'Missing required document metadata: {}'.format(self.key)
+
+    @staticmethod
+    def make(key: str):
+        """Build an instance of this class with auto-filled dummy values.
+
+        Unlike normal classes, polymorphism on __init__ can't be used for exceptions when pickling/unpickling.
+        """
+        return MissingMetadataException(0, '', 0, key)
+
+
+class InvalidCitationLengthException(BELParserWarning):
+    """Base exception raised when the format for a citation is wrong."""
+
+
+class CitationTooShortException(InvalidCitationLengthException):
+    """Raised when a citation does not have the minimum of {type, name, reference}."""
+
+    def __str__(self):
+        return "Citation is missing required fields: {}".format(self.line)
+
+
+class CitationTooLongException(InvalidCitationLengthException):
+    """Raised when a citation has more than the allowed entries, {type, name, reference, date, authors, comments}."""
+
+    def __str__(self):
+        return "Citation contains too many entries: {}".format(self.line)
+
+
+class MissingCitationException(BELParserWarning):
+    """Raised when trying to parse a BEL statement, but no citation is currently set.
+
+    This might be due to a previous error in the formatting of a citation.
+
+    Though it's not a best practice, some BEL curators set other annotations before the citation. If this is the case
+    in your BEL document, and you're *absolutely* sure that all ``UNSET`` statements are correctly written, you can use
+    ``citation_clearing=True`` as a keyword argument in any of the IO functions in :func:`pybel.from_lines`,
+    :func:`pybel.from_url`, or :func:`pybel.from_path`.
+    """
+
+    def __str__(self):
+        return "Missing citation; can't add: {}".format(self.line)
+
+
+class MissingSupportWarning(BELParserWarning):
+    """Raised when trying to parse a BEL statement, but no evidence is currently set.
+
+    All BEL statements must be qualified with evidence.
+
+    If your data is serialized from a database and provenance information is not readily
+    accessible, consider referencing the publication for the database, or a url pointing to the data from either
+    a programmatically or human-readable endpoint.
+    """
+
+    def __str__(self):
+        return "Missing evidence; can't add: {}".format(self.line)
+
+
+class MissingAnnotationWarning(BELParserWarning):
+    """Raised when trying to parse a BEL statement and a required annotation is not present."""
+
+    def __init__(self, line_number, line, position, required_annotations):
+        super(MissingAnnotationWarning, self).__init__(line_number, line, position, required_annotations)
+        self.required_annotations = required_annotations
+
+    def __str__(self):
+        return 'Missing annotations: {}'.format(', '.join(sorted(self.required_annotations)))
+
+
+class InvalidCitationType(BELParserWarning):
+    """Raised when a citation is set with an incorrect type.
+
+    .. hint:: Valid citation types include:
+
+        - ``Book``
+        - ``PubMed``
+        - ``Journal``
+        - ``Online Resource``
+        - ``URL``
+        - ``DOI``
+        - ``Other``
+
+    .. seealso:: OpenBEL wiki on `citations <https://wiki.openbel.org/display/BELNA/Citation>`_
+    """
+
+    def __init__(self, line_number, line, position, citation_type):
+        super(InvalidCitationType, self).__init__(line_number, line, position, citation_type)
+        self.citation_type = citation_type
+
+    def __str__(self):
+        return '"{}" is not a valid citation type'.format(self.citation_type)
+
+
+class InvalidPubMedIdentifierWarning(BELParserWarning):
+    """Raised when a citation is set whose type is ``PubMed`` but whose database identifier is not a valid integer."""
+
+    def __init__(self, line_number, line, position, reference):
+        super(InvalidPubMedIdentifierWarning, self).__init__(line_number, line, position, reference)
+        self.reference = reference
+
+    def __str__(self):
+        return '"{}" is not a valid PubMed identifier'.format(self.reference)
+
+
+# BEL Syntax Warnings
+
+class MalformedTranslocationWarning(BELParserWarning):
+    """Raised when there is a translocation statement without location information."""
+
+    def __init__(self, line_number, line, position, tokens):
+        super(MalformedTranslocationWarning, self).__init__(line_number, line, position, tokens)
+        self.tokens = tokens
+
+    def __str__(self):
+        return 'Unqualified translocation: {} {}'.format(self.line, self.tokens)
+
+
+class PlaceholderAminoAcidWarning(BELParserWarning):
+    """Raised when an invalid amino acid code is given.
+
+    One example might be the usage of X, which is a colloquial signifier for a truncation in a given position. Text
+    mining efforts for knowledge extraction make this mistake often. X might also signify a placeholder amino acid.
+    """
+
+    def __init__(self, line_number, line, position, code):
+        super(PlaceholderAminoAcidWarning, self).__init__(line_number, line, position, code)
+        self.code = code
+
+    def __str__(self):
+        return 'Placeholder amino acid found: {}'.format(self.code)
+
+
+class NestedRelationWarning(BELParserWarning):
+    """Raised when encountering a nested statement.
+
+    See our the docs for an explanation of why we explicitly do not support nested statements.
+    """
+
+    def __str__(self):
+        return 'Nesting is not supported. Split this statement: {}'.format(self.line)
+
+
+# Semantic Warnings
+
+class InvalidEntity(BELParserWarning):
+    """Raised when using a non-entity name for a name."""
+
+    def __init__(self, line_number, line, position, namespace, name):
+        super().__init__(line_number, line, position, namespace, name)
+        self.namespace = namespace
+        self.name = name
+
+    def __str__(self):
+        return '{}:{} should not be coded as an entity'.format(self.namespace, ensure_quotes(self.name))
+
+
+class InvalidFunctionSemantic(BELParserWarning):
+    """Raised when an invalid function is used for a given node.
+
+    For example, an HGNC symbol for a protein-coding gene YFG cannot be referenced as an miRNA with ``m(HGNC:YFG)``
+    """
+
+    def __init__(self, line_number, line, position, func, namespace, name, allowed_functions):
+        super().__init__(line_number, line, position, func, namespace, name, allowed_functions)
+        self.func = func
+        self.namespace = namespace
+        self.name = name
+        self.allowed_functions = allowed_functions
+
+    def __str__(self):
+        return "{} {}:{} should be encoded as one of: {}".format(
+            self.func,
+            self.namespace,
+            ensure_quotes(self.name),
+            ', '.join(self.allowed_functions),
+        )
diff --git a/src/pybel/grounding.py b/src/pybel/grounding.py
index d3d2c4b4..473f08cb 100644
--- a/src/pybel/grounding.py
+++ b/src/pybel/grounding.py
@@ -64,8 +64,8 @@ from tqdm import tqdm
 
 from pybel.constants import (
     ACTIVITY, ANNOTATIONS, CONCEPT, EFFECT, FREE_ANNOTATIONS, FROM_LOC, FUSION, GMOD, IDENTIFIER, KIND, LOCATION,
-    MEMBERS, MODIFIER, NAME, NAMESPACE, OBJECT, PARTNER_3P, PARTNER_5P, PMOD, PRODUCTS, REACTANTS, SUBJECT, TO_LOC,
-    TRANSLOCATION, VARIANTS,
+    MEMBERS, MODIFIER, NAME, NAMESPACE, PARTNER_3P, PARTNER_5P, PMOD, PRODUCTS, REACTANTS, SOURCE_MODIFIER,
+    TARGET_MODIFIER, TO_LOC, TRANSLOCATION, VARIANTS,
 )
 from pybel.dsl import BaseConcept
 from pybel.io import from_nodelink, to_nodelink
@@ -154,8 +154,8 @@ def ground_nodelink(graph_nodelink_dict) -> None:
     name = graph_nodelink_dict.get('graph', {}).get('name', 'graph')
 
     for data in tqdm(graph_nodelink_dict['links'], desc='grounding edges in {}'.format(name)):
-        _process_edge_side(data.get(SUBJECT))
-        _process_edge_side(data.get(OBJECT))
+        _process_edge_side(data.get(SOURCE_MODIFIER))
+        _process_edge_side(data.get(TARGET_MODIFIER))
         if ANNOTATIONS in data:
             _process_annotations(data)
 
diff --git a/src/pybel/io/cx.py b/src/pybel/io/cx.py
index a4f645ee..cf8d2b14 100644
--- a/src/pybel/io/cx.py
+++ b/src/pybel/io/cx.py
@@ -25,10 +25,9 @@ from networkx.utils import open_file
 
 from ..canonicalize import calculate_canonical_name
 from ..constants import (
-    ANNOTATIONS, CITATION, EVIDENCE, FUSION, GRAPH_ANNOTATION_LIST,
-    GRAPH_ANNOTATION_PATTERN, GRAPH_ANNOTATION_URL, GRAPH_METADATA, GRAPH_NAMESPACE_PATTERN, GRAPH_NAMESPACE_URL,
-    MEMBERS, NAME, OBJECT, PARTNER_3P, PARTNER_5P, PRODUCTS, RANGE_3P, RANGE_5P, REACTANTS,
-    RELATION, SUBJECT, UNQUALIFIED_EDGES, VARIANTS,
+    ANNOTATIONS, CITATION, EVIDENCE, FUSION, GRAPH_ANNOTATION_LIST, GRAPH_ANNOTATION_PATTERN, GRAPH_ANNOTATION_URL,
+    GRAPH_METADATA, GRAPH_NAMESPACE_PATTERN, GRAPH_NAMESPACE_URL, MEMBERS, NAME, PARTNER_3P, PARTNER_5P, PRODUCTS,
+    RANGE_3P, RANGE_5P, REACTANTS, RELATION, SOURCE_MODIFIER, TARGET_MODIFIER, UNQUALIFIED_EDGES, VARIANTS,
 )
 from ..dsl import BaseAbundance, BaseEntity
 from ..struct import BELGraph
@@ -52,6 +51,9 @@ log = logging.getLogger(__name__)
 CX_NODE_NAME = 'label'
 NDEX_SOURCE_FORMAT = "ndex:sourceFormat"
 
+NDEX_SOURCE_MODIFIER = 'sourceModifier'
+NDEX_TARGET_MODIFIER = 'targetModifier'
+
 
 def _cx_to_dict(list_of_dicts: List[Dict], key_tag: str = 'k', value_tag: str = 'v') -> Dict:
     """Convert a CX list of dictionaries to a flat dictionary."""
@@ -205,19 +207,19 @@ def to_cx(graph: BELGraph) -> List[Dict]:  # noqa: C901
                     'd': 'list_of_string',
                 })
 
-        if SUBJECT in d:
-            for k, v in flatten_dict(d[SUBJECT]).items():
+        if SOURCE_MODIFIER in d:
+            for k, v in flatten_dict(d[SOURCE_MODIFIER]).items():
                 edge_attributes_entry.append({
                     'po': edge_index,
-                    'n': '{}_{}'.format(SUBJECT, k),
+                    'n': '{}_{}'.format(NDEX_SOURCE_MODIFIER, k),
                     'v': v,
                 })
 
-        if OBJECT in d:
-            for k, v in flatten_dict(d[OBJECT]).items():
+        if TARGET_MODIFIER in d:
+            for k, v in flatten_dict(d[TARGET_MODIFIER]).items():
                 edge_attributes_entry.append({
                     'po': edge_index,
-                    'n': '{}_{}'.format(OBJECT, k),
+                    'n': '{}_{}'.format(NDEX_TARGET_MODIFIER, k),
                     'v': v,
                 })
 
@@ -515,10 +517,10 @@ def from_cx(cx: List[Dict]) -> BELGraph:  # noqa: C901
             if key.startswith(CITATION):
                 vl = _after_underscore(key)
                 edge_citation[eid][vl] = value
-            elif key.startswith(SUBJECT):
+            elif key.startswith(NDEX_SOURCE_MODIFIER):
                 vl = _after_underscore(key)
                 edge_subject[eid][vl] = value
-            elif key.startswith(OBJECT):
+            elif key.startswith(NDEX_TARGET_MODIFIER):
                 vl = _after_underscore(key)
                 edge_object[eid][vl] = value
             elif key == EVIDENCE:
@@ -530,10 +532,10 @@ def from_cx(cx: List[Dict]) -> BELGraph:  # noqa: C901
         edge_data_pp[eid][CITATION] = data
 
     for eid, data in edge_subject.items():
-        edge_data_pp[eid][SUBJECT] = expand_dict(data)
+        edge_data_pp[eid][SOURCE_MODIFIER] = expand_dict(data)
 
     for eid, data in edge_object.items():
-        edge_data_pp[eid][OBJECT] = expand_dict(data)
+        edge_data_pp[eid][TARGET_MODIFIER] = expand_dict(data)
 
     for eid in edge_relation:
         if eid in edge_annotations:  # FIXME stick this in edge_data.items() iteration
@@ -549,8 +551,8 @@ def from_cx(cx: List[Dict]) -> BELGraph:  # noqa: C901
                 relation=edge_relation[eid],
                 citation=edge_data_pp[eid][CITATION],
                 evidence=edge_data_pp[eid][EVIDENCE],
-                subject_modifier=edge_data_pp[eid].get(SUBJECT),
-                object_modifier=edge_data_pp[eid].get(OBJECT),
+                source_modifier=edge_data_pp[eid].get(SOURCE_MODIFIER),
+                target_modifier=edge_data_pp[eid].get(TARGET_MODIFIER),
                 annotations=edge_data_pp[eid].get(ANNOTATIONS),
             )
         elif edge_relation[eid] in UNQUALIFIED_EDGES:
diff --git a/src/pybel/io/graphdati.py b/src/pybel/io/graphdati.py
index df6b97dc..5b437af9 100644
--- a/src/pybel/io/graphdati.py
+++ b/src/pybel/io/graphdati.py
@@ -19,8 +19,8 @@ from tqdm import tqdm
 from .jgif import NAMESPACE_TO_PATTERN
 from ..canonicalize import edge_to_tuple
 from ..constants import (
-    ANNOTATIONS, CITATION, CITATION_DB, CITATION_IDENTIFIER, CITATION_TYPE_PUBMED, CITATION_TYPE_URL, EVIDENCE,
-    RELATION, UNQUALIFIED_EDGES,
+    ANNOTATIONS, CITATION, CITATION_TYPE_PUBMED, CITATION_TYPE_URL, EVIDENCE, IDENTIFIER, NAMESPACE, RELATION,
+    UNQUALIFIED_EDGES,
 )
 from ..parser import BELParser
 from ..struct import BELGraph
@@ -192,7 +192,7 @@ def _get_citation(d):
     if citation is None:
         rv['reference'] = 'Not Available'
     else:
-        rv['database'] = dict(name=citation[CITATION_DB], id=citation[CITATION_IDENTIFIER])
+        rv['database'] = dict(name=citation[NAMESPACE], id=citation[IDENTIFIER])
     return rv
 
 
diff --git a/src/pybel/io/hipathia.py b/src/pybel/io/hipathia.py
index 397d1d3f..0e17ea0f 100644
--- a/src/pybel/io/hipathia.py
+++ b/src/pybel/io/hipathia.py
@@ -74,7 +74,7 @@ from typing import List, Optional, Set, Tuple, Union
 import networkx as nx
 import pandas as pd
 
-from ..constants import CAUSAL_INCREASE_RELATIONS, CAUSAL_POLAR_RELATIONS, IS_A, RELATION
+from ..constants import CAUSAL_INCREASE_RELATIONS, CAUSAL_POLAR_RELATIONS, CITATION_TYPE_OTHER, IS_A, RELATION
 from ..dsl import ComplexAbundance, Protein, hgnc
 from ..struct import BELGraph
 
@@ -189,9 +189,9 @@ def from_hipathia_dfs(name: str, att_df: pd.DataFrame, sif_df: pd.DataFrame) ->
     sif_df['target'] = sif_df['target'].map(_clean_name).map(x.get)
     for source, relation, target in sif_df.values:
         if relation == 'activation':
-            graph.add_increases(source, target, citation='', evidence='')
+            graph.add_increases(source, target, citation=(CITATION_TYPE_OTHER, 'HiPathia'), evidence='')
         elif relation == 'inhibition':
-            graph.add_decreases(source, target, citation='', evidence='')
+            graph.add_decreases(source, target, citation=(CITATION_TYPE_OTHER, 'HiPathia'), evidence='')
         else:
             raise ValueError('unknown relation: {relation}'.format(relation=relation))
 
diff --git a/src/pybel/io/jgif.py b/src/pybel/io/jgif.py
index 43070a60..757c1a56 100644
--- a/src/pybel/io/jgif.py
+++ b/src/pybel/io/jgif.py
@@ -23,8 +23,8 @@ from ..constants import (
     ANNOTATIONS, CITATION, EVIDENCE, METADATA_AUTHORS, METADATA_CONTACT,
     METADATA_INSERT_KEYS, METADATA_LICENSES, RELATION, UNQUALIFIED_EDGES,
 )
+from ..exceptions import NakedNameWarning, UndefinedNamespaceWarning
 from ..parser import BELParser
-from ..parser.exc import NakedNameWarning, UndefinedNamespaceWarning
 from ..struct import BELGraph
 from ..version import get_version
 
diff --git a/src/pybel/io/line_utils.py b/src/pybel/io/line_utils.py
index ffed1c06..8767d96d 100644
--- a/src/pybel/io/line_utils.py
+++ b/src/pybel/io/line_utils.py
@@ -13,12 +13,12 @@ from tqdm import tqdm
 
 from bel_resources import ResourceError, split_file_to_annotations_and_definitions
 from ..constants import INVERSE_DOCUMENT_KEYS, REQUIRED_METADATA
-from ..manager import Manager
-from ..parser import BELParser, MetadataParser
-from ..parser.exc import (
+from ..exceptions import (
     BELParserWarning, BELSyntaxError, InconsistentDefinitionError, MalformedMetadataException, MissingMetadataException,
     PlaceholderAminoAcidWarning, VersionFormatWarning,
 )
+from ..manager import Manager
+from ..parser import BELParser, MetadataParser
 from ..struct.graph import BELGraph
 
 __all__ = [
diff --git a/src/pybel/io/neo4j.py b/src/pybel/io/neo4j.py
index b4e9701c..cd1f51f7 100644
--- a/src/pybel/io/neo4j.py
+++ b/src/pybel/io/neo4j.py
@@ -5,8 +5,7 @@
 from tqdm import tqdm
 
 from ..constants import (
-    ANNOTATIONS, CITATION, CITATION_DB, CITATION_IDENTIFIER, EVIDENCE, FUSION, MEMBERS, NAMESPACE, OBJECT, RELATION,
-    SUBJECT, VARIANTS,
+    ANNOTATIONS, CITATION, EVIDENCE, FUSION, MEMBERS, NAMESPACE, RELATION, SOURCE_MODIFIER, TARGET_MODIFIER, VARIANTS,
 )
 from ..utils import flatten_dict
 
@@ -81,12 +80,12 @@ def to_neo4j(graph, neo_connection, use_tqdm: bool = False):
 
         citation = d.pop(CITATION, None)
         if citation:
-            attrs[CITATION] = '{}:{}'.format(citation[CITATION_DB], citation[CITATION_IDENTIFIER])
+            attrs[CITATION] = citation.curie
 
         if EVIDENCE in d:
             attrs[EVIDENCE] = d[EVIDENCE]
 
-        for side in (SUBJECT, OBJECT):
+        for side in (SOURCE_MODIFIER, TARGET_MODIFIER):
             side_data = d.get(side)
             if side_data:
                 attrs.update(flatten_dict(side_data, parent_key=side))
diff --git a/src/pybel/io/nodelink.py b/src/pybel/io/nodelink.py
index a8ad5ad3..1189cc44 100644
--- a/src/pybel/io/nodelink.py
+++ b/src/pybel/io/nodelink.py
@@ -13,9 +13,11 @@ from networkx.utils import open_file
 
 from .utils import ensure_version
 from ..constants import (
-    FUSION, GRAPH_ANNOTATION_LIST, MEMBERS, OBJECT, PARTNER_3P, PARTNER_5P, PRODUCTS, REACTANTS, SUBJECT,
+    CITATION, FUSION, GRAPH_ANNOTATION_LIST, MEMBERS, PARTNER_3P, PARTNER_5P, PRODUCTS, REACTANTS, SOURCE_MODIFIER,
+    TARGET_MODIFIER,
 )
 from ..dsl import BaseEntity
+from ..language import CitationDict
 from ..struct import BELGraph
 from ..struct.graph import _handle_modifier
 from ..tokens import parse_result_to_dsl
@@ -179,11 +181,14 @@ def _from_nodelink_json_helper(data: Mapping[str, Any]) -> BELGraph:
             if k not in {'source', 'target', 'key'}
         }
 
-        for side in (SUBJECT, OBJECT):
+        for side in (SOURCE_MODIFIER, TARGET_MODIFIER):
             side_data = edge_data.get(side)
             if side_data:
                 _handle_modifier(side_data)
 
+        if CITATION in edge_data:
+            edge_data[CITATION] = CitationDict(**edge_data[CITATION])
+
         graph.add_edge(u, v, key=hash_edge(u, v, edge_data), **edge_data)
 
     return graph
diff --git a/src/pybel/io/sbel.py b/src/pybel/io/sbel.py
index 386f62a6..98b682ba 100644
--- a/src/pybel/io/sbel.py
+++ b/src/pybel/io/sbel.py
@@ -9,7 +9,8 @@ from typing import Any, Iterable, List, TextIO, Union
 from networkx.utils import open_file
 
 from .nodelink import _augment_node, _fix_annotation_list
-from ..constants import OBJECT, SUBJECT
+from ..constants import CITATION, SOURCE_MODIFIER, TARGET_MODIFIER
+from ..language import CitationDict
 from ..struct.graph import BELGraph, _handle_modifier
 from ..tokens import parse_result_to_dsl
 from ..utils import hash_edge
@@ -97,10 +98,12 @@ def add_sbel(graph: BELGraph, it: Iterable[SBEL]) -> None:
             for k, v in data.items()
             if k not in {'source', 'target', 'key'}
         }
-        for side in (SUBJECT, OBJECT):
+        for side in (SOURCE_MODIFIER, TARGET_MODIFIER):
             side_data = edge_data.get(side)
             if side_data:
                 _handle_modifier(side_data)
+        if CITATION in edge_data:
+            edge_data[CITATION] = CitationDict(**edge_data[CITATION])
         graph.add_edge(u, v, key=hash_edge(u, v, edge_data), **edge_data)
 
 
diff --git a/src/pybel/io/triples/converters.py b/src/pybel/io/triples/converters.py
index 75e23e89..dcd8d1dd 100644
--- a/src/pybel/io/triples/converters.py
+++ b/src/pybel/io/triples/converters.py
@@ -8,7 +8,7 @@ from typing import Dict, Tuple
 from ...constants import (
     ACTIVITY, ASSOCIATION, CAUSAL_DECREASE_RELATIONS, CAUSAL_INCREASE_RELATIONS, CAUSAL_RELATIONS, CAUSES_NO_CHANGE,
     CORRELATIVE_RELATIONS, DECREASES, DEGRADATION, DIRECTLY_DECREASES, DIRECTLY_INCREASES, EQUIVALENT_TO, HAS_PRODUCT,
-    HAS_REACTANT, HAS_VARIANT, INCREASES, IS_A, MODIFIER, OBJECT, PART_OF, REGULATES, RELATION,
+    HAS_REACTANT, HAS_VARIANT, INCREASES, IS_A, MODIFIER, PART_OF, REGULATES, RELATION, TARGET_MODIFIER,
 )
 from ...dsl import (
     Abundance, BaseAbundance, BaseEntity, BiologicalProcess, CentralDogma, ComplexAbundance, Gene, MicroRna,
@@ -287,8 +287,8 @@ class RegulatesAmountConverter(TypedConverter):
     @classmethod
     def predicate(cls, u: BaseEntity, v: BaseEntity, key: str, edge_data: EdgeData) -> bool:
         """Test a BEL edge."""
-        object_modifier = edge_data.get(OBJECT)
-        return edge_data[RELATION] == cls.relation and (not object_modifier or not object_modifier.get(MODIFIER))
+        target_modifier = edge_data.get(TARGET_MODIFIER)
+        return edge_data[RELATION] == cls.relation and (not target_modifier or not target_modifier.get(MODIFIER))
 
 
 class IncreasesAmountConverter(RegulatesAmountConverter):
@@ -321,11 +321,11 @@ class RegulatesDegradationConverter(TypedConverter):
     @classmethod
     def predicate(cls, u: BaseEntity, v: BaseEntity, key: str, edge_data: EdgeData) -> bool:
         """Test a BEL edge."""
-        object_modifier = edge_data.get(OBJECT)
+        target_modifier = edge_data.get(TARGET_MODIFIER)
         return (
             edge_data[RELATION] == cls.relation
-            and object_modifier
-            and object_modifier.get(MODIFIER) == DEGRADATION
+            and target_modifier
+            and target_modifier.get(MODIFIER) == DEGRADATION
         )
 
 
@@ -359,11 +359,11 @@ class RegulatesActivityConverter(TypedConverter):
     @classmethod
     def predicate(cls, u: BaseEntity, v: BaseEntity, key: str, edge_data: EdgeData) -> bool:
         """Test a BEL edge."""
-        object_modifier = edge_data.get(OBJECT)
+        target_modifier = edge_data.get(TARGET_MODIFIER)
         return (
             edge_data[RELATION] == cls.relation
-            and object_modifier
-            and object_modifier.get(MODIFIER) == ACTIVITY
+            and target_modifier
+            and target_modifier.get(MODIFIER) == ACTIVITY
         )
 
 
diff --git a/src/pybel/io/umbrella_nodelink.py b/src/pybel/io/umbrella_nodelink.py
index b3cb62b6..a4071c14 100644
--- a/src/pybel/io/umbrella_nodelink.py
+++ b/src/pybel/io/umbrella_nodelink.py
@@ -27,7 +27,7 @@ from typing import Any, Mapping, TextIO, Union
 from networkx.utils import open_file
 
 from ..canonicalize import _decanonicalize_edge_node, edge_to_tuple
-from ..constants import GRAPH_ANNOTATION_LIST, OBJECT, SUBJECT
+from ..constants import GRAPH_ANNOTATION_LIST, SOURCE_MODIFIER, TARGET_MODIFIER
 from ..struct import BELGraph
 
 __all__ = [
@@ -62,8 +62,8 @@ def to_umbrella_nodelink(graph: BELGraph) -> Mapping[str, Any]:
                 chain(
                     data.copy().items(),
                     [
-                        ('source', mapping[_decanonicalize_edge_node(u, data, node_position=SUBJECT)]),
-                        ('target', mapping[_decanonicalize_edge_node(v, data, node_position=OBJECT)]),
+                        ('source', mapping[_decanonicalize_edge_node(u, data, node_position=SOURCE_MODIFIER)]),
+                        ('target', mapping[_decanonicalize_edge_node(v, data, node_position=TARGET_MODIFIER)]),
                         ('key', key),
                     ],
                 ),
diff --git a/src/pybel/language.py b/src/pybel/language.py
index e8e74e9c..9d83758f 100644
--- a/src/pybel/language.py
+++ b/src/pybel/language.py
@@ -91,7 +91,7 @@ class Entity(dict):
         )
 
     def __str__(self):  # noqa: D105
-        return self.curie
+        return self.obo if self.identifier and self.name else self.curie
 
 
 #: A dictionary of activity labels used in the ma() function in activity(p(X), ma(Y))
@@ -149,12 +149,15 @@ activities = list(activity_labels.keys())
 
 cytoplasm = Entity(name='cytoplasm', namespace='go', identifier='0005737')
 nucleus = Entity(name='nucleus', namespace='go', identifier='0005634')
+intracellular = Entity(name='intracellular', namespace='go', identifier='0005622')
+extracellular = Entity(name='extracellular space', namespace='go', identifier='0005615')
+cell_surface = Entity(name='cell surface', namespace='go', identifier='0009986')
 
 #: Maps the default BEL cellular components to Gene Ontology Cellular Components
 compartment_mapping = {
-    INTRACELLULAR: Entity(name='intracellular', namespace='go', identifier='0005622'),
-    EXTRACELLULAR: Entity(name='extracellular space', namespace='go', identifier='0005615'),
-    CELL_SURFACE: Entity(name='cell surface', namespace='go', identifier='0009986'),
+    INTRACELLULAR: intracellular,
+    EXTRACELLULAR: extracellular,
+    CELL_SURFACE: cell_surface,
     'cytoplasm': cytoplasm,
     'nucleus': nucleus,
 }
@@ -552,3 +555,16 @@ gmod_mappings = {
 
 BEL_DEFAULT_NAMESPACE_VERSION = '2.1.1'
 BEL_DEFAULT_NAMESPACE_URL = 'http://openbel.org/2.1.1.belns'  # just needs something unique... will change later
+
+
+class CitationDict(Entity):
+    """A dictionary describing a citation."""
+
+    def __init__(self, namespace: str, identifier: str, *, name: Optional[str] = None, **kwargs):
+        super().__init__(namespace=namespace, identifier=identifier, name=name)
+        self.update(kwargs)
+
+
+def citation_dict(*, namespace: str, identifier: str, db_name: Optional[str] = None, **kwargs) -> CitationDict:
+    """Make a citation dictionary."""
+    return CitationDict(namespace=namespace, identifier=identifier, name=db_name, **kwargs)
diff --git a/src/pybel/manager/cache_manager.py b/src/pybel/manager/cache_manager.py
index bf509d45..da3ea172 100644
--- a/src/pybel/manager/cache_manager.py
+++ b/src/pybel/manager/cache_manager.py
@@ -6,7 +6,6 @@ Under the hood, PyBEL caches namespace and annotation files for quick recall on
 enable this option, but can specify a database location if they choose.
 """
 
-import json
 import logging
 import time
 from itertools import chain
@@ -29,8 +28,8 @@ from .models import (
 from .query_manager import QueryManager
 from .utils import extract_shared_optional, extract_shared_required, update_insert_values
 from ..constants import (
-    ANNOTATIONS, BEL_DEFAULT_NAMESPACE, CITATION, CITATION_DB, CITATION_IDENTIFIER, CITATION_TYPE_PUBMED, EVIDENCE,
-    METADATA_INSERT_KEYS, OBJECT, RELATION, SUBJECT, UNQUALIFIED_EDGES, belns_encodings, get_cache_connection,
+    ANNOTATIONS, BEL_DEFAULT_NAMESPACE, CITATION, CITATION_TYPE_PUBMED, EVIDENCE, IDENTIFIER, METADATA_INSERT_KEYS,
+    NAMESPACE, RELATION, SOURCE_MODIFIER, TARGET_MODIFIER, UNQUALIFIED_EDGES, belns_encodings, get_cache_connection,
 )
 from ..dsl import BaseConcept, BaseEntity
 from ..language import (
@@ -712,7 +711,7 @@ class InsertManager(NamespaceManager, LookupManager):
             elif EVIDENCE not in data or CITATION not in data:
                 continue
 
-            elif CITATION_DB not in data[CITATION] or CITATION_IDENTIFIER not in data[CITATION]:
+            elif NAMESPACE not in data[CITATION] or IDENTIFIER not in data[CITATION]:
                 continue
 
             else:
@@ -777,8 +776,8 @@ class InsertManager(NamespaceManager, LookupManager):
         """Add a qualified edge to the network."""
         citation_dict = data[CITATION]
         citation = self.get_or_create_citation(
-            db=citation_dict[CITATION_DB],
-            db_id=citation_dict[CITATION_IDENTIFIER],
+            namespace=citation_dict[NAMESPACE],
+            identifier=citation_dict[IDENTIFIER],
         )
         evidence = self.get_or_create_evidence(citation, data[EVIDENCE])
         annotations = self._get_annotation_entries_from_data(graph, data)
@@ -925,9 +924,9 @@ class InsertManager(NamespaceManager, LookupManager):
 
         edge = Edge(
             source=source,
-            source_modifier=data.get(SUBJECT),
+            source_modifier=data.get(SOURCE_MODIFIER),
             target=target,
-            target_modifier=data.get(OBJECT),
+            target_modifier=data.get(TARGET_MODIFIER),
             relation=relation,
             bel=bel,
             md5=md5,
@@ -946,30 +945,30 @@ class InsertManager(NamespaceManager, LookupManager):
     def get_or_create_citation(
         self,
         *,
-        db_id: str,
-        db: Optional[str] = None
+        identifier: str,
+        namespace: Optional[str] = None,
     ) -> Citation:
         """Create a citation if it does not exist, or return it if it does.
 
-        :param db_id: Identifier of the given citation (e.g. PubMed id)
-        :param db: Citation type (defaults to PubMed)
+        :param identifier: Identifier of the given citation (e.g. PubMed id)
+        :param namespace: Citation type (defaults to PubMed)
         """
-        if db is None:
-            db = CITATION_TYPE_PUBMED
+        if namespace is None:
+            namespace = CITATION_TYPE_PUBMED
 
-        citation_curie = '{}:{}'.format(db, db_id)
+        citation_curie = f'{namespace}:{identifier}'
         if citation_curie in self.curie_to_citation:
             citation = self.curie_to_citation[citation_curie]
             self.session.add(citation)
             return citation
 
-        citation = self.get_citation_by_reference(db, db_id)
+        citation = self.get_citation_by_reference(namespace, identifier)
 
         if citation is not None:
             self.curie_to_citation[citation_curie] = citation
             return citation
 
-        self.curie_to_citation[citation_curie] = citation = Citation(db=db, db_id=db_id)
+        self.curie_to_citation[citation_curie] = citation = Citation(db=namespace, db_id=identifier)
         self.session.add(citation)
         return citation
 
diff --git a/src/pybel/manager/citation_utils.py b/src/pybel/manager/citation_utils.py
index 26f1653e..11de63fc 100644
--- a/src/pybel/manager/citation_utils.py
+++ b/src/pybel/manager/citation_utils.py
@@ -11,7 +11,7 @@ from typing import Dict, Iterable, List, Optional, Set, Tuple, Union
 
 import requests
 
-from ..constants import CITATION, CITATION_IDENTIFIER, CITATION_TYPE_PUBMED
+from ..constants import CITATION, CITATION_TYPE_PUBMED, IDENTIFIER
 from ..struct.filters import filter_edges
 from ..struct.filters.edge_predicates import has_pubmed
 from ..struct.summary.provenance import get_pubmed_identifiers
@@ -159,7 +159,7 @@ def get_citations_by_pmids(
     unenriched_pmids = {}
 
     for pmid in pmids:
-        citation = manager.get_or_create_citation(db=CITATION_TYPE_PUBMED, db_id=pmid)
+        citation = manager.get_or_create_citation(namespace=CITATION_TYPE_PUBMED, identifier=pmid)
 
         if not citation.is_enriched:
             unenriched_pmids[pmid] = citation
@@ -232,7 +232,7 @@ def enrich_pubmed_citations(
     pmid_data, errors = get_citations_by_pmids(manager, pmids=pmids, group_size=group_size, sleep_time=sleep_time)
 
     for u, v, k in filter_edges(graph, has_pubmed):
-        pmid = graph[u][v][k][CITATION][CITATION_IDENTIFIER].strip()
+        pmid = graph[u][v][k][CITATION][IDENTIFIER].strip()
 
         if pmid not in pmid_data:
             logger.warning('Missing data for PubMed identifier: %s', pmid)
diff --git a/src/pybel/manager/models.py b/src/pybel/manager/models.py
index d09401f1..de3ffebf 100644
--- a/src/pybel/manager/models.py
+++ b/src/pybel/manager/models.py
@@ -13,12 +13,13 @@ from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import backref, relationship
 
 from ..constants import (
-    CITATION, CITATION_AUTHORS, CITATION_DATE, CITATION_DB, CITATION_DB_NAME, CITATION_FIRST_AUTHOR,
-    CITATION_IDENTIFIER, CITATION_JOURNAL, CITATION_LAST_AUTHOR, CITATION_PAGES, CITATION_TYPE_PUBMED, CITATION_VOLUME,
-    EVIDENCE, IDENTIFIER, METADATA_AUTHORS, METADATA_CONTACT, METADATA_COPYRIGHT, METADATA_DESCRIPTION,
-    METADATA_DISCLAIMER, METADATA_LICENSES, METADATA_NAME, METADATA_VERSION, NAME, NAMESPACE,
+    CITATION, CITATION_AUTHORS, CITATION_DATE, CITATION_FIRST_AUTHOR, CITATION_JOURNAL, CITATION_LAST_AUTHOR,
+    CITATION_PAGES, CITATION_TYPE_PUBMED, CITATION_VOLUME, EVIDENCE, IDENTIFIER, METADATA_AUTHORS, METADATA_CONTACT,
+    METADATA_COPYRIGHT, METADATA_DESCRIPTION, METADATA_DISCLAIMER, METADATA_LICENSES, METADATA_NAME, METADATA_VERSION,
+    NAME, NAMESPACE,
 )
 from ..io.gpickle import from_bytes, to_bytes
+from ..language import Entity
 from ..tokens import parse_result_to_dsl
 
 __all__ = [
@@ -441,16 +442,13 @@ class Citation(Base):
         :param bool include_id: If true, includes the model identifier
         :return: Citation dictionary for the recreation of a :class:`BELGraph`.
         """
-        result = {
-            CITATION_DB: self.db,
-            CITATION_IDENTIFIER: self.db_id,
-        }
+        result = Entity(namespace=self.db, identifier=self.db_id)
 
         if include_id:
             result['id'] = self.id
 
         if self.title:
-            result[CITATION_DB_NAME] = self.title
+            result[NAME] = self.title
 
         if self.journal:
             result[CITATION_JOURNAL] = self.journal
diff --git a/src/pybel/parser/exc.py b/src/pybel/parser/exc.py
deleted file mode 100644
index 03333862..00000000
--- a/src/pybel/parser/exc.py
+++ /dev/null
@@ -1,405 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""Exceptions for the BEL parser.
-
-A message for "General Parser Failure" is displayed when a problem was caused due to an unforeseen error. The line
-number and original statement are printed for the user to debug.
-"""
-
-from ..exceptions import PyBELWarning
-from ..utils import ensure_quotes
-
-
-class BELParserWarning(PyBELWarning):
-    """The base PyBEL parser exception, which holds the line and position where a parsing problem occurred."""
-
-    def __init__(self, line_number: int, line: str, position: int, *args):
-        """Initialize the BEL parser warning.
-
-        :param line_number: The line number on which this warning occurred
-        :param line: The content of the line
-        :param position: The position within the line where the warning occurred
-        """
-        super().__init__(line_number, line, position, *args)
-        self.line_number = line_number
-        self.line = line
-        self.position = position
-
-    def __str__(self):
-        return 'General Parser Failure on line {} at pos {}: {}'.format(self.line_number, self.position, self.line)
-
-
-class BELSyntaxError(BELParserWarning, SyntaxError):
-    """For general syntax errors."""
-
-
-class InconsistentDefinitionError(BELParserWarning):
-    """Base PyBEL error for redefinition."""
-
-    def __init__(self, line_number: int, line: str, position: int, definition: str):
-        super(InconsistentDefinitionError, self).__init__(line_number, line, position, definition)
-        self.definition = definition
-
-    def __str__(self):
-        return 'Tried to redefine {} with: {}'.format(self.definition, self.line)
-
-
-class RedefinedNamespaceError(InconsistentDefinitionError):
-    """Raised when a namespace is redefined."""
-
-
-class RedefinedAnnotationError(InconsistentDefinitionError):
-    """Raised when an annotation is redefined."""
-
-
-# Naming Warnings
-
-class NameWarning(BELParserWarning):
-    """The base class for errors related to nomenclature."""
-
-    def __init__(self, line_number: int, line: str, position: int, name: str, *args):
-        """Build a warning wrapping a given name."""
-        super().__init__(line_number, line, position, name, *args)
-        self.name = name
-
-
-class NakedNameWarning(NameWarning):
-    """Raised when there is an identifier without a namespace. Enable lenient mode to suppress."""
-
-    def __str__(self):
-        return '"{}" should be qualified with a valid namespace'.format(self.name)
-
-
-class MissingDefaultNameWarning(NameWarning):
-    """Raised if reference to value not in default namespace."""
-
-    def __str__(self):
-        return '"{}" is not in the default namespace'.format(self.name)
-
-
-class NamespaceIdentifierWarning(NameWarning):
-    """The base class for warnings related to namespace:name identifiers."""
-
-    def __init__(self, line_number: int, line: str, position: int, namespace: str, name: str):
-        """Initialize the namespace identifier warning.
-
-        :param line_number: The line number of the line that caused the exception
-        :param line: The line that caused the exception
-        :param position: The line's position of the exception
-        :param namespace: The namespace of the identifier
-        :param name: The name of the identifier
-        """
-        super(NamespaceIdentifierWarning, self).__init__(line_number, line, position, name, namespace)
-        self.namespace = namespace
-
-
-class UndefinedNamespaceWarning(NamespaceIdentifierWarning):
-    """Raised if reference made to undefined namespace."""
-
-    def __str__(self):
-        return '"{}" is not a defined namespace'.format(self.namespace)
-
-
-class MissingNamespaceNameWarning(NamespaceIdentifierWarning):
-    """Raised if reference to value not in namespace."""
-
-    def __str__(self):
-        return '"{}" is not in the {} namespace'.format(self.name, self.namespace)
-
-
-class MissingNamespaceRegexWarning(NamespaceIdentifierWarning):
-    """Raised if reference not matching regex."""
-
-    def __str__(self):
-        return '''"{}" doesn't match the regex for {} namespace'''.format(self.name, self.namespace)
-
-
-class AnnotationWarning(BELParserWarning):
-    """Base exception for annotation warnings."""
-
-    def __init__(self, line_number, line, position, annotation, *args):
-        """Build an AnnotationWarning.
-
-        :param int line_number: The line number on which the warning occurred
-        :param str line: The line on which the warning occurred
-        :param int position: The position in the line that caused the warning
-        :param str annotation: The annotation name that caused the warning
-        """
-        super(AnnotationWarning, self).__init__(line_number, line, position, annotation, *args)
-        self.annotation = annotation
-
-
-class UndefinedAnnotationWarning(AnnotationWarning):
-    """Raised when an undefined annotation is used."""
-
-    def __str__(self):
-        return '''"{}" is not defined'''.format(self.annotation)
-
-
-class MissingAnnotationKeyWarning(AnnotationWarning):
-    """Raised when trying to unset an annotation that is not set."""
-
-    def __str__(self):
-        return '''"{}" is not set, so it can't be unset'''.format(self.annotation)
-
-
-class AnnotationIdentifierWarning(AnnotationWarning):
-    """Base exception for annotation:value pairs."""
-
-    def __init__(self, line_number, line, position, annotation, value):
-        super(AnnotationIdentifierWarning, self).__init__(line_number, line, position, annotation, value)
-        self.value = value
-
-
-class IllegalAnnotationValueWarning(AnnotationIdentifierWarning):
-    """Raised when an annotation has a value that does not belong to the original set of valid annotation values."""
-
-    def __str__(self):
-        return '"{}" is not defined in the {} annotation'.format(self.value, self.annotation)
-
-
-class MissingAnnotationRegexWarning(AnnotationIdentifierWarning):
-    """Raised if annotation doesn't match regex."""
-
-    def __str__(self):
-        return '''"{}" doesn't match the regex for {} annotation'''.format(self.value, self.annotation)
-
-
-# Provenance Warnings
-
-class VersionFormatWarning(BELParserWarning):
-    """Raised if the version string doesn't adhere to semantic versioning or ``YYYYMMDD`` format."""
-
-    def __init__(self, line_number, line, position, version_string):
-        super(VersionFormatWarning, self).__init__(line_number, line, position, version_string)
-        self.version_string = version_string
-
-    def __str__(self):
-        return (
-            'Version string "{}" neither is a date like YYYYMMDD nor adheres to semantic versioning.'
-            ' See http://semver.org/'.format(self.version_string)
-        )
-
-
-class MetadataException(BELParserWarning):
-    """Base exception for issues with document metadata."""
-
-    def __str__(self):
-        return 'Invalid metadata - "{}"'.format(self.line)
-
-
-class MalformedMetadataException(MetadataException):
-    """Raised when an invalid metadata line is encountered."""
-
-
-class InvalidMetadataException(BELParserWarning):
-    """Raised when an incorrect document metadata key is used.
-
-    .. hint:: Valid document metadata keys are:
-
-        - ``Authors``
-        - ``ContactInfo``
-        - ``Copyright``
-        - ``Description``
-        - ``Disclaimer``
-        - ``Licenses``
-        - ``Name``
-        - ``Version``
-
-    .. seealso:: BEL specification on the `properties section <http://openbel.org/language/web/version_1.0/
-                 bel_specification_version_1.0.html#_properties_section>`_
-    """
-
-    def __init__(self, line_number, line, position, key, value):
-        super(InvalidMetadataException, self).__init__(line_number, line, position, key, value)
-        self.key = key
-        self.value = value
-
-    def __str__(self):
-        return 'Invalid document metadata key: {}'.format(self.key)
-
-
-class MissingMetadataException(BELParserWarning):
-    """Raised when a BEL Script is missing critical metadata."""
-
-    def __init__(self, line_number, line, position, key):
-        super(MissingMetadataException, self).__init__(line_number, line, position, key)
-        self.key = key
-
-    def __str__(self):
-        return 'Missing required document metadata: {}'.format(self.key)
-
-    @staticmethod
-    def make(key: str):
-        """Build an instance of this class with auto-filled dummy values.
-
-        Unlike normal classes, polymorphism on __init__ can't be used for exceptions when pickling/unpickling.
-        """
-        return MissingMetadataException(0, '', 0, key)
-
-
-class InvalidCitationLengthException(BELParserWarning):
-    """Base exception raised when the format for a citation is wrong."""
-
-
-class CitationTooShortException(InvalidCitationLengthException):
-    """Raised when a citation does not have the minimum of {type, name, reference}."""
-
-    def __str__(self):
-        return "Citation is missing required fields: {}".format(self.line)
-
-
-class CitationTooLongException(InvalidCitationLengthException):
-    """Raised when a citation has more than the allowed entries, {type, name, reference, date, authors, comments}."""
-
-    def __str__(self):
-        return "Citation contains too many entries: {}".format(self.line)
-
-
-class MissingCitationException(BELParserWarning):
-    """Raised when trying to parse a BEL statement, but no citation is currently set.
-
-    This might be due to a previous error in the formatting of a citation.
-
-    Though it's not a best practice, some BEL curators set other annotations before the citation. If this is the case
-    in your BEL document, and you're *absolutely* sure that all ``UNSET`` statements are correctly written, you can use
-    ``citation_clearing=True`` as a keyword argument in any of the IO functions in :func:`pybel.from_lines`,
-    :func:`pybel.from_url`, or :func:`pybel.from_path`.
-    """
-
-    def __str__(self):
-        return "Missing citation; can't add: {}".format(self.line)
-
-
-class MissingSupportWarning(BELParserWarning):
-    """Raised when trying to parse a BEL statement, but no evidence is currently set.
-
-    All BEL statements must be qualified with evidence.
-
-    If your data is serialized from a database and provenance information is not readily
-    accessible, consider referencing the publication for the database, or a url pointing to the data from either
-    a programmatically or human-readable endpoint.
-    """
-
-    def __str__(self):
-        return "Missing evidence; can't add: {}".format(self.line)
-
-
-class MissingAnnotationWarning(BELParserWarning):
-    """Raised when trying to parse a BEL statement and a required annotation is not present."""
-
-    def __init__(self, line_number, line, position, required_annotations):
-        super(MissingAnnotationWarning, self).__init__(line_number, line, position, required_annotations)
-        self.required_annotations = required_annotations
-
-    def __str__(self):
-        return 'Missing annotations: {}'.format(', '.join(sorted(self.required_annotations)))
-
-
-class InvalidCitationType(BELParserWarning):
-    """Raised when a citation is set with an incorrect type.
-
-    .. hint:: Valid citation types include:
-
-        - ``Book``
-        - ``PubMed``
-        - ``Journal``
-        - ``Online Resource``
-        - ``URL``
-        - ``DOI``
-        - ``Other``
-
-    .. seealso:: OpenBEL wiki on `citations <https://wiki.openbel.org/display/BELNA/Citation>`_
-    """
-
-    def __init__(self, line_number, line, position, citation_type):
-        super(InvalidCitationType, self).__init__(line_number, line, position, citation_type)
-        self.citation_type = citation_type
-
-    def __str__(self):
-        return '"{}" is not a valid citation type'.format(self.citation_type)
-
-
-class InvalidPubMedIdentifierWarning(BELParserWarning):
-    """Raised when a citation is set whose type is ``PubMed`` but whose database identifier is not a valid integer."""
-
-    def __init__(self, line_number, line, position, reference):
-        super(InvalidPubMedIdentifierWarning, self).__init__(line_number, line, position, reference)
-        self.reference = reference
-
-    def __str__(self):
-        return '"{}" is not a valid PubMed identifier'.format(self.reference)
-
-
-# BEL Syntax Warnings
-
-class MalformedTranslocationWarning(BELParserWarning):
-    """Raised when there is a translocation statement without location information."""
-
-    def __init__(self, line_number, line, position, tokens):
-        super(MalformedTranslocationWarning, self).__init__(line_number, line, position, tokens)
-        self.tokens = tokens
-
-    def __str__(self):
-        return 'Unqualified translocation: {} {}'.format(self.line, self.tokens)
-
-
-class PlaceholderAminoAcidWarning(BELParserWarning):
-    """Raised when an invalid amino acid code is given.
-
-    One example might be the usage of X, which is a colloquial signifier for a truncation in a given position. Text
-    mining efforts for knowledge extraction make this mistake often. X might also signify a placeholder amino acid.
-    """
-
-    def __init__(self, line_number, line, position, code):
-        super(PlaceholderAminoAcidWarning, self).__init__(line_number, line, position, code)
-        self.code = code
-
-    def __str__(self):
-        return 'Placeholder amino acid found: {}'.format(self.code)
-
-
-class NestedRelationWarning(BELParserWarning):
-    """Raised when encountering a nested statement.
-
-    See our the docs for an explanation of why we explicitly do not support nested statements.
-    """
-
-    def __str__(self):
-        return 'Nesting is not supported. Split this statement: {}'.format(self.line)
-
-
-# Semantic Warnings
-
-class InvalidEntity(BELParserWarning):
-    """Raised when using a non-entity name for a name."""
-
-    def __init__(self, line_number, line, position, namespace, name):
-        super().__init__(line_number, line, position, namespace, name)
-        self.namespace = namespace
-        self.name = name
-
-    def __str__(self):
-        return '{}:{} should not be coded as an entity'.format(self.namespace, ensure_quotes(self.name))
-
-
-class InvalidFunctionSemantic(BELParserWarning):
-    """Raised when an invalid function is used for a given node.
-
-    For example, an HGNC symbol for a protein-coding gene YFG cannot be referenced as an miRNA with ``m(HGNC:YFG)``
-    """
-
-    def __init__(self, line_number, line, position, func, namespace, name, allowed_functions):
-        super().__init__(line_number, line, position, func, namespace, name, allowed_functions)
-        self.func = func
-        self.namespace = namespace
-        self.name = name
-        self.allowed_functions = allowed_functions
-
-    def __str__(self):
-        return "{} {}:{} should be encoded as one of: {}".format(
-            self.func,
-            self.namespace,
-            ensure_quotes(self.name),
-            ', '.join(self.allowed_functions),
-        )
diff --git a/src/pybel/parser/modifiers/constants.py b/src/pybel/parser/modifiers/constants.py
index f4dd67b4..b2e9b0ac 100644
--- a/src/pybel/parser/modifiers/constants.py
+++ b/src/pybel/parser/modifiers/constants.py
@@ -4,8 +4,8 @@
 
 from pyparsing import Keyword, MatchFirst, oneOf
 
-from ..exc import PlaceholderAminoAcidWarning
 from ... import language
+from ...exceptions import PlaceholderAminoAcidWarning
 
 aa_single = oneOf(list(language.amino_acid_dict.keys()))
 aa_single.setParseAction(lambda s, l, t: [language.amino_acid_dict[t[0]]])
diff --git a/src/pybel/parser/parse_bel.py b/src/pybel/parser/parse_bel.py
index 199fb1e7..cbd5d4c2 100644
--- a/src/pybel/parser/parse_bel.py
+++ b/src/pybel/parser/parse_bel.py
@@ -7,19 +7,14 @@ This module handles parsing BEL relations and validation of semantics.
 
 import itertools as itt
 import logging
-from typing import Dict, List, Mapping, Optional, Pattern, Set, Union
+from functools import lru_cache
+from typing import Any, Dict, List, Mapping, Optional, Pattern, Set, Union
 
 import pyparsing
-from pyparsing import (
-    Group, Keyword, MatchFirst, ParseResults, StringEnd, Suppress, delimitedList, oneOf, replaceWith,
-)
+from pyparsing import Group, Keyword, MatchFirst, ParseResults, StringEnd, Suppress, delimitedList, oneOf, replaceWith
 
 from .baseparser import BaseParser
 from .constants import NamespaceTermEncodingMapping
-from .exc import (
-    InvalidEntity, InvalidFunctionSemantic, MalformedTranslocationWarning, MissingAnnotationWarning,
-    MissingCitationException, MissingSupportWarning, NestedRelationWarning,
-)
 from .modifiers import (
     get_fragment_language, get_fusion_language, get_gene_modification_language, get_gene_substitution_language,
     get_hgvs_language, get_legacy_fusion_langauge, get_location_language, get_protein_modification_language,
@@ -30,19 +25,25 @@ from .parse_control import ControlParser
 from .utils import WCW, nest, one_of_tags, triple
 from .. import language
 from ..constants import (
-    ABUNDANCE, ACTIVITY, ASSOCIATION, BEL_DEFAULT_NAMESPACE, BINDS, BIOPROCESS, CAUSES_NO_CHANGE, CELL_SECRETION,
-    CELL_SURFACE_EXPRESSION, COMPLEX, COMPOSITE, CONCEPT, CORRELATION, 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, NO_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,
+    ABUNDANCE, ACTIVITY, ASSOCIATION, BINDS, BIOPROCESS, CAUSES_NO_CHANGE, CELL_SECRETION, CELL_SURFACE_EXPRESSION,
+    COMPLEX, COMPOSITE, CONCEPT, CORRELATION, DECREASES, DEGRADATION, DIRECTLY_DECREASES, DIRECTLY_INCREASES, DIRTY,
+    EFFECT, EQUIVALENT_TO, FROM_LOC, FUNCTION, FUSION, GENE, IDENTIFIER, INCREASES, IS_A, LINE, LOCATION, MEMBERS,
+    MIRNA, MODIFIER, NAME, NAMESPACE, NEGATIVE_CORRELATION, NO_CORRELATION, PART_OF, PATHOLOGY, POPULATION,
+    POSITIVE_CORRELATION, PRODUCTS, PROTEIN, REACTANTS, REACTION, REGULATES, RELATION, RNA, SOURCE, TARGET, TO_LOC,
+    TRANSCRIBED_TO, TRANSLATED_TO, TRANSLOCATION, TWO_WAY_RELATIONS, VARIANTS, belns_encodings,
 )
-from ..dsl import BaseEntity, cell_surface_expression, secretion
+from ..dsl import BaseEntity
+from ..exceptions import (
+    InvalidEntity, InvalidFunctionSemantic, MalformedTranslocationWarning, MissingAnnotationWarning,
+    MissingCitationException, MissingSupportWarning, NestedRelationWarning,
+)
+from ..struct.graph import BELGraph
 from ..tokens import parse_result_to_dsl
 
 __all__ = [
     'BELParser',
     'modifier_po_to_dict',
+    'parse',
 ]
 
 logger = logging.getLogger('pybel.parser')
@@ -236,7 +237,7 @@ class BELParser(BaseParser):
 
     def __init__(
         self,
-        graph,
+        graph: Optional[BELGraph] = None,
         namespace_to_term_to_encoding: Optional[NamespaceTermEncodingMapping] = None,
         namespace_to_pattern: Optional[Mapping[str, Pattern]] = None,
         annotation_to_term: Optional[Mapping[str, Set[str]]] = None,
@@ -252,10 +253,10 @@ class BELParser(BaseParser):
     ) -> None:
         """Build a BEL parser.
 
-        :param pybel.BELGraph graph: The BEL Graph to use to store the network
+        :param graph: The BEL graph to use to store the network
         :param namespace_to_term_to_encoding: A dictionary of {namespace: {name: encoding}}. Delegated to
          :class:`pybel.parser.parse_identifier.IdentifierParser`
-        :param namespace_to_pattern: A dictionary of {namespace: regular expression strings}. Delegated to
+        :param namespace_to_pattern: A dictionary of {namespace: compiled regular expression}. Delegated to
          :class:`pybel.parser.parse_identifier.IdentifierParser`
         :param annotation_to_term: A dictionary of {annotation: set of values}. Delegated to
          :class:`pybel.parser.ControlParser`
@@ -444,11 +445,11 @@ class BELParser(BaseParser):
         self.bp_path.setParseAction(self.check_function_semantics)
 
         self.activity_standard = activity_tag + nest(
-            Group(self.simple_abundance)(TARGET) + pyparsing.Optional(WCW + Group(self.molecular_activity)(EFFECT)),
+            self.simple_abundance + pyparsing.Optional(WCW + Group(self.molecular_activity)(EFFECT)),
         )
 
         activity_legacy_tags = oneOf(language.activities)(MODIFIER)
-        self.activity_legacy = activity_legacy_tags + nest(Group(self.simple_abundance)(TARGET))
+        self.activity_legacy = activity_legacy_tags + nest(self.simple_abundance)
         self.activity_legacy.setParseAction(handle_activity_legacy)
 
         #: `2.3.3 <http://openbel.org/language/version_2.0/bel_specification_version_2.0.html#Xactivity>`_
@@ -461,24 +462,26 @@ class BELParser(BaseParser):
         from_loc = Suppress(FROM_LOC) + nest(concept(FROM_LOC))
         to_loc = Suppress(TO_LOC) + nest(concept(TO_LOC))
 
-        self.cell_secretion = cell_secretion_tag + nest(Group(self.simple_abundance)(TARGET))
+        self.cell_secretion = cell_secretion_tag + nest(self.simple_abundance)
+        self.cell_secretion.addParseAction(handle_secretion)
 
-        self.cell_surface_expression = cell_surface_expression_tag + nest(Group(self.simple_abundance)(TARGET))
+        self.cell_surface_expression = cell_surface_expression_tag + nest(self.simple_abundance)
+        self.cell_surface_expression.addParseAction(handle_surface_expression)
 
         self.translocation_standard = nest(
-            Group(self.simple_abundance)(TARGET)
+            self.simple_abundance
             + WCW
             + Group(from_loc + WCW + to_loc)(EFFECT),
         )
 
         self.translocation_legacy = nest(
-            Group(self.simple_abundance)(TARGET)
+            self.simple_abundance
             + WCW
             + Group(concept(FROM_LOC) + WCW + concept(TO_LOC))(EFFECT),
         )
 
         self.translocation_legacy.addParseAction(handle_legacy_tloc)
-        self.translocation_unqualified = nest(Group(self.simple_abundance)(TARGET))
+        self.translocation_unqualified = nest(self.simple_abundance)
 
         if self.disallow_unqualified_translocations:
             self.translocation_unqualified.setParseAction(self.handle_translocation_illegal)
@@ -491,7 +494,7 @@ class BELParser(BaseParser):
         ])
 
         #: `2.5.2 <http://openbel.org/language/version_2.0/bel_specification_version_2.0.html#_degradation_deg>`_
-        self.degradation = degradation_tags + nest(Group(self.simple_abundance)(TARGET))
+        self.degradation = degradation_tags + nest(self.simple_abundance)
 
         #: `2.5.3 <http://openbel.org/language/version_2.0/bel_specification_version_2.0.html#_reaction_rxn>`_
         self.reactants = Suppress(REACTANTS) + nest(delimitedList(Group(self.simple_abundance)))
@@ -592,7 +595,8 @@ class BELParser(BaseParser):
             # self.has_variant_relation,
             # self.part_of_reaction,
         ])
-        self.relation.setParseAction(self._handle_relation_harness)
+        if self.graph is not None:
+            self.relation.setParseAction(self._handle_relation_harness)
 
         self.inverted_unqualified_relation = MatchFirst([
             self.has_member,
@@ -641,6 +645,10 @@ class BELParser(BaseParser):
 
         super(BELParser, self).__init__(self.language, streamline=autostreamline)
 
+    def parse(self, s: str) -> Mapping[str, Any]:
+        """Parse the string."""
+        return self.parseString(s).asDict()
+
     @property
     def _namespace_dict(self) -> Mapping[str, Mapping[str, str]]:
         """Get the dictionary of {namespace: {name: encoding}} stored in the internal identifier parser."""
@@ -657,7 +665,8 @@ class BELParser(BaseParser):
 
     def clear(self):
         """Clear the graph and all control parser data (current citation, annotations, and statement group)."""
-        self.graph.clear()
+        if self.graph is not None:
+            self.graph.clear()
         self.control_parser.clear()
 
     def handle_nested_relation(self, line: str, position: int, tokens: ParseResults):
@@ -672,17 +681,17 @@ class BELParser(BaseParser):
 
         subject_hash = self._handle_relation_checked(
             line, position, {
-                SUBJECT: tokens[SUBJECT],
+                SOURCE: tokens[SOURCE],
                 RELATION: tokens[RELATION],
-                OBJECT: tokens[OBJECT][SUBJECT],
+                TARGET: tokens[TARGET][SOURCE],
             },
         )
 
         object_hash = self._handle_relation_checked(
             line, position, {
-                SUBJECT: tokens[OBJECT][SUBJECT],
-                RELATION: tokens[OBJECT][RELATION],
-                OBJECT: tokens[OBJECT][OBJECT],
+                SOURCE: tokens[TARGET][SOURCE],
+                RELATION: tokens[TARGET][RELATION],
+                TARGET: tokens[TARGET][TARGET],
             },
         )
         self.metagraph.add((subject_hash, object_hash))
@@ -753,7 +762,16 @@ class BELParser(BaseParser):
         """Handle list relations like ``p(X) hasComponents list(p(Y), p(Z), ...)``."""
         return self._handle_list_helper(tokens, PART_OF)
 
-    def _add_qualified_edge_helper(self, *, u, u_modifier, relation, v, v_modifier, annotations) -> str:
+    def _add_qualified_edge_helper(
+        self,
+        *,
+        source,
+        source_modifier,
+        relation,
+        target,
+        target_modifier,
+        annotations,
+    ) -> str:
         """Add a qualified edge from the internal aspects of the parser."""
         m = {
             BINDS: self.graph.add_binds,
@@ -763,39 +781,43 @@ class BELParser(BaseParser):
             evidence=self.control_parser.evidence,
             citation=self.control_parser.get_citation(),
             annotations=annotations,
-            subject_modifier=u_modifier,
-            object_modifier=v_modifier,
+            source_modifier=source_modifier,
+            target_modifier=target_modifier,
             **{LINE: self.get_line_number()},
         )
         if adder is not None:
-            return adder(u=u, v=v, **d)
+            return adder(source=source, target=target, **d)
         else:
-            return self.graph.add_qualified_edge(u=u, v=v, relation=relation, **d)
+            return self.graph.add_qualified_edge(source=source, target=target, relation=relation, **d)
 
-    def _add_qualified_edge(self, *, u, u_modifier, relation, v, v_modifier, annotations) -> str:
+    def _add_qualified_edge(self, *, source, source_modifier, relation, target, target_modifier, annotations) -> str:
         """Add an edge, then adds the opposite direction edge if it should."""
         d = dict(
             relation=relation,
             annotations=annotations,
         )
         if relation in TWO_WAY_RELATIONS:
-            self._add_qualified_edge_helper(u=v, u_modifier=v_modifier, v=u, v_modifier=u_modifier, **d)
-        return self._add_qualified_edge_helper(u=u, u_modifier=u_modifier, v=v, v_modifier=v_modifier, **d)
+            self._add_qualified_edge_helper(
+                source=target, source_modifier=target_modifier, target=source, target_modifier=source_modifier, **d
+            )
+        return self._add_qualified_edge_helper(
+            source=source, source_modifier=source_modifier, target=target, target_modifier=target_modifier, **d
+        )
 
     def _handle_relation(self, tokens: ParseResults) -> str:
         """Handle a relation."""
-        u = self.ensure_node(tokens[SUBJECT])
-        u_modifier = modifier_po_to_dict(tokens[SUBJECT])
+        source = self.ensure_node(tokens[SOURCE])
+        source_modifier = modifier_po_to_dict(tokens[SOURCE])
         relation = tokens[RELATION]
-        v = self.ensure_node(tokens[OBJECT])
-        v_modifier = modifier_po_to_dict(tokens[OBJECT])
+        target = self.ensure_node(tokens[TARGET])
+        target_modifier = modifier_po_to_dict(tokens[TARGET])
 
         annotations = self._get_prepared_annotations()
 
         return self._add_qualified_edge(
-            u=u, u_modifier=u_modifier,
+            source=source, source_modifier=source_modifier,
             relation=relation,
-            v=v, v_modifier=v_modifier,
+            target=target, target_modifier=target_modifier,
             annotations=annotations,
         )
 
@@ -837,25 +859,22 @@ class BELParser(BaseParser):
 
     def handle_unqualified_relation(self, _, __, tokens: ParseResults) -> ParseResults:
         """Handle unqualified relations."""
-        subject_node_dsl = self.ensure_node(tokens[SUBJECT])
-        object_node_dsl = self.ensure_node(tokens[OBJECT])
+        subject_node_dsl = self.ensure_node(tokens[SOURCE])
+        object_node_dsl = self.ensure_node(tokens[TARGET])
         relation = tokens[RELATION]
         self.graph.add_unqualified_edge(subject_node_dsl, object_node_dsl, relation)
         return tokens
 
     def handle_inverse_unqualified_relation(self, _, __, tokens: ParseResults) -> ParseResults:
         """Handle unqualified relations that should go reverse."""
-        u = self.ensure_node(tokens[SUBJECT])
-        v = self.ensure_node(tokens[OBJECT])
+        source = self.ensure_node(tokens[SOURCE])
+        target = self.ensure_node(tokens[TARGET])
         relation = tokens[RELATION]
-        self.graph.add_unqualified_edge(v, u, relation)
+        self.graph.add_unqualified_edge(source=target, target=source, relation=relation)
         return tokens
 
     def ensure_node(self, tokens: ParseResults) -> BaseEntity:
         """Turn parsed tokens into canonical node name and makes sure its in the graph."""
-        if MODIFIER in tokens:
-            return self.ensure_node(tokens[TARGET])
-
         node = parse_result_to_dsl(tokens)
         self.graph.add_node_from_data(node)
         return node
@@ -869,9 +888,11 @@ class BELParser(BaseParser):
 
 def handle_molecular_activity_default(_: str, __: int, tokens: ParseResults) -> ParseResults:
     """Handle a BEL 2.0 style molecular activity with BEL default names."""
-    upgraded = language.activity_labels[tokens[0]]
-    tokens[NAMESPACE] = BEL_DEFAULT_NAMESPACE
-    tokens[NAME] = upgraded
+    upgraded_cls = language.activity_labels[tokens[0]]
+    upgraded_concept = language.activity_mapping[upgraded_cls]
+    tokens[NAMESPACE] = upgraded_concept.namespace
+    tokens[NAME] = upgraded_concept.name
+    tokens[IDENTIFIER] = upgraded_concept.identifier
     return tokens
 
 
@@ -879,10 +900,7 @@ def handle_activity_legacy(_: str, __: int, tokens: ParseResults) -> ParseResult
     """Handle BEL 1.0 activities."""
     legacy_cls = language.activity_labels[tokens[MODIFIER]]
     tokens[MODIFIER] = ACTIVITY
-    tokens[EFFECT] = {
-        NAME: legacy_cls,
-        NAMESPACE: BEL_DEFAULT_NAMESPACE,
-    }
+    tokens[EFFECT] = language.activity_mapping[legacy_cls]
     logger.log(5, 'upgraded legacy activity to %s', legacy_cls)
     return tokens
 
@@ -893,6 +911,24 @@ def handle_legacy_tloc(line: str, position: int, tokens: ParseResults) -> ParseR
     return tokens
 
 
+def handle_secretion(_, __, tokens: ParseResults) -> ParseResults:
+    tokens[MODIFIER] = TRANSLOCATION
+    tokens[EFFECT] = {
+        FROM_LOC: language.intracellular,
+        TO_LOC: language.extracellular,
+    }
+    return tokens
+
+
+def handle_surface_expression(_, __, tokens: ParseResults) -> ParseResults:
+    tokens[MODIFIER] = TRANSLOCATION
+    tokens[EFFECT] = {
+        FROM_LOC: language.intracellular,
+        TO_LOC: language.cell_surface,
+    }
+    return tokens
+
+
 def modifier_po_to_dict(tokens):
     """Get the location, activity, and/or transformation information as a dictionary.
 
@@ -907,9 +943,6 @@ def modifier_po_to_dict(tokens):
     if MODIFIER not in tokens:
         return attrs
 
-    if LOCATION in tokens[TARGET]:
-        attrs[LOCATION] = tokens[TARGET][LOCATION].asDict()
-
     if tokens[MODIFIER] == DEGRADATION:
         attrs[MODIFIER] = tokens[MODIFIER]
 
@@ -923,15 +956,42 @@ def modifier_po_to_dict(tokens):
         attrs[MODIFIER] = tokens[MODIFIER]
 
         if EFFECT in tokens:
-            attrs[EFFECT] = tokens[EFFECT].asDict()
+            try:
+                attrs[EFFECT] = tokens[EFFECT].asDict()
+            except AttributeError:  # for when it was auto-upgraded
+                attrs[EFFECT] = dict(tokens[EFFECT])
 
     elif tokens[MODIFIER] == CELL_SECRETION:
-        attrs.update(secretion())
+        attrs[MODIFIER] = TRANSLOCATION
+        attrs[EFFECT] = {
+            FROM_LOC: language.intracellular,
+            TO_LOC: language.extracellular,
+        }
 
     elif tokens[MODIFIER] == CELL_SURFACE_EXPRESSION:
-        attrs.update(cell_surface_expression())
+        attrs[MODIFIER] = TRANSLOCATION
+        attrs[EFFECT] = {
+            FROM_LOC: language.intracellular,
+            TO_LOC: language.cell_surface,
+        }
 
     else:
         raise ValueError('Invalid value for tokens[MODIFIER]: {}'.format(tokens[MODIFIER]))
 
     return attrs
+
+
+@lru_cache()
+def _default_parser():
+    return BELParser(skip_validation=True, citation_clearing=False)
+
+
+@lru_cache()
+def parse(s: str, pprint=False):
+    """Parse a BEL statement (without validation)."""
+    rv = _default_parser().parse(s)
+    if pprint:
+        import json
+        print(json.dumps(rv, indent=2))
+    else:
+        return rv
diff --git a/src/pybel/parser/parse_concept.py b/src/pybel/parser/parse_concept.py
index c6927d1b..dc0d1cee 100644
--- a/src/pybel/parser/parse_concept.py
+++ b/src/pybel/parser/parse_concept.py
@@ -3,6 +3,7 @@
 """A module holding the :class:`IdentifierParser`."""
 
 import logging
+import re
 from collections import defaultdict
 from typing import Mapping, Optional, Pattern, Set
 
@@ -10,12 +11,12 @@ from pyparsing import ParseResults, Suppress
 
 from .baseparser import BaseParser
 from .constants import NamespaceTermEncodingMapping
-from .exc import (
+from .utils import quote, word
+from ..constants import DIRTY, IDENTIFIER, NAME, NAMESPACE
+from ..exceptions import (
     MissingDefaultNameWarning, MissingNamespaceNameWarning, MissingNamespaceRegexWarning, NakedNameWarning,
     UndefinedNamespaceWarning,
 )
-from .utils import quote, word
-from ..constants import DIRTY, IDENTIFIER, NAME, NAMESPACE
 
 __all__ = [
     'ConceptParser',
@@ -37,6 +38,7 @@ class ConceptParser(BaseParser):
         default_namespace: Optional[Set[str]] = None,
         allow_naked_names: bool = False,
         skip_validation: bool = False,
+        ensure_go: bool = True,
     ) -> None:
         """Initialize the concept parser.
 
@@ -73,6 +75,9 @@ class ConceptParser(BaseParser):
             self.identifier_qualified.setParseAction(self.handle_identifier_qualified)
 
         self.namespace_to_pattern = namespace_to_pattern or {}
+        if ensure_go and 'go' not in self.namespace_to_name_to_encoding:
+            self.namespace_to_pattern['go'] = re.compile(r'^\d+$')
+
         self.default_namespace = set(default_namespace) if default_namespace is not None else None
         self.allow_naked_names = allow_naked_names
 
diff --git a/src/pybel/parser/parse_control.py b/src/pybel/parser/parse_control.py
index 47b72810..223dd502 100644
--- a/src/pybel/parser/parse_control.py
+++ b/src/pybel/parser/parse_control.py
@@ -10,30 +10,30 @@ This module handles parsing control statement, which add annotations and namespa
 """
 
 import logging
-from typing import Dict, List, Mapping, Optional, Pattern, Set
+from typing import Any, Dict, List, Mapping, Optional, Pattern, Set
 
-from pyparsing import And, MatchFirst, ParseResults, Suppress, oneOf, pyparsing_common as ppc
+from pyparsing import And, Keyword, MatchFirst, ParseResults, Suppress, oneOf, pyparsing_common as ppc
 
 from .baseparser import BaseParser
-from .exc import (
-    CitationTooLongException, CitationTooShortException, IllegalAnnotationValueWarning, InvalidCitationType,
-    InvalidPubMedIdentifierWarning, MissingAnnotationKeyWarning, MissingAnnotationRegexWarning,
-    MissingCitationException, UndefinedAnnotationWarning,
-)
 from .utils import delimited_quoted_list, delimited_unquoted_list, is_int, qid, quote
 from ..constants import (
     ANNOTATIONS, BEL_KEYWORD_ALL, BEL_KEYWORD_CITATION, BEL_KEYWORD_EVIDENCE, BEL_KEYWORD_SET,
     BEL_KEYWORD_STATEMENT_GROUP, BEL_KEYWORD_SUPPORT, BEL_KEYWORD_UNSET, CITATION, CITATION_TYPES, CITATION_TYPE_PUBMED,
     EVIDENCE,
 )
-from ..utils import CitationDict, citation_dict
+from ..exceptions import (
+    CitationTooLongException, CitationTooShortException, IllegalAnnotationValueWarning, InvalidCitationType,
+    InvalidPubMedIdentifierWarning, MissingAnnotationKeyWarning, MissingAnnotationRegexWarning,
+    MissingCitationException, UndefinedAnnotationWarning,
+)
+from ..language import CitationDict
 
 __all__ = ['ControlParser']
 
 logger = logging.getLogger(__name__)
 
-set_tag = Suppress(BEL_KEYWORD_SET)
-unset_tag = Suppress(BEL_KEYWORD_UNSET)
+set_tag = Keyword(BEL_KEYWORD_SET)
+unset_tag = Keyword(BEL_KEYWORD_UNSET)
 unset_all = Suppress(BEL_KEYWORD_ALL)
 
 supporting_text_tags = oneOf([BEL_KEYWORD_EVIDENCE, BEL_KEYWORD_SUPPORT])
@@ -111,7 +111,7 @@ class ControlParser(BaseParser):
 
         self.unset_all = unset_all.setParseAction(self.handle_unset_all)
 
-        self.set_statements = set_tag + MatchFirst([
+        self.set_statements = set_tag('action') + MatchFirst([
             self.set_statement_group,
             self.set_citation,
             self.set_evidence,
@@ -119,7 +119,7 @@ class ControlParser(BaseParser):
             self.set_command_list,
         ])
 
-        self.unset_statements = unset_tag + MatchFirst([
+        self.unset_statements = unset_tag('action') + MatchFirst([
             self.unset_all,
             self.unset_citation,
             self.unset_evidence,
@@ -340,7 +340,7 @@ class ControlParser(BaseParser):
         self.clear()
         return tokens
 
-    def get_annotations(self) -> Dict:
+    def get_annotations(self) -> Dict[str, Any]:
         """Get the current annotations."""
         return {
             EVIDENCE: self.evidence,
@@ -348,9 +348,12 @@ class ControlParser(BaseParser):
             ANNOTATIONS: self.annotations.copy(),
         }
 
-    def get_citation(self) -> CitationDict:
+    def get_citation(self) -> Optional[CitationDict]:
         """Get the citation dictionary."""
-        return citation_dict(db=self.citation_db, db_id=self.citation_db_id)
+        return (
+            CitationDict(namespace=self.citation_db, identifier=self.citation_db_id)
+            if self.citation_db and self.citation_db_id else None
+        )
 
     def get_missing_required_annotations(self) -> List[str]:
         """Return missing required annotations."""
diff --git a/src/pybel/parser/parse_metadata.py b/src/pybel/parser/parse_metadata.py
index c6653633..2a34dd0e 100644
--- a/src/pybel/parser/parse_metadata.py
+++ b/src/pybel/parser/parse_metadata.py
@@ -10,13 +10,16 @@ from pyparsing import And, MatchFirst, ParseResults, Suppress, Word, pyparsing_c
 
 from .baseparser import BaseParser
 from .constants import NamespaceTermEncodingMapping
-from .exc import InvalidMetadataException, RedefinedAnnotationError, RedefinedNamespaceError, VersionFormatWarning
 from .utils import delimited_quoted_list, qid, quote, word
 from ..constants import (
     BEL_KEYWORD_ANNOTATION, BEL_KEYWORD_AS, BEL_KEYWORD_DEFINE, BEL_KEYWORD_DOCUMENT, BEL_KEYWORD_LIST,
     BEL_KEYWORD_NAMESPACE, BEL_KEYWORD_PATTERN, BEL_KEYWORD_SET, BEL_KEYWORD_URL, DOCUMENT_KEYS, METADATA_VERSION,
     belns_encodings,
 )
+from ..exceptions import (
+    InvalidMetadataException, RedefinedAnnotationError, RedefinedNamespaceError,
+    VersionFormatWarning,
+)
 from ..resources.resources import keyword_to_url
 from ..utils import valid_date_version
 
diff --git a/src/pybel/parser/utils.py b/src/pybel/parser/utils.py
index c4a66689..2f046420 100644
--- a/src/pybel/parser/utils.py
+++ b/src/pybel/parser/utils.py
@@ -11,7 +11,7 @@ from pyparsing import (
     removeQuotes, replaceWith,
 )
 
-from ..constants import OBJECT, RELATION, SUBJECT
+from ..constants import RELATION, SOURCE, TARGET
 
 logger = logging.getLogger('pybel')
 
@@ -77,4 +77,4 @@ def one_of_tags(
 
 def triple(subject, relation, obj) -> ParserElement:
     """Build a simple triple in PyParsing that has a ``subject relation object`` format."""
-    return And([Group(subject)(SUBJECT), relation(RELATION), Group(obj)(OBJECT)])
+    return And([Group(subject)(SOURCE), relation(RELATION), Group(obj)(TARGET)])
diff --git a/src/pybel/struct/filters/edge_predicate_builders.py b/src/pybel/struct/filters/edge_predicate_builders.py
index 6684204c..b8b4c34e 100644
--- a/src/pybel/struct/filters/edge_predicate_builders.py
+++ b/src/pybel/struct/filters/edge_predicate_builders.py
@@ -8,7 +8,7 @@ from .edge_filters import invert_edge_predicate
 from .edge_predicates import edge_predicate, has_authors, has_pubmed, true_edge_predicate
 from .typing import EdgePredicate
 from ..graph import BELGraph
-from ...constants import ANNOTATIONS, CAUSAL_RELATIONS, CITATION, CITATION_AUTHORS, CITATION_IDENTIFIER, RELATION
+from ...constants import ANNOTATIONS, CAUSAL_RELATIONS, CITATION, CITATION_AUTHORS, IDENTIFIER, RELATION
 from ...dsl import BaseEntity
 from ...typing import EdgeData, Strings
 
@@ -154,7 +154,7 @@ def build_pmid_inclusion_filter(pmids: Strings) -> EdgePredicate:
         @edge_predicate
         def pmid_inclusion_filter(edge_data: EdgeData) -> bool:
             """Pass for edges with PubMed citations matching the contained PubMed identifier."""
-            return has_pubmed(edge_data) and edge_data[CITATION][CITATION_IDENTIFIER] == pmids
+            return has_pubmed(edge_data) and edge_data[CITATION].identifier == pmids
 
     elif isinstance(pmids, Iterable):
         pmids = set(pmids)
@@ -162,7 +162,7 @@ def build_pmid_inclusion_filter(pmids: Strings) -> EdgePredicate:
         @edge_predicate
         def pmid_inclusion_filter(edge_data: EdgeData) -> bool:
             """Pass for edges with PubMed citations matching one of the contained PubMed identifiers."""
-            return has_pubmed(edge_data) and edge_data[CITATION][CITATION_IDENTIFIER] in pmids
+            return has_pubmed(edge_data) and edge_data[CITATION].identifier in pmids
 
     else:
         raise TypeError
diff --git a/src/pybel/struct/filters/edge_predicates.py b/src/pybel/struct/filters/edge_predicates.py
index 7c210449..3511475b 100644
--- a/src/pybel/struct/filters/edge_predicates.py
+++ b/src/pybel/struct/filters/edge_predicates.py
@@ -9,8 +9,8 @@ from .typing import EdgePredicate
 from .utils import part_has_modifier
 from ..graph import BELGraph
 from ...constants import (
-    ACTIVITY, ANNOTATIONS, ASSOCIATION, CAUSAL_RELATIONS, CITATION, CITATION_AUTHORS, CITATION_DB,
-    CITATION_TYPE_PUBMED, DEGRADATION, DIRECT_CAUSAL_RELATIONS, EVIDENCE, OBJECT, POLAR_RELATIONS, RELATION, SUBJECT,
+    ACTIVITY, ANNOTATIONS, ASSOCIATION, CAUSAL_RELATIONS, CITATION, CITATION_AUTHORS, CITATION_TYPE_PUBMED, DEGRADATION,
+    DIRECT_CAUSAL_RELATIONS, EVIDENCE, NAMESPACE, POLAR_RELATIONS, RELATION, SOURCE_MODIFIER, TARGET_MODIFIER,
     TRANSLOCATION,
 )
 from ...dsl import BaseEntity, BiologicalProcess, Pathology
@@ -77,7 +77,7 @@ def has_provenance(edge_data: EdgeData) -> bool:
 @edge_predicate
 def has_pubmed(edge_data: EdgeData) -> bool:
     """Check if the edge has a PubMed citation."""
-    return CITATION in edge_data and CITATION_TYPE_PUBMED == edge_data[CITATION][CITATION_DB]
+    return CITATION in edge_data and CITATION_TYPE_PUBMED == edge_data[CITATION][NAMESPACE]
 
 
 @edge_predicate
@@ -124,7 +124,10 @@ def _has_modifier(edge_data: EdgeData, modifier: str) -> bool:
                         :data:`pybel.constants.DEGRADATION`, or :data:`pybel.constants.TRANSLOCATION`.
     :return: Does either the subject or object have the given modifier
     """
-    return part_has_modifier(edge_data, SUBJECT, modifier) or part_has_modifier(edge_data, OBJECT, modifier)
+    return (
+        part_has_modifier(edge_data, SOURCE_MODIFIER, modifier)
+        or part_has_modifier(edge_data, TARGET_MODIFIER, modifier)
+    )
 
 
 @edge_predicate
diff --git a/src/pybel/struct/filters/node_predicates/activities.py b/src/pybel/struct/filters/node_predicates/activities.py
index 26983d1c..272d1f80 100644
--- a/src/pybel/struct/filters/node_predicates/activities.py
+++ b/src/pybel/struct/filters/node_predicates/activities.py
@@ -4,7 +4,7 @@
 
 from ..utils import part_has_modifier
 from ...graph import BELGraph
-from ....constants import ACTIVITY, DEGRADATION, OBJECT, SUBJECT, TRANSLOCATION
+from ....constants import ACTIVITY, DEGRADATION, SOURCE_MODIFIER, TARGET_MODIFIER, TRANSLOCATION
 from ....dsl import BaseEntity
 
 __all__ = [
@@ -28,12 +28,12 @@ def has_edge_modifier(graph: BELGraph, node: BaseEntity, modifier: str) -> bool:
                         :data:`pybel.constants.TRANSLOCATION`
     """
     modifier_in_subject = any(
-        part_has_modifier(d, SUBJECT, modifier)
+        part_has_modifier(d, SOURCE_MODIFIER, modifier)
         for _, _, d in graph.out_edges(node, data=True)
     )
 
     modifier_in_object = any(
-        part_has_modifier(d, OBJECT, modifier)
+        part_has_modifier(d, TARGET_MODIFIER, modifier)
         for _, _, d in graph.in_edges(node, data=True)
     )
 
diff --git a/src/pybel/struct/graph.py b/src/pybel/struct/graph.py
index b45b35e6..9afee92b 100644
--- a/src/pybel/struct/graph.py
+++ b/src/pybel/struct/graph.py
@@ -17,23 +17,23 @@ from .operations import left_full_join, left_node_intersection_join, left_outer_
 from .utils import update_metadata
 from ..canonicalize import edge_to_bel
 from ..constants import (
-    ACTIVITY, ANNOTATIONS, ASSOCIATION, CAUSES_NO_CHANGE, CITATION, CITATION_AUTHORS, CITATION_DB, CITATION_IDENTIFIER,
-    CITATION_TYPE_PUBMED, CORRELATION, DECREASES, DEGRADATION, DIRECTLY_DECREASES, DIRECTLY_INCREASES, EFFECT,
-    EQUIVALENT_TO, EVIDENCE, FROM_LOC, GRAPH_ANNOTATION_LIST, GRAPH_ANNOTATION_PATTERN, GRAPH_ANNOTATION_URL,
-    GRAPH_METADATA, GRAPH_NAMESPACE_PATTERN, GRAPH_NAMESPACE_URL, GRAPH_PATH, GRAPH_PYBEL_VERSION, HAS_PRODUCT,
-    HAS_REACTANT, HAS_VARIANT, INCREASES, IS_A, LOCATION, METADATA_AUTHORS, METADATA_CONTACT, METADATA_COPYRIGHT,
-    METADATA_DESCRIPTION, METADATA_DISCLAIMER, METADATA_LICENSES, METADATA_NAME, METADATA_VERSION, MODIFIER,
-    NEGATIVE_CORRELATION, NO_CORRELATION, OBJECT, ORTHOLOGOUS, PART_OF, POSITIVE_CORRELATION, REGULATES, RELATION,
-    SUBJECT, TO_LOC, TRANSCRIBED_TO, TRANSLATED_TO, TRANSLOCATION,
+    ACTIVITY, ANNOTATIONS, ASSOCIATION, CAUSES_NO_CHANGE, CITATION, CITATION_AUTHORS, CITATION_TYPE_PUBMED, CORRELATION,
+    DECREASES, DEGRADATION, DIRECTLY_DECREASES, DIRECTLY_INCREASES, EFFECT, EQUIVALENT_TO, EVIDENCE, FROM_LOC,
+    GRAPH_ANNOTATION_LIST, GRAPH_ANNOTATION_PATTERN, GRAPH_ANNOTATION_URL, GRAPH_METADATA, GRAPH_NAMESPACE_PATTERN,
+    GRAPH_NAMESPACE_URL, GRAPH_PATH, GRAPH_PYBEL_VERSION, HAS_PRODUCT, HAS_REACTANT, HAS_VARIANT, IDENTIFIER, INCREASES,
+    IS_A, LOCATION, METADATA_AUTHORS, METADATA_CONTACT, METADATA_COPYRIGHT, METADATA_DESCRIPTION, METADATA_DISCLAIMER,
+    METADATA_LICENSES, METADATA_NAME, METADATA_VERSION, MODIFIER, NAMESPACE, NEGATIVE_CORRELATION, NO_CORRELATION,
+    ORTHOLOGOUS, PART_OF, POSITIVE_CORRELATION, REGULATES, RELATION, SOURCE_MODIFIER, TARGET_MODIFIER, TO_LOC,
+    TRANSCRIBED_TO, TRANSLATED_TO, TRANSLOCATION,
 )
 from ..dsl import (
     BaseAbundance, BaseConcept, BaseEntity, CentralDogma, ComplexAbundance, Gene, ListAbundance, MicroRna, Protein,
     ProteinModification, Reaction, Rna, activity,
 )
-from ..language import Entity
-from ..parser.exc import BELParserWarning
+from ..exceptions import BELParserWarning
+from ..language import CitationDict, Entity, citation_dict
 from ..typing import EdgeData
-from ..utils import CitationDict, citation_dict, hash_edge
+from ..utils import hash_edge
 from ..version import get_version
 
 __all__ = [
@@ -388,31 +388,30 @@ class BELGraph(nx.MultiDiGraph):
             {} if context is None else context,
         ))
 
-    def _help_add_edge(self, u: BaseEntity, v: BaseEntity, attr: Mapping) -> str:
+    def _help_add_edge(self, source: BaseEntity, target: BaseEntity, attr: Mapping) -> str:
         """Help add a pre-built edge."""
-        self.add_node_from_data(u)
-        self.add_node_from_data(v)
+        self.add_node_from_data(source)
+        self.add_node_from_data(target)
+        return self._help_add_edge_helper(source=source, target=target, attr=attr)
 
-        return self._help_add_edge_helper(u, v, attr)
+    def _help_add_edge_helper(self, source: BaseEntity, target: BaseEntity, attr: Mapping[str, Any]) -> str:
+        key = hash_edge(source, target, attr)
 
-    def _help_add_edge_helper(self, u: BaseEntity, v: BaseEntity, attr: Mapping[str, Any]) -> str:
-        key = hash_edge(u, v, attr)
-
-        if not self.has_edge(u, v, key):
-            self.add_edge(u, v, key=key, **attr)
+        if not self.has_edge(source, target, key):
+            self.add_edge(source, target, key=key, **attr)
 
         return key
 
-    def add_unqualified_edge(self, u: BaseEntity, v: BaseEntity, relation: str) -> str:
+    def add_unqualified_edge(self, source: BaseEntity, target: BaseEntity, relation: str) -> str:
         """Add a unique edge that has no annotations.
 
-        :param u: The source node
-        :param v: The target node
+        :param source: The source node
+        :param target: The target node
         :param relation: A relationship label from :mod:`pybel.constants`
         :return: The key for this edge (a unique hash)
         """
         attr = {RELATION: relation}
-        return self._help_add_edge(u, v, attr)
+        return self._help_add_edge(source=source, target=target, attr=attr)
 
     def add_transcription(self, gene: Gene, rna: Union[Rna, MicroRna]) -> str:
         """Add a transcription relation from a gene to an RNA or miRNA node.
@@ -430,15 +429,15 @@ class BELGraph(nx.MultiDiGraph):
         """
         return self.add_unqualified_edge(rna, protein, TRANSLATED_TO)
 
-    def _add_two_way_qualified_edge(self, u: BaseEntity, v: BaseEntity, *args, **kwargs) -> str:
+    def _add_two_way_qualified_edge(self, source: BaseEntity, target: BaseEntity, *args, **kwargs) -> str:
         """Add an qualified edge both ways."""
-        self.add_qualified_edge(u=v, v=u, *args, **kwargs)
-        return self.add_qualified_edge(u=u, v=v, *args, **kwargs)
+        self.add_qualified_edge(source=target, target=source, *args, **kwargs)
+        return self.add_qualified_edge(source=source, target=target, *args, **kwargs)
 
-    def _add_two_way_unqualified_edge(self, u: BaseEntity, v: BaseEntity, *args, **kwargs) -> str:
+    def _add_two_way_unqualified_edge(self, source: BaseEntity, target: BaseEntity, *args, **kwargs) -> str:
         """Add an unqualified edge both ways."""
-        self.add_unqualified_edge(v, u, *args, **kwargs)
-        return self.add_unqualified_edge(u, v, *args, **kwargs)
+        self.add_unqualified_edge(target, source, *args, **kwargs)
+        return self.add_unqualified_edge(source, target, *args, **kwargs)
 
     add_equivalence = partialmethod(_add_two_way_unqualified_edge, relation=EQUIVALENT_TO)
     """Add two equivalence relations for the nodes."""
@@ -463,15 +462,15 @@ class BELGraph(nx.MultiDiGraph):
 
     def add_qualified_edge(
         self,
-        u,
-        v,
+        source: BaseEntity,
+        target: BaseEntity,
         *,
         relation: str,
         evidence: str,
         citation: Union[str, Tuple[str, str], CitationDict],
         annotations: Optional[AnnotationsHint] = None,
-        subject_modifier: Optional[Mapping[str, Any]] = None,
-        object_modifier: Optional[Mapping[str, Any]] = None,
+        source_modifier: Optional[Mapping[str, Any]] = None,
+        target_modifier: Optional[Mapping[str, Any]] = None,
         **attr
     ) -> str:
         """Add a qualified edge.
@@ -479,15 +478,15 @@ class BELGraph(nx.MultiDiGraph):
         Qualified edges have a relation, evidence, citation, and optional annotations, subject modifications,
         and object modifications.
 
-        :param u: The source node
-        :param v: The target node
+        :param source: The source node
+        :param target: The target node
         :param relation: The type of relation this edge represents
         :param evidence: The evidence string from an article
         :param citation: The citation data dictionary for this evidence. If a string is given,
          assumes it's a PubMed identifier and auto-fills the citation type.
         :param annotations: The annotations data dictionary
-        :param subject_modifier: The modifiers (like activity) on the subject node. See data model documentation.
-        :param object_modifier: The modifiers (like activity) on the object node. See data model documentation.
+        :param source_modifier: The modifiers (like activity) on the subject node. See data model documentation.
+        :param target_modifier: The modifiers (like activity) on the object node. See data model documentation.
 
         :return: The hash of the edge
         """
@@ -496,11 +495,11 @@ class BELGraph(nx.MultiDiGraph):
             evidence=evidence,
             citation=citation,
             annotations=annotations,
-            subject_modifier=subject_modifier,
-            object_modifier=object_modifier,
+            source_modifier=source_modifier,
+            target_modifier=target_modifier,
             **attr
         )
-        return self._help_add_edge(u, v, attr)
+        return self._help_add_edge(source=source, target=target, attr=attr)
 
     @staticmethod
     def _build_attr(
@@ -508,8 +507,8 @@ class BELGraph(nx.MultiDiGraph):
         evidence: str,
         citation: Union[str, Tuple[str, str], CitationDict],
         annotations: Optional[AnnotationsHint] = None,
-        subject_modifier: Optional[Mapping[str, Any]] = None,
-        object_modifier: Optional[Mapping[str, Any]] = None,
+        source_modifier: Optional[Dict[str, Any]] = None,
+        target_modifier: Optional[Dict[str, Any]] = None,
         **attr
     ):
         attr.update({
@@ -521,18 +520,18 @@ class BELGraph(nx.MultiDiGraph):
         if annotations:  # clean up annotations
             attr[ANNOTATIONS] = _clean_annotations(annotations)
 
-        if subject_modifier:
-            attr[SUBJECT] = _handle_modifier(subject_modifier)
+        if source_modifier:
+            attr[SOURCE_MODIFIER] = _handle_modifier(source_modifier)
 
-        if object_modifier:
-            attr[OBJECT] = _handle_modifier(object_modifier)
+        if target_modifier:
+            attr[TARGET_MODIFIER] = _handle_modifier(target_modifier)
 
         return attr
 
     def add_binds(
         self,
-        u,
-        v,
+        source: BaseAbundance,
+        target: BaseAbundance,
         *,
         evidence: str,
         citation: Union[str, Tuple[str, str], CitationDict],
@@ -540,10 +539,10 @@ class BELGraph(nx.MultiDiGraph):
         **attr
     ) -> str:
         """Add a "binding" relationship between the two entities such that ``u => complex(u, v)``."""
-        complex_abundance = ComplexAbundance([u, v])
+        complex_abundance = ComplexAbundance([source, target])
         return self.add_directly_increases(
-            u,
-            complex_abundance,
+            source=source,
+            target=complex_abundance,
             citation=citation,
             evidence=evidence,
             annotations=annotations,
@@ -583,50 +582,50 @@ class BELGraph(nx.MultiDiGraph):
     add_causes_no_change = partialmethod(add_qualified_edge, relation=CAUSES_NO_CHANGE)
     """Add a :data:`pybel.constants.CAUSES_NO_CHANGE` relationship with :meth:`add_qualified_edge`."""
 
-    add_inhibits = partialmethod(add_decreases, object_modifier=activity())
+    add_inhibits = partialmethod(add_decreases, target_modifier=activity())
     """Add an "inhibits" relationship.
 
     A more specific version of :meth:`add_decreases` that automatically populates the object modifier with an
     activity."""
 
-    add_directly_inhibits = partialmethod(add_directly_decreases, object_modifier=activity())
+    add_directly_inhibits = partialmethod(add_directly_decreases, target_modifier=activity())
 
-    add_activates = partialmethod(add_increases, object_modifier=activity())
+    add_activates = partialmethod(add_increases, target_modifier=activity())
     """Add an "activates" relationship.
 
     A more specific version of :meth:`add_increases` that automatically populates the object modifier with an
     activity."""
 
-    add_directly_activates = partialmethod(add_directly_increases, object_modifier=activity())
+    add_directly_activates = partialmethod(add_directly_increases, target_modifier=activity())
 
     def _modify(
         self,
         add_edge_fn: str,
         name: str,
-        u,
-        v,
+        source: BaseEntity,
+        target: CentralDogma,
         code: Optional[str] = None,
         position: Optional[int] = None,
         *,
         evidence: str,
         citation: Union[str, Mapping[str, str]],
         annotations: Optional[AnnotationsHint] = None,
-        subject_modifier: Optional[Mapping] = None,
-        object_modifier: Optional[Mapping] = None,
+        source_modifier: Optional[Mapping] = None,
+        target_modifier: Optional[Mapping] = None,
         **attr
     ):
         """Add a simple modification."""
         adder = getattr(self, add_edge_fn)
         return adder(
-            u,
-            v.with_variants(ProteinModification(
+            source=source,
+            target=target.with_variants(ProteinModification(
                 name=name, code=code, position=position,
             )),
             evidence=evidence,
             citation=citation,
             annotations=annotations,
-            subject_modifier=subject_modifier,
-            object_modifier=object_modifier,
+            source_modifier=source_modifier,
+            target_modifier=target_modifier,
             **attr
         )
 
@@ -668,7 +667,7 @@ class BELGraph(nx.MultiDiGraph):
         self,
         reactants: Union[BaseAbundance, Iterable[BaseAbundance]],
         products: Union[BaseAbundance, Iterable[BaseAbundance]],
-    ):
+    ) -> None:
         """Add a reaction directly to the graph."""
         return self.add_node_from_data(Reaction(reactants=reactants, products=products))
 
@@ -905,9 +904,9 @@ def _handle_modifier(side_data: Dict[str, Any]) -> Mapping[str, Any]:
 
 def _handle_citation(citation: Union[str, Tuple[str, str], CitationDict]) -> CitationDict:
     if isinstance(citation, str):
-        return citation_dict(db=CITATION_TYPE_PUBMED, db_id=citation)
+        return citation_dict(namespace=CITATION_TYPE_PUBMED, identifier=citation)
     elif isinstance(citation, tuple):
-        return citation_dict(db=citation[0], db_id=citation[1])
+        return citation_dict(namespace=citation[0], identifier=citation[1])
     elif isinstance(citation, CitationDict):
         return citation
     elif isinstance(citation, dict):
@@ -915,7 +914,7 @@ def _handle_citation(citation: Union[str, Tuple[str, str], CitationDict]) -> Cit
     elif citation is None:
         raise ValueError('citation was None')
     else:
-        raise TypeError('citation is the wrong type: {}'.format(citation))
+        raise TypeError(f'citation is the wrong type: {citation}')
 
 
 class Dispatch:
@@ -987,7 +986,7 @@ class CountDispatch(Dispatch):
 def _iterate_citations(graph: BELGraph) -> Iterable[Tuple[str, str]]:
     for _, _, data in graph.edges(data=True):
         if CITATION in data:
-            yield data[CITATION][CITATION_DB], data[CITATION][CITATION_IDENTIFIER]
+            yield data[CITATION][NAMESPACE], data[CITATION][IDENTIFIER]
 
 
 def _iterate_authors(graph: BELGraph) -> Iterable[str]:
diff --git a/src/pybel/struct/grouping/provenance.py b/src/pybel/struct/grouping/provenance.py
index 72366b43..870c5ef5 100644
--- a/src/pybel/struct/grouping/provenance.py
+++ b/src/pybel/struct/grouping/provenance.py
@@ -6,7 +6,7 @@ from collections import defaultdict
 from typing import Mapping, Tuple
 
 from ..graph import BELGraph
-from ...constants import CITATION, CITATION_DB, CITATION_IDENTIFIER
+from ...constants import CITATION, IDENTIFIER, NAMESPACE
 
 __all__ = [
     'get_subgraphs_by_citation',
@@ -24,7 +24,7 @@ def get_subgraphs_by_citation(graph: BELGraph) -> Mapping[Tuple[str, str], BELGr
     for u, v, key, data in graph.edges(keys=True, data=True):
         if CITATION not in data:
             continue
-        dk = data[CITATION][CITATION_DB], data[CITATION][CITATION_IDENTIFIER]
+        dk = data[CITATION][NAMESPACE], data[CITATION][IDENTIFIER]
 
         rv[dk].add_edge(u, v, key=key, **data)
 
diff --git a/src/pybel/struct/mutation/inference/transfer.py b/src/pybel/struct/mutation/inference/transfer.py
index bfc0264b..c4e946a6 100644
--- a/src/pybel/struct/mutation/inference/transfer.py
+++ b/src/pybel/struct/mutation/inference/transfer.py
@@ -5,7 +5,10 @@
 from typing import Iterable, List
 
 from ...graph import BELGraph
-from ....constants import ANNOTATIONS, CAUSAL_RELATIONS, CITATION, EVIDENCE, IS_A, OBJECT, RELATION, SUBJECT
+from ....constants import (
+    ANNOTATIONS, CAUSAL_RELATIONS, CITATION, EVIDENCE, IS_A, RELATION, SOURCE_MODIFIER,
+    TARGET_MODIFIER,
+)
 from ....dsl import BaseEntity
 
 __all__ = [
@@ -35,8 +38,8 @@ def transfer_causal_edges(graph: BELGraph, source: BaseEntity, target: BaseEntit
             evidence=data[EVIDENCE],
             citation=data[CITATION],
             annotations=data.get(ANNOTATIONS),
-            subject_modifier=data.get(SUBJECT),
-            object_modifier=data.get(OBJECT),
+            source_modifier=data.get(SOURCE_MODIFIER),
+            target_modifier=data.get(TARGET_MODIFIER),
         )
 
     for u, _, data in graph.in_edges(source, data=True):
@@ -50,8 +53,8 @@ def transfer_causal_edges(graph: BELGraph, source: BaseEntity, target: BaseEntit
             evidence=data[EVIDENCE],
             citation=data[CITATION],
             annotations=data.get(ANNOTATIONS),
-            subject_modifier=data.get(SUBJECT),
-            object_modifier=data.get(OBJECT),
+            source_modifier=data.get(SOURCE_MODIFIER),
+            target_modifier=data.get(TARGET_MODIFIER),
         )
 
 
diff --git a/src/pybel/struct/mutation/metadata.py b/src/pybel/struct/mutation/metadata.py
index 6c830530..de8db2b8 100644
--- a/src/pybel/struct/mutation/metadata.py
+++ b/src/pybel/struct/mutation/metadata.py
@@ -6,7 +6,7 @@ import logging
 
 from ..graph import BELGraph
 from ..pipeline import in_place_transformation
-from ...constants import ANNOTATIONS, CITATION, CITATION_DB, CITATION_IDENTIFIER
+from ...constants import ANNOTATIONS, CITATION, IDENTIFIER, NAMESPACE
 
 __all__ = [
     'strip_annotations',
@@ -76,7 +76,7 @@ def remove_annotation_value(graph: BELGraph, annotation: str, value: str) -> Non
         del graph[u][v][k][ANNOTATIONS][annotation][value]
 
 
-_CITATION_KEEP_KEYS = {CITATION_IDENTIFIER, CITATION_DB}
+_CITATION_KEEP_KEYS = {IDENTIFIER, NAMESPACE}
 
 
 @in_place_transformation
diff --git a/src/pybel/struct/summary/errors.py b/src/pybel/struct/summary/errors.py
index 754ba8aa..fdfd6687 100644
--- a/src/pybel/struct/summary/errors.py
+++ b/src/pybel/struct/summary/errors.py
@@ -9,7 +9,7 @@ from typing import Iterable, List, Mapping, Set
 from ..filters.edge_predicates import edge_has_annotation
 from ..graph import BELGraph, WarningTuple
 from ...constants import ANNOTATIONS
-from ...parser.exc import BELSyntaxError, MissingNamespaceNameWarning, MissingNamespaceRegexWarning, NakedNameWarning
+from ...exceptions import BELSyntaxError, MissingNamespaceNameWarning, MissingNamespaceRegexWarning, NakedNameWarning
 
 __all__ = [
     'get_syntax_errors',
diff --git a/src/pybel/struct/summary/node_summary.py b/src/pybel/struct/summary/node_summary.py
index 57988fdc..c59a68da 100644
--- a/src/pybel/struct/summary/node_summary.py
+++ b/src/pybel/struct/summary/node_summary.py
@@ -10,8 +10,8 @@ from typing import Any, Iterable, List, Mapping, Optional, Set, Tuple
 from ..filters import get_nodes, has_activity, has_variant, is_degraded, is_translocated
 from ..graph import BELGraph
 from ...constants import (
-    ACTIVITY, CONCEPT, EFFECT, FROM_LOC, FUSION, KIND, LOCATION, MEMBERS, MODIFIER, NAME, NAMESPACE, OBJECT, PARTNER_3P,
-    PARTNER_5P, SUBJECT, TO_LOC, TRANSLOCATION, VARIANTS,
+    ACTIVITY, CONCEPT, EFFECT, FROM_LOC, FUSION, KIND, LOCATION, MEMBERS, MODIFIER, NAME, NAMESPACE, PARTNER_3P,
+    PARTNER_5P, SOURCE_MODIFIER, TARGET_MODIFIER, TO_LOC, TRANSLOCATION, VARIANTS,
 )
 from ...dsl import BaseConcept, BaseEntity, CentralDogma, EntityVariant, FusionBase, ListAbundance, Pathology, Reaction
 from ...language import Entity
@@ -75,7 +75,7 @@ def _iterate_namespaces(graph: BELGraph) -> Iterable[str]:
 
 
 def _iterate_edge_entities(graph: BELGraph) -> Iterable[Entity]:
-    for ((_, _, data), side) in itt.product(graph.edges(data=True), (SUBJECT, OBJECT)):
+    for ((_, _, data), side) in itt.product(graph.edges(data=True), (SOURCE_MODIFIER, TARGET_MODIFIER)):
         side_data = data.get(side)
         if side_data is None:
             continue
@@ -202,7 +202,7 @@ def _identifier_filtered_iterator(graph) -> Iterable[Tuple[str, str]]:
             for pair in _get_node_names(member):
                 yield pair
 
-    for ((_, _, data), side) in itt.product(graph.edges(data=True), (SUBJECT, OBJECT)):
+    for ((_, _, data), side) in itt.product(graph.edges(data=True), (SOURCE_MODIFIER, TARGET_MODIFIER)):
         side_data = data.get(side)
         if side_data is None:
             continue
diff --git a/src/pybel/struct/summary/provenance.py b/src/pybel/struct/summary/provenance.py
index f0d07ca7..084bdadc 100644
--- a/src/pybel/struct/summary/provenance.py
+++ b/src/pybel/struct/summary/provenance.py
@@ -6,7 +6,7 @@ from typing import Iterable, Set
 
 from ..filters.edge_predicates import has_pubmed
 from ..graph import BELGraph
-from ...constants import CITATION, CITATION_IDENTIFIER
+from ...constants import CITATION, IDENTIFIER
 
 __all__ = [
     'iterate_pubmed_identifiers',
@@ -21,7 +21,7 @@ def iterate_pubmed_identifiers(graph: BELGraph) -> Iterable[str]:
     :return: An iterator over the PubMed identifiers in the graph
     """
     return (
-        data[CITATION][CITATION_IDENTIFIER].strip()
+        data[CITATION][IDENTIFIER].strip()
         for _, _, data in graph.edges(data=True)
         if has_pubmed(data)
     )
diff --git a/src/pybel/struct/summary/supersummary.py b/src/pybel/struct/summary/supersummary.py
index 565ebcd9..d230e40b 100644
--- a/src/pybel/struct/summary/supersummary.py
+++ b/src/pybel/struct/summary/supersummary.py
@@ -11,8 +11,9 @@ import pandas as pd
 from humanize import intword
 from tabulate import tabulate
 
+from .node_summary import count_namespaces
 from ..graph import BELGraph
-from ...constants import CITATION, CITATION_DB, CITATION_IDENTIFIER, RELATION, TWO_WAY_RELATIONS
+from ...constants import CITATION, IDENTIFIER, NAMESPACE, RELATION, TWO_WAY_RELATIONS
 from ...dsl import BaseConcept
 from ...utils import multidict
 
@@ -45,12 +46,16 @@ def functions(graph, file: Optional[TextIO] = None, examples: bool = True):
 def namespaces_table_df(graph: BELGraph, examples: bool = True) -> pd.DataFrame:
     """Create a dataframe describing the namespaces in the graph."""
     namespace_mapping = multidict((node.namespace, node) for node in graph if isinstance(node, BaseConcept))
-    namespace_c = Counter({namespace: len(nodes) for namespace, nodes in namespace_mapping.items()})
+    namespace_c = count_namespaces(graph)
     if not examples:
         return pd.DataFrame(namespace_c.most_common(), columns=['Namespace', 'Count'])
     return pd.DataFrame(
         [
-            (namespace, count, random.choice(namespace_mapping[namespace]))  # noqa:S311
+            (
+                namespace,
+                count,
+                random.choice(namespace_mapping[namespace]) if namespace in namespace_mapping else '',  # noqa:S311
+            )
             for namespace, count in namespace_c.most_common()
         ],
         columns=['Namespace', 'Count', 'Example'],
@@ -105,7 +110,7 @@ def edges(
 def citations(graph: BELGraph, n: Optional[int] = 15, file: Optional[TextIO] = None) -> None:
     """Print a summary of the citations in the graph."""
     edge_mapping = multidict(
-        ((data[CITATION][CITATION_DB], data[CITATION][CITATION_IDENTIFIER]), graph.edge_to_bel(u, v, data))
+        ((data[CITATION][NAMESPACE], data[CITATION][IDENTIFIER]), graph.edge_to_bel(u, v, data))
         for u, v, data in graph.edges(data=True)
         if CITATION in data
     )
diff --git a/src/pybel/tokens.py b/src/pybel/tokens.py
index dd9113d4..2a43df6b 100644
--- a/src/pybel/tokens.py
+++ b/src/pybel/tokens.py
@@ -6,9 +6,8 @@ from typing import List
 
 from .constants import (
     CONCEPT, FRAGMENT, FRAGMENT_DESCRIPTION, FRAGMENT_START, FRAGMENT_STOP, FUNCTION, FUSION, FUSION_MISSING,
-    FUSION_REFERENCE, FUSION_START, FUSION_STOP, GMOD, HGVS, IDENTIFIER, KIND, MEMBERS, MODIFIER, NAME, NAMESPACE,
-    PARTNER_3P, PARTNER_5P, PMOD, PMOD_CODE, PMOD_POSITION, PRODUCTS, RANGE_3P, RANGE_5P, REACTANTS, REACTION, TARGET,
-    VARIANTS, XREFS,
+    FUSION_REFERENCE, FUSION_START, FUSION_STOP, GMOD, HGVS, IDENTIFIER, KIND, MEMBERS, NAME, NAMESPACE, PARTNER_3P,
+    PARTNER_5P, PMOD, PMOD_CODE, PMOD_POSITION, PRODUCTS, RANGE_3P, RANGE_5P, REACTANTS, REACTION, VARIANTS, XREFS,
 )
 from .dsl import (
     BaseAbundance, BaseEntity, CentralDogma, EnumeratedFusionRange, FUNC_TO_DSL, FUNC_TO_FUSION_DSL, FUNC_TO_LIST_DSL,
@@ -26,10 +25,9 @@ def parse_result_to_dsl(tokens) -> BaseEntity:
 
     :type tokens: dict or pyparsing.ParseResults
     """
-    if MODIFIER in tokens:
-        return parse_result_to_dsl(tokens[TARGET])
-
-    elif REACTION == tokens[FUNCTION]:
+    # if MODIFIER in tokens:
+    #     return parse_result_to_dsl(tokens[TARGET])
+    if REACTION == tokens[FUNCTION]:
         return _reaction_po_to_dict(tokens)
 
     elif VARIANTS in tokens:
diff --git a/src/pybel/utils.py b/src/pybel/utils.py
index 2450b7d9..0fb34303 100644
--- a/src/pybel/utils.py
+++ b/src/pybel/utils.py
@@ -13,8 +13,8 @@ from datetime import datetime
 from typing import Any, List, Mapping, Optional, Tuple, TypeVar
 
 from .constants import (
-    ACTIVITY, CITATION, CITATION_DB, CITATION_DB_NAME, CITATION_IDENTIFIER, DEGRADATION, EFFECT, EVIDENCE, FROM_LOC,
-    IDENTIFIER, LOCATION, MODIFIER, NAME, NAMESPACE, OBJECT, RELATION, SUBJECT, TO_LOC, TRANSLOCATION,
+    ACTIVITY, CITATION, DEGRADATION, EFFECT, EVIDENCE, FROM_LOC, IDENTIFIER, LOCATION, MODIFIER, NAME, NAMESPACE,
+    RELATION, SOURCE_MODIFIER, TARGET_MODIFIER, TO_LOC, TRANSLOCATION,
 )
 from .typing import EdgeData
 
@@ -138,7 +138,7 @@ def parse_datetime(s: str) -> datetime.date:
 def _get_citation_str(data: Mapping) -> Optional[str]:
     citation = data.get(CITATION)
     if citation is not None:
-        return '{}:{}'.format(citation[CITATION_DB], citation[CITATION_IDENTIFIER])
+        return citation.curie
 
 
 def hash_edge(source, target, edge_data: EdgeData) -> str:
@@ -224,8 +224,8 @@ def canonicalize_edge(edge_data: EdgeData) -> CanonicalEdge:
     """Canonicalize the edge to a tuple based on the relation, subject modifications, and object modifications."""
     return (
         edge_data[RELATION],
-        _canonicalize_edge_modifications(edge_data.get(SUBJECT)),
-        _canonicalize_edge_modifications(edge_data.get(OBJECT)),
+        _canonicalize_edge_modifications(edge_data.get(SOURCE_MODIFIER)),
+        _canonicalize_edge_modifications(edge_data.get(TARGET_MODIFIER)),
     )
 
 
@@ -300,26 +300,6 @@ def get_corresponding_pickle_path(path: str) -> str:
     return '{path}.pickle'.format(path=path)
 
 
-def citation_dict(*, db: str, db_id: str, db_name: Optional[str] = None, **kwargs):
-    """Make a citation dictionary."""
-    return CitationDict(db=db, db_id=db_id, db_name=db_name, **kwargs)
-
-
-class CitationDict(dict):
-    """A dictionary describing a citation."""
-
-    def __init__(self, db: str, db_id: str, *, db_name: Optional[str] = None, **kwargs):
-        super().__init__()
-        self[CITATION_DB] = db
-        self[CITATION_IDENTIFIER] = db_id
-        if db_name is not None:
-            self[CITATION_DB_NAME] = db_name
-        self.update(kwargs)
-
-    def __hash__(self):
-        return hash((self[CITATION_DB], self[CITATION_IDENTIFIER]))
-
-
 X = TypeVar('X')
 Y = TypeVar('Y')
 


Test patch:
diff --git a/tests/constant_helper.py b/tests/constant_helper.py
index cc6c91e7..df04cec2 100644
--- a/tests/constant_helper.py
+++ b/tests/constant_helper.py
@@ -11,7 +11,7 @@ from pybel.dsl import (
     Protein, ProteinFusion, ProteinModification, Reaction, Rna, RnaFusion, secretion, translocation,
 )
 from pybel.dsl.namespaces import hgnc
-from pybel.utils import citation_dict
+from pybel.language import activity_mapping, citation_dict, compartment_mapping
 
 logger = logging.getLogger(__name__)
 
@@ -36,8 +36,8 @@ expected_test_thorough_metadata = {
     METADATA_CONTACT: "charles.hoyt@scai.fraunhofer.de",
 }
 
-citation_1 = citation_dict(db=CITATION_TYPE_PUBMED, db_id='123455')
-citation_2 = citation_dict(db=CITATION_TYPE_PUBMED, db_id='123456')
+citation_1 = citation_dict(namespace=CITATION_TYPE_PUBMED, identifier='123455')
+citation_2 = citation_dict(namespace=CITATION_TYPE_PUBMED, identifier='123456')
 
 evidence_1 = "Evidence 1"
 dummy_evidence = 'These are mostly made up'
@@ -291,8 +291,8 @@ BEL_THOROUGH_EDGES = [
          EVIDENCE: dummy_evidence,
          CITATION: citation_1,
          RELATION: DIRECTLY_DECREASES,
-         SUBJECT: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
-         OBJECT: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
+         SOURCE_MODIFIER: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
+         TARGET_MODIFIER: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
      }),
     (akt1, Protein('HGNC', 'AKT1', variants=Hgvs('p.Arg1851*')), {
         RELATION: HAS_VARIANT,
@@ -304,7 +304,7 @@ BEL_THOROUGH_EDGES = [
         EVIDENCE: dummy_evidence,
         CITATION: citation_1,
         RELATION: INCREASES,
-        SUBJECT: {MODIFIER: DEGRADATION},
+        SOURCE_MODIFIER: {MODIFIER: DEGRADATION},
     }),
     (akt1, Protein('HGNC', 'CFTR', variants=Hgvs('p.Gly576Ala')), {
         EVIDENCE: dummy_evidence,
@@ -315,39 +315,33 @@ BEL_THOROUGH_EDGES = [
         EVIDENCE: dummy_evidence,
         CITATION: citation_1,
         RELATION: INCREASES,
-        SUBJECT: {MODIFIER: ACTIVITY, EFFECT: {NAMESPACE: BEL_DEFAULT_NAMESPACE, NAME: 'kin'}},
+        SOURCE_MODIFIER: {MODIFIER: ACTIVITY, EFFECT: activity_mapping['kin']},
     }),
     (akt1, Rna('HGNC', 'CFTR', variants=Hgvs('r.1653_1655delcuu')), {
         EVIDENCE: dummy_evidence,
         CITATION: citation_1,
         RELATION: INCREASES,
-        SUBJECT: {MODIFIER: ACTIVITY},
+        SOURCE_MODIFIER: {MODIFIER: ACTIVITY},
     }),
     (akt1, egfr, {
         EVIDENCE: dummy_evidence,
         CITATION: citation_1,
         RELATION: INCREASES,
-        SUBJECT: {
+        SOURCE_MODIFIER: {
             MODIFIER: ACTIVITY,
-            EFFECT: {
-                NAMESPACE: BEL_DEFAULT_NAMESPACE,
-                NAME: 'cat'
-            }
+            EFFECT: activity_mapping['cat'],
         },
-        OBJECT: {MODIFIER: DEGRADATION},
+        TARGET_MODIFIER: {MODIFIER: DEGRADATION},
     }),
     (akt1, egfr, {
         EVIDENCE: dummy_evidence,
         CITATION: citation_1,
         RELATION: INCREASES,
-        SUBJECT: {
+        SOURCE_MODIFIER: {
             MODIFIER: ACTIVITY,
-            EFFECT: {NAME: 'kin', NAMESPACE: BEL_DEFAULT_NAMESPACE}
+            EFFECT: activity_mapping['kin'],
         },
-        OBJECT: translocation(
-            {NAMESPACE: BEL_DEFAULT_NAMESPACE, NAME: 'intracellular'},
-            {NAMESPACE: BEL_DEFAULT_NAMESPACE, NAME: 'extracellular space'}
-        ),
+        TARGET_MODIFIER: secretion(),
     }),
     (Gene('HGNC', 'AKT1', variants=Hgvs('c.308G>A')), tmprss2_erg_gene_fusion, {
         EVIDENCE: dummy_evidence,
@@ -359,7 +353,7 @@ BEL_THOROUGH_EDGES = [
          EVIDENCE: dummy_evidence,
          CITATION: citation_1,
          RELATION: INCREASES,
-         SUBJECT: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
+         SOURCE_MODIFIER: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
      }),
     (MicroRna('HGNC', 'MIR21'), bcr_jak2_gene_fusion,
      {
@@ -372,7 +366,7 @@ BEL_THOROUGH_EDGES = [
          EVIDENCE: dummy_evidence,
          CITATION: citation_1,
          RELATION: DECREASES,
-         SUBJECT: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
+         SOURCE_MODIFIER: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
      }),
     (MicroRna('HGNC', 'MIR21'), MicroRna('HGNC', 'MIR21', variants=Hgvs('p.Phe508del')), {
         RELATION: HAS_VARIANT,
@@ -382,7 +376,7 @@ BEL_THOROUGH_EDGES = [
          EVIDENCE: dummy_evidence,
          CITATION: citation_1,
          RELATION: INCREASES,
-         OBJECT: {MODIFIER: DEGRADATION},
+         TARGET_MODIFIER: {MODIFIER: DEGRADATION},
      }),
     (Gene('HGNC', 'CFTR'), Gene('HGNC', 'CFTR', variants=Hgvs('c.1521_1523delCTT')), {
         RELATION: HAS_VARIANT,
@@ -404,7 +398,7 @@ BEL_THOROUGH_EDGES = [
          EVIDENCE: dummy_evidence,
          CITATION: citation_1,
          RELATION: INCREASES,
-         SUBJECT: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
+         SOURCE_MODIFIER: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
      }),
     (chchd4_aifm1_gene_fusion, tmprss2_erg_protein_fusion, {
         EVIDENCE: dummy_evidence,
@@ -426,7 +420,7 @@ BEL_THOROUGH_EDGES = [
         EVIDENCE: dummy_evidence,
         CITATION: citation_1,
         RELATION: INCREASES,
-        OBJECT: translocation(INTRACELLULAR, CELL_SURFACE),
+        TARGET_MODIFIER: translocation(INTRACELLULAR, CELL_SURFACE),
     }),
     (cftr, Protein('HGNC', 'CFTR', variants=Hgvs('=')), {
         RELATION: HAS_VARIANT,
@@ -550,13 +544,13 @@ BEL_THOROUGH_EDGES = [
         EVIDENCE: dummy_evidence,
         CITATION: citation_1,
         RELATION: INCREASES,
-        OBJECT: secretion(),
+        TARGET_MODIFIER: secretion(),
     }),
     (Protein('HGNC', 'MIA', variants=Fragment(1, '?')), egfr, {
         EVIDENCE: dummy_evidence,
         CITATION: citation_1,
         RELATION: INCREASES,
-        OBJECT: {
+        TARGET_MODIFIER: {
             MODIFIER: TRANSLOCATION,
             EFFECT: {
                 FROM_LOC: {NAMESPACE: 'GO', NAME: 'cell surface'},
@@ -568,7 +562,7 @@ BEL_THOROUGH_EDGES = [
         EVIDENCE: dummy_evidence,
         CITATION: citation_1,
         RELATION: INCREASES,
-        OBJECT: {
+        TARGET_MODIFIER: {
             MODIFIER: TRANSLOCATION, EFFECT: {
                 FROM_LOC: {NAMESPACE: 'GO', NAME: 'cell surface'},
                 TO_LOC: {NAMESPACE: 'GO', NAME: 'endosome'}
@@ -592,19 +586,19 @@ BEL_THOROUGH_EDGES = [
         EVIDENCE: 'These were all explicitly stated in the BEL 2.0 Specification',
         CITATION: citation_2,
         RELATION: DIRECTLY_DECREASES,
-        SUBJECT: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
+        SOURCE_MODIFIER: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
     }),
     (Gene('HGNC', 'CAT'), hydrogen_peroxide, {
         EVIDENCE: 'These were all explicitly stated in the BEL 2.0 Specification',
         CITATION: citation_2,
         RELATION: DIRECTLY_DECREASES,
-        SUBJECT: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
+        SOURCE_MODIFIER: {LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}},
     }),
     (Protein('HGNC', 'HMGCR'), BiologicalProcess('GO', 'cholesterol biosynthetic process'), {
         EVIDENCE: 'These were all explicitly stated in the BEL 2.0 Specification',
         CITATION: citation_2,
         RELATION: RATE_LIMITING_STEP_OF,
-        SUBJECT: {MODIFIER: ACTIVITY, EFFECT: {NAMESPACE: BEL_DEFAULT_NAMESPACE, NAME: 'cat'}},
+        SOURCE_MODIFIER: {MODIFIER: ACTIVITY, EFFECT: activity_mapping['cat']},
     }),
     (Gene('HGNC', 'APP', variants=Hgvs('c.275341G>C')), Pathology('MESHD', 'Alzheimer Disease'),
      {
@@ -617,21 +611,21 @@ BEL_THOROUGH_EDGES = [
         EVIDENCE: 'These were all explicitly stated in the BEL 2.0 Specification',
         CITATION: citation_2,
         RELATION: REGULATES,
-        SUBJECT: {MODIFIER: ACTIVITY, EFFECT: {NAME: 'pep', NAMESPACE: BEL_DEFAULT_NAMESPACE}},
-        OBJECT: {MODIFIER: ACTIVITY, EFFECT: {NAME: 'pep', NAMESPACE: BEL_DEFAULT_NAMESPACE}},
+        SOURCE_MODIFIER: {MODIFIER: ACTIVITY, EFFECT: activity_mapping['pep']},
+        TARGET_MODIFIER: {MODIFIER: ACTIVITY, EFFECT: activity_mapping['pep']},
     }),
     (Protein('HGNC', 'GSK3B', variants=ProteinModification('Ph', 'Ser', 9)), Protein('HGNC', 'GSK3B'), {
         EVIDENCE: 'These were all explicitly stated in the BEL 2.0 Specification',
         CITATION: citation_2,
         RELATION: POSITIVE_CORRELATION,
-        OBJECT: {MODIFIER: ACTIVITY, EFFECT: {NAMESPACE: BEL_DEFAULT_NAMESPACE, NAME: 'kin'}},
+        TARGET_MODIFIER: {MODIFIER: ACTIVITY, EFFECT: activity_mapping['kin']},
     }),
 
     (Protein('HGNC', 'GSK3B'), Protein('HGNC', 'GSK3B', variants=ProteinModification('Ph', 'Ser', 9)), {
         EVIDENCE: 'These were all explicitly stated in the BEL 2.0 Specification',
         CITATION: citation_2,
         RELATION: POSITIVE_CORRELATION,
-        SUBJECT: {MODIFIER: ACTIVITY, EFFECT: {NAMESPACE: BEL_DEFAULT_NAMESPACE, NAME: 'kin'}},
+        SOURCE_MODIFIER: {MODIFIER: ACTIVITY, EFFECT: activity_mapping['kin']},
     }),
 
     (Reaction(
@@ -726,11 +720,11 @@ BEL_THOROUGH_EDGES = [
          EVIDENCE: 'These were all explicitly stated in the BEL 2.0 Specification',
          CITATION: citation_2,
          RELATION: INCREASES,
-         OBJECT: {
+         TARGET_MODIFIER: {
              MODIFIER: TRANSLOCATION,
              EFFECT: {
-                 FROM_LOC: {NAMESPACE: BEL_DEFAULT_NAMESPACE, NAME: 'intracellular'},
-                 TO_LOC: {NAMESPACE: BEL_DEFAULT_NAMESPACE, NAME: 'cell surface'}
+                 FROM_LOC: compartment_mapping['intracellular'],
+                 TO_LOC: compartment_mapping['cell surface'],
              }
          },
      }),
diff --git a/tests/constants.py b/tests/constants.py
index 7d76778d..19ab7426 100644
--- a/tests/constants.py
+++ b/tests/constants.py
@@ -10,13 +10,13 @@ from json import dumps
 from pybel import BELGraph
 from pybel.canonicalize import edge_to_bel
 from pybel.constants import (
-    ANNOTATIONS, ASSOCIATION, CITATION, CITATION_DB, CITATION_IDENTIFIER, CITATION_TYPE_PUBMED, DECREASES,
-    DIRECTLY_DECREASES, EVIDENCE, INCREASES, METADATA_AUTHORS, METADATA_DESCRIPTION, METADATA_LICENSES, METADATA_NAME,
-    METADATA_VERSION, PART_OF, RELATION,
+    ANNOTATIONS, ASSOCIATION, CITATION, CITATION_TYPE_PUBMED, DECREASES, DIRECTLY_DECREASES, EVIDENCE, IDENTIFIER,
+    INCREASES, METADATA_AUTHORS, METADATA_DESCRIPTION, METADATA_LICENSES, METADATA_NAME, METADATA_VERSION, NAMESPACE,
+    PART_OF, RELATION,
 )
 from pybel.dsl import BaseEntity, ComplexAbundance, Pathology, Protein
 from pybel.dsl.namespaces import hgnc
-from pybel.parser.exc import (
+from pybel.exceptions import (
     BELParserWarning, BELSyntaxError, IllegalAnnotationValueWarning, InvalidCitationLengthException,
     InvalidCitationType, InvalidFunctionSemantic, InvalidPubMedIdentifierWarning, MalformedTranslocationWarning,
     MissingAnnotationKeyWarning, MissingAnnotationRegexWarning, MissingCitationException, MissingMetadataException,
@@ -27,7 +27,8 @@ from pybel.parser.exc import (
 from pybel.parser.parse_bel import BELParser
 from pybel.parser.parse_control import ControlParser
 from pybel.testing.constants import test_bel_thorough
-from pybel.utils import citation_dict, subdict_matches
+from pybel.utils import subdict_matches
+from pybel.language import citation_dict
 from tests.constant_helper import (
     BEL_THOROUGH_EDGES, BEL_THOROUGH_NODES, citation_1, evidence_1, expected_test_simple_metadata,
     expected_test_thorough_metadata,
@@ -38,11 +39,11 @@ logger = logging.getLogger(__name__)
 OPENBEL_DOMAIN = 'http://resources.openbel.org'
 OPENBEL_ANNOTATION_RESOURCES = OPENBEL_DOMAIN + '/belframework/20150611/annotation/'
 
-test_citation_dict = citation_dict(db=CITATION_TYPE_PUBMED, db_id='1235813')
+test_citation_dict = citation_dict(namespace=CITATION_TYPE_PUBMED, identifier='1235813')
 
-SET_CITATION_TEST = 'SET Citation = {{"{db}", "{db_id}"}}'.format(**test_citation_dict)
+SET_CITATION_TEST = f'SET Citation = {{"{test_citation_dict.namespace}", "{test_citation_dict.identifier}"}}'
 test_evidence_text = 'I read it on Twitter'
-test_set_evidence = 'SET Evidence = "{}"'.format(test_evidence_text)
+test_set_evidence = f'SET Evidence = "{test_evidence_text}"'
 
 HGNC_KEYWORD = 'HGNC'
 MESH_DISEASES_KEYWORD = 'MeSHDisease'
@@ -56,8 +57,8 @@ casp8 = hgnc(name='CASP8')
 
 def update_provenance(control_parser: ControlParser) -> None:
     """Put a default evidence and citation in a BEL parser."""
-    control_parser.citation_db = test_citation_dict[CITATION_DB]
-    control_parser.citation_db_id = test_citation_dict[CITATION_IDENTIFIER]
+    control_parser.citation_db = test_citation_dict.namespace
+    control_parser.citation_db_id = test_citation_dict.identifier
     control_parser.evidence = test_evidence_text
 
 
@@ -248,13 +249,13 @@ class BelReconstitutionMixin(TestGraphMixin):
         self.assertIn(casp8, graph)
 
         bel_simple_citation_1 = {
-            CITATION_IDENTIFIER: "123455",
-            CITATION_DB: CITATION_TYPE_PUBMED,
+            IDENTIFIER: "123455",
+            NAMESPACE: CITATION_TYPE_PUBMED,
         }
 
         bel_simple_citation_2 = {
-            CITATION_IDENTIFIER: "123456",
-            CITATION_DB: CITATION_TYPE_PUBMED,
+            IDENTIFIER: "123456",
+            NAMESPACE: CITATION_TYPE_PUBMED,
         }
 
         evidence_1_extra = "Evidence 1 w extra notes"
diff --git a/tests/test_canonicalization.py b/tests/test_canonicalization.py
index 1dc69c51..888b43cd 100644
--- a/tests/test_canonicalization.py
+++ b/tests/test_canonicalization.py
@@ -150,14 +150,14 @@ class TestCanonicalizeEdge(unittest.TestCase):
     def get_data(self, k):
         return self.g[self.u][self.v][k]
 
-    def add_edge(self, subject_modifier=None, object_modifier=None, annotations=None):
+    def add_edge(self, source_modifier=None, target_modifier=None, annotations=None):
         key = self.g.add_increases(
             self.u,
             self.v,
             evidence=n(),
             citation=n(),
-            subject_modifier=subject_modifier,
-            object_modifier=object_modifier,
+            source_modifier=source_modifier,
+            target_modifier=target_modifier,
             annotations=annotations,
         )
 
@@ -165,7 +165,7 @@ class TestCanonicalizeEdge(unittest.TestCase):
 
     def test_failure(self):
         with self.assertRaises(ValueError):
-            self.add_edge(subject_modifier={MODIFIER: 'nope'})
+            self.add_edge(source_modifier={MODIFIER: 'nope'})
 
     def test_canonicalize_edge_info(self):
         c1 = self.add_edge(
@@ -181,11 +181,11 @@ class TestCanonicalizeEdge(unittest.TestCase):
         )
 
         c3 = self.add_edge(
-            subject_modifier=activity('tport'),
+            source_modifier=activity('tport'),
         )
 
         c4 = self.add_edge(
-            subject_modifier=activity('tport', namespace=BEL_DEFAULT_NAMESPACE),
+            source_modifier=activity('tport', namespace=BEL_DEFAULT_NAMESPACE),
         )
 
         self.assertEqual(c1, c2)
@@ -195,40 +195,40 @@ class TestCanonicalizeEdge(unittest.TestCase):
     def test_subject_degradation_location(self):
         self.assertEqual(
             self.add_edge(
-                subject_modifier=degradation()
+                source_modifier=degradation()
             ),
             self.add_edge(
-                subject_modifier=degradation()
+                source_modifier=degradation()
             )
         )
 
         self.assertEqual(
             self.add_edge(
-                subject_modifier=degradation(location=Entity(name='somewhere', namespace='GO'))
+                source_modifier=degradation(location=Entity(name='somewhere', namespace='GO'))
             ),
             self.add_edge(
-                subject_modifier=degradation(location=Entity(name='somewhere', namespace='GO'))
+                source_modifier=degradation(location=Entity(name='somewhere', namespace='GO'))
             )
         )
 
         self.assertNotEqual(
             self.add_edge(
-                subject_modifier=degradation()
+                source_modifier=degradation()
             ),
             self.add_edge(
-                subject_modifier=degradation(location=Entity(name='somewhere', namespace='GO'))
+                source_modifier=degradation(location=Entity(name='somewhere', namespace='GO'))
             )
         )
 
     def test_translocation(self):
         self.assertEqual(
-            self.add_edge(subject_modifier=secretion()),
-            self.add_edge(subject_modifier=secretion()),
+            self.add_edge(source_modifier=secretion()),
+            self.add_edge(source_modifier=secretion()),
         )
 
         self.assertEqual(
-            self.add_edge(subject_modifier=secretion()),
-            self.add_edge(subject_modifier=translocation(INTRACELLULAR, EXTRACELLULAR)),
+            self.add_edge(source_modifier=secretion()),
+            self.add_edge(source_modifier=translocation(INTRACELLULAR, EXTRACELLULAR)),
         )
 
 
diff --git a/tests/test_io/test_cx/cases.py b/tests/test_io/test_cx/cases.py
index 2f6fee02..9995283f 100644
--- a/tests/test_io/test_cx/cases.py
+++ b/tests/test_io/test_cx/cases.py
@@ -7,9 +7,7 @@ import unittest
 from typing import Mapping, Tuple
 
 from pybel import BELGraph, BaseEntity
-from pybel.constants import (
-    ANNOTATIONS, CITATION, CITATION_DB, CITATION_IDENTIFIER, EVIDENCE, OBJECT, RELATION, SUBJECT,
-)
+from pybel.constants import ANNOTATIONS, CITATION, EVIDENCE, IDENTIFIER, NAMESPACE, TARGET_MODIFIER, RELATION, SOURCE_MODIFIER
 from pybel.typing import EdgeData
 
 __all__ = [
@@ -26,7 +24,7 @@ def _edge_to_tuple(u: BaseEntity, v: BaseEntity, edge_data: EdgeData):
     if citation is None:
         citation_hashable = None
     else:
-        citation_hashable = (citation[CITATION_DB], citation[CITATION_IDENTIFIER])
+        citation_hashable = (citation[NAMESPACE], citation[IDENTIFIER])
 
     evidence_hashable = edge_data.get(EVIDENCE)
 
@@ -39,17 +37,17 @@ def _edge_to_tuple(u: BaseEntity, v: BaseEntity, edge_data: EdgeData):
             for key, values in sorted(annotations.items())
         )
 
-    subject = edge_data.get(SUBJECT)
-    if subject is None:
+    source_modifier = edge_data.get(SOURCE_MODIFIER)
+    if source_modifier is None:
         subject_hashable = None
     else:
-        subject_hashable = json.dumps(subject, ensure_ascii=True, sort_keys=True, indent=0)
+        subject_hashable = json.dumps(source_modifier, ensure_ascii=True, sort_keys=True, indent=0)
 
-    obj = edge_data.get(OBJECT)
-    if obj is None:
+    target_modifier = edge_data.get(TARGET_MODIFIER)
+    if target_modifier is None:
         object_hashable = None
     else:
-        object_hashable = json.dumps(obj, ensure_ascii=True, sort_keys=True, indent=0)
+        object_hashable = json.dumps(target_modifier, ensure_ascii=True, sort_keys=True, indent=0)
 
     return (
         u,
diff --git a/tests/test_io/test_cx/examples.py b/tests/test_io/test_cx/examples.py
index bc89cc2e..2b1d1e27 100644
--- a/tests/test_io/test_cx/examples.py
+++ b/tests/test_io/test_cx/examples.py
@@ -8,7 +8,7 @@ from pybel.dsl import (
     ComplexAbundance, Protein, abundance, activity, bioprocess, gene, gmod, named_complex_abundance, pathology, pmod,
     protein_fusion, protein_substitution, reaction, rna,
 )
-from pybel.utils import citation_dict
+from pybel.language import citation_dict
 
 example_graph = BELGraph()
 
@@ -45,17 +45,17 @@ kin(p(HGNC:PTK2)) increases kin(p(HGNC:MAPK3))
 kin(p(HGNC:PTK2)) increases kin(complex(SCOMP:"p85/p110 PI3Kinase Complex"))
 """
 
-example_graph.add_increases(ptk2, mapk1, evidence=e1, citation=c1, object_modifier=kinase_activity)
-example_graph.add_increases(ptk2_rgb2_sos1, ras_family, evidence=e1, citation=c1, object_modifier=gtp_activity)
-example_graph.add_increases(ras_family, mapk1, evidence=e1, citation=c1, subject_modifier=gtp_activity,
-                            object_modifier=kinase_activity)
-example_graph.add_increases(ptk2, ptk2_rgb2_sos1, evidence=e1, citation=c1, subject_modifier=kinase_activity)
-example_graph.add_increases(ptk2, mapk1, evidence=e1, citation=c1, subject_modifier=kinase_activity,
-                            object_modifier=kinase_activity)
-example_graph.add_increases(ptk2, mapk3, evidence=e1, citation=c1, subject_modifier=kinase_activity,
-                            object_modifier=kinase_activity)
-example_graph.add_increases(ptk2, pi3k_complex, evidence=e1, citation=c1, subject_modifier=kinase_activity,
-                            object_modifier=kinase_activity)
+example_graph.add_increases(ptk2, mapk1, evidence=e1, citation=c1, target_modifier=kinase_activity)
+example_graph.add_increases(ptk2_rgb2_sos1, ras_family, evidence=e1, citation=c1, target_modifier=gtp_activity)
+example_graph.add_increases(ras_family, mapk1, evidence=e1, citation=c1, source_modifier=gtp_activity,
+                            target_modifier=kinase_activity)
+example_graph.add_increases(ptk2, ptk2_rgb2_sos1, evidence=e1, citation=c1, source_modifier=kinase_activity)
+example_graph.add_increases(ptk2, mapk1, evidence=e1, citation=c1, source_modifier=kinase_activity,
+                            target_modifier=kinase_activity)
+example_graph.add_increases(ptk2, mapk3, evidence=e1, citation=c1, source_modifier=kinase_activity,
+                            target_modifier=kinase_activity)
+example_graph.add_increases(ptk2, pi3k_complex, evidence=e1, citation=c1, source_modifier=kinase_activity,
+                            target_modifier=kinase_activity)
 
 """
 SET Evidence=" two common MTHFR polymorphisms, namely 677C>T (Ala222Val) and 1298A>C (Glu429Ala), are known to reduce MTHFR activity. \
@@ -81,8 +81,8 @@ mthfr_a1298c = Protein(namespace='hgnc', name='MTHFR', variants=[protein_substit
 folic_acid = abundance('CHEBI', 'folic acid')
 alzheimer_disease = pathology('MESH', 'Alzheimer Disease')
 
-example_graph.add_decreases(mthfr_c677t, mthfr, citation=c2, evidence=e2, object_modifier=activity())
-example_graph.add_decreases(mthfr_a1298c, mthfr, citation=c2, evidence=e2, object_modifier=activity())
+example_graph.add_decreases(mthfr_c677t, mthfr, citation=c2, evidence=e2, target_modifier=activity())
+example_graph.add_decreases(mthfr_a1298c, mthfr, citation=c2, evidence=e2, target_modifier=activity())
 example_graph.add_negative_correlation(mthfr_c677t, folic_acid, citation=c2, evidence=e2)
 example_graph.add_positive_correlation(mthfr_c677t, alzheimer_disease, citation=c2, evidence=e2)
 
@@ -115,7 +115,7 @@ a(SCHEM:Kallidin) increases cat(p(HGNC:BDKRB1))
 cat(p(HGNC:BDKRB1)) increases cat(p(SFAM:"PLA2 Family"))
 """
 
-c4 = citation_dict(db=CITATION_TYPE_OTHER, db_id='Genstruct Reference')
+c4 = citation_dict(namespace=CITATION_TYPE_OTHER, identifier='Genstruct Reference')
 e4 = '% Entrez Gene summary: Rat: SUMMARY: precursor protein of kinin which is found in plasma; cysteine protease inhibitor and a major acute phase reactant [RGD] OMIM summary: (summary is not available from this source) kininogens; Endogenous peptides present in most body fluids. Certain enzymes convert them to active kinins which are involved in inflammation, blood clotting, complement reactions, etc. Kininogens belong to the cystatin superfamily. They are cysteine proteinase inhibitors. High-molecular-weight kininogen (hmwk) is split by plasma kallikrein to produce bradykinin. Low-molecular-weight kininogen (lmwk) is split by tissue kallikrein to produce kallidin. kinins; Inflammatory mediators that cause dilation of blood vessels and altered vascular permeability.  Kinins are small peptides produced from kininogen by kallikrein and are broken down by kininases. Act on phospholipase and increase arachidonic acid release and thus prostaglandin (PGE2) production. bradykinin; Vasoactive nonapeptide (RPPGFSPFR) formed by action of proteases on kininogens. Very similar to kallidin (which has the same sequence but with an additional N terminal lysine). Bradykinin is a very potent vasodilator and increases permeability of post capillary venules, it acts on endothelial cells to activate phospholipase A2. It is also spasmogenic for some smooth muscle and will cause pain. kallidin; Decapeptide (lysyl bradykinin, amino acid sequence KRPPGFSPFR) produced in kidney. Like bradykinin, an inflammatory mediator (a kinin), causes dilation of renal blood vessels and increased water excretion.'
 e4 = str(hash(e4))
 
@@ -127,9 +127,9 @@ pla2_family = Protein('SFAM', 'PLA2 Family')
 kng1_to_kallidin = reaction(reactants=[kng1], products=[kallidin])
 
 example_graph.add_increases(inflammatory_process, kng1_to_kallidin, citation=c4, evidence=e4)
-example_graph.add_increases(kallidin, bdkrb1, citation=c4, evidence=e4, object_modifier=catalytic_activity)
-example_graph.add_increases(bdkrb1, pla2_family, citation=c4, evidence=e4, subject_modifier=catalytic_activity,
-                            object_modifier=catalytic_activity)
+example_graph.add_increases(kallidin, bdkrb1, citation=c4, evidence=e4, target_modifier=catalytic_activity)
+example_graph.add_increases(bdkrb1, pla2_family, citation=c4, evidence=e4, source_modifier=catalytic_activity,
+                            target_modifier=catalytic_activity)
 
 c5 = '10866298'
 e5 = 'We found that PD180970 inhibited in vivo tyrosine phosphorylation of p210Bcr-Abl (IC50 = 170 nM) and the p210BcrAbl substrates Gab2 and CrkL (IC50 = 80 nM) in human K562 chronic myelogenous leukemic cells. In vitro, PD180970 potently inhibited autophosphorylation of p210Bcr-Abl (IC50 = 5 nM) and the kinase activity of purified recombinant Abl tyrosine kinase (IC50 = 2.2 nM).'
@@ -147,8 +147,12 @@ bcr_abl1_fus = protein_fusion(partner_5p=Protein(namespace='hgnc', name='BCR'),
 crkl_ph = Protein(namespace='hgnc', name='CRKL', variants=[pmod('Ph', 'Tyr')])
 gab2_ph = Protein(namespace='hgnc', name='GAB2', variants=[pmod('Ph', 'Tyr')])
 
-example_graph.add_directly_increases(bcr_abl1_fus, crkl_ph, citation=c5, evidence=e5,
-                                     annotations={'Species': '9606', 'Confidence': 'High'},
-                                     subject_modifier=kinase_activity)
-example_graph.add_directly_increases(bcr_abl1_fus, gab2_ph, citation=c5, evidence=e5, annotations={'Species': '9606'},
-                                     subject_modifier=kinase_activity)
+example_graph.add_directly_increases(
+    bcr_abl1_fus, crkl_ph, citation=c5, evidence=e5,
+    annotations={'Species': '9606', 'Confidence': 'High'},
+    source_modifier=kinase_activity,
+)
+example_graph.add_directly_increases(
+    bcr_abl1_fus, gab2_ph, citation=c5, evidence=e5, annotations={'Species': '9606'},
+    source_modifier=kinase_activity,
+)
diff --git a/tests/test_io/test_hipathia/test_export.py b/tests/test_io/test_hipathia/test_export.py
index 211a993b..247efaf2 100644
--- a/tests/test_io/test_hipathia/test_export.py
+++ b/tests/test_io/test_hipathia/test_export.py
@@ -10,6 +10,7 @@ import pandas as pd
 from pybel import BELGraph
 from pybel.dsl import ComplexAbundance, Protein
 from pybel.io.hipathia import ATT_COLS, to_hipathia_dfs
+from pybel.testing.utils import n
 
 PROTEIN_NAMESPACE = 'ncbigene'
 FAMILY_NAMESPACE = 'hipathia.family'
@@ -40,7 +41,7 @@ class TestExportHipathia(unittest.TestCase):
 
     def test_protein_activates_protein(self):
         """Test conversion of ``p(A) -> p(D)``."""
-        self.graph.add_increases(a, d, citation='', evidence='')
+        self.graph.add_increases(a, d, citation=n(), evidence='')
 
         att_df, sif_df = self._get_dfs()
 
@@ -64,7 +65,7 @@ class TestExportHipathia(unittest.TestCase):
 
     def test_protein_activates_family(self):
         """Test conversion of ``p(A) -> p(B); p(B1) isA p(B); p(B2) isA p(B)``."""
-        self.graph.add_increases(a, b_family, citation='', evidence='')
+        self.graph.add_increases(a, b_family, citation=n(), evidence=n())
         self.graph.add_is_a(b1, b_family)
         self.graph.add_is_a(b2, b_family)
 
@@ -90,7 +91,7 @@ class TestExportHipathia(unittest.TestCase):
 
     def test_protein_activates_complex_proteins(self):
         """Test conversion of ``p(A) -> complex(p(E), p(F))``."""
-        self.graph.add_increases(a, e_f, citation='', evidence='')
+        self.graph.add_increases(a, e_f, citation=n(), evidence='')
 
         att_df, sif_df = self._get_dfs()
 
@@ -115,7 +116,7 @@ class TestExportHipathia(unittest.TestCase):
 
     def test_protein_activates_complex_mixed(self):
         """Test conversion of ``p(A) -> complex(p(E), p(B))`` when b is a family."""
-        self.graph.add_increases(a, e_f, citation='', evidence='')
+        self.graph.add_increases(a, e_f, citation=n(), evidence='')
 
         att_df, sif_df = self._get_dfs()
 
diff --git a/tests/test_io/test_import.py b/tests/test_io/test_import.py
index f7a87a65..1b66eca2 100644
--- a/tests/test_io/test_import.py
+++ b/tests/test_io/test_import.py
@@ -21,13 +21,13 @@ from pybel.constants import (
 )
 from pybel.dsl import BaseEntity, Gene, Protein
 from pybel.examples.sialic_acid_example import sialic_acid_graph
+from pybel.exceptions import (
+    BELSyntaxError, InvalidFunctionSemantic, MissingCitationException, MissingNamespaceRegexWarning,
+)
 from pybel.io.exc import ImportVersionWarning, import_version_message_fmt
 from pybel.io.line_utils import parse_lines
 from pybel.io.nodelink import from_nodelink_jsons, to_nodelink_jsons
 from pybel.parser import BELParser
-from pybel.parser.exc import (
-    BELSyntaxError, InvalidFunctionSemantic, MissingCitationException, MissingNamespaceRegexWarning,
-)
 from pybel.struct.summary import get_syntax_errors
 from pybel.testing.cases import TemporaryCacheClsMixin, TemporaryCacheMixin
 from pybel.testing.constants import (
diff --git a/tests/test_io/test_jgif.py b/tests/test_io/test_jgif.py
index 75eded5c..0c81e848 100644
--- a/tests/test_io/test_jgif.py
+++ b/tests/test_io/test_jgif.py
@@ -9,10 +9,14 @@ import unittest
 
 from pybel import from_cbn_jgif, to_jgif
 from pybel.constants import (
-    ACTIVITY, ANNOTATIONS, BEL_DEFAULT_NAMESPACE, CITATION, CITATION_IDENTIFIER, CITATION_DB, CITATION_TYPE_OTHER,
-    CITATION_TYPE_PUBMED, DECREASES, DIRECTLY_INCREASES, EFFECT, EVIDENCE, MODIFIER, NAME, NAMESPACE, OBJECT, RELATION,
+    ACTIVITY, ANNOTATIONS, CITATION, CITATION_TYPE_OTHER, CITATION_TYPE_PUBMED, DECREASES, DIRECTLY_INCREASES, EFFECT,
+    EVIDENCE, IDENTIFIER, MODIFIER, NAMESPACE, TARGET_MODIFIER, RELATION,
 )
-from pybel.dsl import Abundance, BiologicalProcess, ComplexAbundance, NamedComplexAbundance, Pathology, Protein, ProteinModification
+from pybel.dsl import (
+    Abundance, BiologicalProcess, ComplexAbundance, NamedComplexAbundance, Pathology, Protein,
+    ProteinModification,
+)
+from pybel.language import activity_mapping
 from pybel.testing.constants import test_jgif_path
 from tests.constants import TestGraphMixin
 
@@ -80,10 +84,10 @@ jgif_expected_edges = [
         RELATION: DIRECTLY_INCREASES,
         EVIDENCE: 'NMDA-mediated influx of calcium led to activated of the calcium-dependent phosphatase calcineurin and the subsequent dephosphorylation and activation of the protein-tyrosine phosphatase STEP',
         CITATION: {
-            CITATION_DB: CITATION_TYPE_PUBMED,
-            CITATION_IDENTIFIER: '12483215'
+            NAMESPACE: CITATION_TYPE_PUBMED,
+            IDENTIFIER: '12483215'
         },
-        OBJECT: {MODIFIER: ACTIVITY, EFFECT: {NAMESPACE: BEL_DEFAULT_NAMESPACE, NAME: 'phos'}},
+        TARGET_MODIFIER: {MODIFIER: ACTIVITY, EFFECT: activity_mapping['phos']},
         ANNOTATIONS: {
             'Species': {'10116': True},
             'Cell': {'neuron': True}
@@ -93,8 +97,8 @@ jgif_expected_edges = [
         RELATION: DECREASES,
         EVIDENCE: "\"These data suggested that FOXO3 downregulates the accumulation of CD8 T cells in tissue specific fashion during an acute LCMV [lymphocytic choriomeningitis virus] infection.\" (p. 3)",
         CITATION: {
-            CITATION_DB: CITATION_TYPE_OTHER,
-            CITATION_IDENTIFIER: "22359505"
+            NAMESPACE: CITATION_TYPE_OTHER,
+            IDENTIFIER: "22359505"
         },
         ANNOTATIONS: {
             'Species': {'10090': True},
@@ -105,10 +109,10 @@ jgif_expected_edges = [
         RELATION: DIRECTLY_INCREASES,
         EVIDENCE: "IL-15 utilizes ... the common cytokine receptor γ-chain (CD132) for signal transduction in lymphocytes",
         CITATION: {
-            CITATION_DB: CITATION_TYPE_OTHER,
-            CITATION_IDENTIFIER: "20335267"
+            NAMESPACE: CITATION_TYPE_OTHER,
+            IDENTIFIER: "20335267"
         },
-        OBJECT: {MODIFIER: ACTIVITY, EFFECT: {NAMESPACE: BEL_DEFAULT_NAMESPACE, NAME: 'cat'}},
+        TARGET_MODIFIER: {MODIFIER: ACTIVITY, EFFECT: activity_mapping['cat']},
         ANNOTATIONS: {
             'Tissue': {'lung': True}
         }
diff --git a/tests/test_io/test_pynpa.py b/tests/test_io/test_pynpa.py
index 1d16ee25..67da4334 100644
--- a/tests/test_io/test_pynpa.py
+++ b/tests/test_io/test_pynpa.py
@@ -10,6 +10,7 @@ from pybel import BELGraph
 from pybel.dsl import ComplexAbundance, Gene, Protein, Rna
 from pybel.io.pynpa import to_npa_dfs, to_npa_layers
 from pybel.struct.getters import get_tf_pairs
+from pybel.testing.utils import n
 
 g1 = Gene('hgnc', '1')
 r1 = Rna('hgnc', '1')
@@ -27,8 +28,8 @@ class TestPyNPA(unittest.TestCase):
     def setUp(self) -> None:
         """Set up a small test graph."""
         self.graph = BELGraph()
-        self.graph.add_increases(ComplexAbundance([p1, g2]), r2, citation='', evidence='')
-        self.graph.add_increases(p2, p3, citation='', evidence='')
+        self.graph.add_increases(ComplexAbundance([p1, g2]), r2, citation=n(), evidence=n())
+        self.graph.add_increases(p2, p3, citation=n(), evidence=n())
 
     def test_get_tf_pairs(self):
         """Test getting transcription factor-target pairs."""
diff --git a/tests/test_io/test_spia.py b/tests/test_io/test_spia.py
index 7e096c0f..e2aa553b 100644
--- a/tests/test_io/test_spia.py
+++ b/tests/test_io/test_spia.py
@@ -249,7 +249,7 @@ class TestSpia(unittest.TestCase):
             citation=citation,
             annotations={'Species': '9606', 'Confidence': 'High'},
             evidence=evidence_1,
-            object_modifier=activity()
+            target_modifier=activity(),
         )
 
         spia_dfs = to_spia_dfs(self.sialic_acid_graph)
@@ -266,7 +266,7 @@ class TestSpia(unittest.TestCase):
             citation=citation,
             annotations={'Species': '9606', 'Confidence': 'High'},
             evidence=evidence_1,
-            object_modifier=activity()
+            target_modifier=activity(),
         )
 
         spia_dfs = to_spia_dfs(self.sialic_acid_graph)
diff --git a/tests/test_io/test_triples.py b/tests/test_io/test_triples.py
index ebadacc9..bb755ea1 100644
--- a/tests/test_io/test_triples.py
+++ b/tests/test_io/test_triples.py
@@ -8,7 +8,7 @@ from typing import Tuple, Type
 from pybel import BELGraph
 from pybel.constants import (
     ASSOCIATION, DECREASES, DIRECTLY_DECREASES, DIRECTLY_INCREASES, EQUIVALENT_TO, INCREASES, IS_A,
-    NEGATIVE_CORRELATION, OBJECT, PART_OF, POSITIVE_CORRELATION, REGULATES, RELATION,
+    NEGATIVE_CORRELATION, TARGET_MODIFIER, PART_OF, POSITIVE_CORRELATION, REGULATES, RELATION,
 )
 from pybel.dsl import (
     Abundance, BaseEntity, BiologicalProcess, ComplexAbundance, MicroRna, NamedComplexAbundance, Pathology,
@@ -32,7 +32,7 @@ def _rel(x):
 
 
 def _rela(x, y=None):
-    return {RELATION: x, OBJECT: activity(y)}
+    return {RELATION: x, TARGET_MODIFIER: activity(y)}
 
 
 def _assoc(y):
diff --git a/tests/test_io/test_umbrella_nodelink.py b/tests/test_io/test_umbrella_nodelink.py
index 573b26ed..195bbeca 100644
--- a/tests/test_io/test_umbrella_nodelink.py
+++ b/tests/test_io/test_umbrella_nodelink.py
@@ -22,9 +22,17 @@ class TestUmbrellaNodeLinkExporter(unittest.TestCase):
         self.assertEqual(32, len(custom_json_dict['nodes']))
 
         # 3 new nodes are created:
-        self.assertIn("act(p(hgnc:MAPK1), ma(kin))", custom_json_dict['nodes'])
-        self.assertIn("act(p(hgnc:PTK2, pmod(Ph, Tyr, 925)), ma(kin))", custom_json_dict['nodes'])
-        self.assertIn('act(p(fus(hgnc:BCR, "?", hgnc:ABL1, "?")), ma(kin))', custom_json_dict['nodes'])
+        self.assertIn(
+            'act(p(hgnc:MAPK1), ma(go:0016301 ! "kinase activity"))',
+            custom_json_dict['nodes'],
+        )
+        self.assertIn(
+            'act(p(hgnc:PTK2, pmod(Ph, Tyr, 925)), ma(go:0016301 ! "kinase activity"))',
+            custom_json_dict['nodes'])
+        self.assertIn(
+            'act(p(fus(hgnc:BCR, "?", hgnc:ABL1, "?")), ma(go:0016301 ! "kinase activity"))',
+            custom_json_dict['nodes'],
+        )
 
     def test_exporter_edges(self):
         """Test no new edges created."""
diff --git a/tests/test_manager/test_citation_utils.py b/tests/test_manager/test_citation_utils.py
index e17494b8..bb667067 100644
--- a/tests/test_manager/test_citation_utils.py
+++ b/tests/test_manager/test_citation_utils.py
@@ -84,7 +84,7 @@ class TestCitations(TemporaryCacheMixin):
 
         get_citations_by_pmids(manager=self.manager, pmids=pmids)
 
-        citation = self.manager.get_or_create_citation(db=CITATION_TYPE_PUBMED, db_id='25818332')
+        citation = self.manager.get_or_create_citation(namespace=CITATION_TYPE_PUBMED, identifier='25818332')
         self.assertIsNotNone(citation)
 
     def test_enrich_list_grouped(self):
@@ -101,7 +101,7 @@ class TestCitations(TemporaryCacheMixin):
         self.assertIsNotNone(citation)
 
     def test_enrich_overwrite(self):
-        citation = self.manager.get_or_create_citation(db=CITATION_TYPE_PUBMED, db_id=self.pmid)
+        citation = self.manager.get_or_create_citation(namespace=CITATION_TYPE_PUBMED, identifier=self.pmid)
         self.manager.session.commit()
         self.assertIsNone(citation.date)
         self.assertIsNone(citation.title)
diff --git a/tests/test_manager/test_manager_graph.py b/tests/test_manager/test_manager_graph.py
index 78b27bbe..a1427cf6 100644
--- a/tests/test_manager/test_manager_graph.py
+++ b/tests/test_manager/test_manager_graph.py
@@ -9,8 +9,8 @@ from random import randint
 
 from pybel import BELGraph, from_bel_script, from_database, to_database
 from pybel.constants import (
-    ABUNDANCE, BIOPROCESS, CITATION_DB, CITATION_IDENTIFIER, CITATION_TYPE_PUBMED, DECREASES,
-    HAS_PRODUCT, HAS_REACTANT, INCREASES, METADATA_NAME, METADATA_VERSION, MIRNA, PART_OF, PATHOLOGY, PROTEIN, RELATION,
+    ABUNDANCE, BIOPROCESS, CITATION_TYPE_PUBMED, DECREASES, HAS_PRODUCT, HAS_REACTANT, IDENTIFIER, INCREASES,
+    METADATA_NAME, METADATA_VERSION, MIRNA, NAMESPACE, PART_OF, PATHOLOGY, PROTEIN, RELATION,
 )
 from pybel.dsl import (
     BaseEntity, ComplexAbundance, CompositeAbundance, EnumeratedFusionRange, Fragment, Gene, GeneFusion,
@@ -306,7 +306,7 @@ class TestQuery(TemporaryCacheMixin):
         self.assertFalse(rv[0].is_enriched)
 
     def test_query_citaiton_by_reference(self):
-        rv = self.manager.query_citations(db=CITATION_TYPE_PUBMED, db_id=test_citation_dict[CITATION_IDENTIFIER])
+        rv = self.manager.query_citations(db=CITATION_TYPE_PUBMED, db_id=test_citation_dict[IDENTIFIER])
         self.assertEqual(1, len(rv))
         self.assertTrue(rv[0].is_pubmed)
         self.assertFalse(rv[0].is_enriched)
@@ -340,8 +340,8 @@ class TestEnsure(TemporaryCacheMixin):
     def test_get_or_create_citation(self):
         reference = str(randint(1, 1000000))
         citation_dict = {
-            CITATION_DB: CITATION_TYPE_PUBMED,
-            CITATION_IDENTIFIER: reference,
+            NAMESPACE: CITATION_TYPE_PUBMED,
+            IDENTIFIER: reference,
         }
 
         citation = self.manager.get_or_create_citation(**citation_dict)
@@ -359,14 +359,14 @@ class TestEnsure(TemporaryCacheMixin):
         self.assertEqual(citation_dict, citation_reloaded_from_dict.to_json())
 
         citation_reloaded_from_reference = self.manager.get_citation_by_reference(
-            citation_dict[CITATION_DB], citation_dict[CITATION_IDENTIFIER],
+            citation_dict[NAMESPACE], citation_dict[IDENTIFIER],
         )
         self.assertIsNotNone(citation_reloaded_from_reference)
         self.assertEqual(citation_dict, citation_reloaded_from_reference.to_json())
 
     def test_get_or_create_evidence(self):
         citation_db, citation_ref = CITATION_TYPE_PUBMED, str(randint(1, 1000000))
-        basic_citation = self.manager.get_or_create_citation(db=citation_db, db_id=citation_ref)
+        basic_citation = self.manager.get_or_create_citation(namespace=citation_db, identifier=citation_ref)
         utf8_test_evidence = u"Yes, all the information is true! This contains a unicode alpha: α"
 
         evidence = self.manager.get_or_create_evidence(basic_citation, utf8_test_evidence)
@@ -795,7 +795,7 @@ class TestReconstituteEdges(TemporaryCacheMixin):
             evidence='In endothelial cells, ET-1 secretion is detectable under basal conditions, whereas thrombin '
                      'induces its secretion.',
             citation='10473669',
-            subject_modifier=secretion()
+            source_modifier=secretion(),
         )
 
         make_dummy_namespaces(self.manager, self.graph)
@@ -804,8 +804,8 @@ class TestReconstituteEdges(TemporaryCacheMixin):
         self.assertEqual(2, network.nodes.count(), msg='Missing one or both of the nodes.')
         self.assertEqual(1, network.edges.count(), msg='Missing the edge')
 
-        #edge = network.edges.first()
-        #self.assertEqual(2, edge.properties.count())
+        # edge = network.edges.first()
+        # self.assertEqual(2, edge.properties.count())
 
     @mock_bel_resources
     def test_subject_translocation_custom_to_loc(self, mock):
@@ -814,10 +814,10 @@ class TestReconstituteEdges(TemporaryCacheMixin):
             Protein(name='EDN1', namespace='HGNC'),
             evidence='In endothelial cells, ET-1 secretion is detectable under basal conditions, whereas thrombin induces its secretion.',
             citation='10473669',
-            subject_modifier=translocation(
+            source_modifier=translocation(
                 from_loc=Entity(namespace='TEST', name='A'),
                 to_loc=Entity(namespace='GO', name='extracellular space'),
-            )
+            ),
         )
 
         make_dummy_namespaces(self.manager, self.graph)
@@ -839,7 +839,7 @@ class TestReconstituteEdges(TemporaryCacheMixin):
             Protein(name=p2_name, namespace='HGNC'),
             evidence=n(),
             citation=n(),
-            subject_modifier=activity('kin')
+            source_modifier=activity('kin'),
         )
 
         make_dummy_namespaces(self.manager, self.graph)
@@ -866,7 +866,7 @@ class TestReconstituteEdges(TemporaryCacheMixin):
             Protein(name=p2_name, namespace='HGNC'),
             evidence=n(),
             citation=n(),
-            subject_modifier=activity(name=dummy_activity_name, namespace=dummy_activity_namespace)
+            source_modifier=activity(name=dummy_activity_name, namespace=dummy_activity_namespace),
         )
 
         make_dummy_namespaces(self.manager, self.graph)
@@ -891,7 +891,7 @@ class TestReconstituteEdges(TemporaryCacheMixin):
             Protein(name=p2_name, namespace='HGNC'),
             evidence=n(),
             citation=n(),
-            object_modifier=activity('kin')
+            target_modifier=activity('kin')
         )
 
         make_dummy_namespaces(self.manager, self.graph)
@@ -918,7 +918,7 @@ class TestReconstituteEdges(TemporaryCacheMixin):
             Protein(name=p2_name, namespace='HGNC'),
             evidence=n(),
             citation=n(),
-            object_modifier=activity(name=dummy_activity_name, namespace=dummy_activity_namespace)
+            target_modifier=activity(name=dummy_activity_name, namespace=dummy_activity_namespace)
         )
 
         make_dummy_namespaces(self.manager, self.graph)
@@ -939,7 +939,7 @@ class TestReconstituteEdges(TemporaryCacheMixin):
             Protein(name='YFG2', namespace='HGNC'),
             evidence=n(),
             citation=n(),
-            subject_modifier=degradation(),
+            source_modifier=degradation(),
         )
         make_dummy_namespaces(self.manager, self.graph)
 
@@ -957,7 +957,7 @@ class TestReconstituteEdges(TemporaryCacheMixin):
             Protein(name='YFG2', namespace='HGNC'),
             evidence=n(),
             citation=n(),
-            object_modifier=degradation(),
+            target_modifier=degradation(),
         )
         make_dummy_namespaces(self.manager, self.graph)
 
@@ -975,7 +975,7 @@ class TestReconstituteEdges(TemporaryCacheMixin):
             Protein(name='YFG2', namespace='HGNC'),
             evidence=n(),
             citation=n(),
-            subject_modifier=location(Entity(namespace='GO', name='nucleus', identifier='GO:0005634'))
+            source_modifier=location(Entity(namespace='GO', name='nucleus', identifier='0005634')),
         )
         make_dummy_namespaces(self.manager, self.graph)
 
@@ -999,10 +999,10 @@ class TestReconstituteEdges(TemporaryCacheMixin):
          form of Cdc42Hs and weakly activates the JNK family of MAP kinases. PAK4 is a mediator of filopodia
          formation and may play a role in the reorganization of the actin cytoskeleton. Multiple alternatively
          spliced transcript variants encoding distinct isoforms have been found for this gene.""",
-            citation={CITATION_DB: "Online Resource", CITATION_IDENTIFIER: "PAK4 Hs ENTREZ Gene Summary"},
+            citation={NAMESPACE: "Online Resource", IDENTIFIER: "PAK4 Hs ENTREZ Gene Summary"},
             annotations={'Species': '9606'},
-            subject_modifier=activity('gtp'),
-            object_modifier=activity('kin'),
+            source_modifier=activity('gtp'),
+            target_modifier=activity('kin'),
         )
 
         make_dummy_namespaces(self.manager, self.graph)
@@ -1026,8 +1026,8 @@ class TestReconstituteEdges(TemporaryCacheMixin):
         MEF2A results in the repression of MEF2A transcriptional activation, a function that requires the
         deacetylase domain of HDAC4.""",
             annotations={'Species': '9606'},
-            subject_modifier=activity('cat', location=Entity(namespace='GO', name='nucleus')),
-            object_modifier=activity('tscript', location=Entity(namespace='GO', name='nucleus'))
+            source_modifier=activity('cat', location=Entity(namespace='GO', name='nucleus')),
+            target_modifier=activity('tscript', location=Entity(namespace='GO', name='nucleus'))
         )
 
         make_dummy_namespaces(self.manager, self.graph)
diff --git a/tests/test_manager/test_manager_model.py b/tests/test_manager/test_manager_model.py
index 07da491a..a0696bc7 100644
--- a/tests/test_manager/test_manager_model.py
+++ b/tests/test_manager/test_manager_model.py
@@ -7,13 +7,14 @@ import json
 import unittest
 
 from pybel.constants import (
-    CITATION_IDENTIFIER, CITATION_DB_NAME, CITATION_DB, CITATION_TYPE_PUBMED, IDENTIFIER,
+    CITATION_TYPE_PUBMED, IDENTIFIER,
     METADATA_AUTHORS, METADATA_CONTACT, METADATA_COPYRIGHT, METADATA_DESCRIPTION, METADATA_DISCLAIMER,
     METADATA_LICENSES, METADATA_NAME, METADATA_VERSION, NAME, NAMESPACE, NAMESPACE_DOMAIN_OTHER,
 )
+from pybel.language import citation_dict
 from pybel.manager.models import Citation, Namespace, NamespaceEntry, Network
 from pybel.testing.utils import n
-from pybel.utils import citation_dict
+
 
 class TestNetwork(unittest.TestCase):
 
@@ -152,8 +153,8 @@ class TestModels(unittest.TestCase):
             db_id=db_id,
         )
 
-        expected = citation_dict(db=CITATION_TYPE_PUBMED, db_id=db_id)
+        expected = citation_dict(namespace=CITATION_TYPE_PUBMED, identifier=db_id)
         self.assertEqual(expected, model.to_json())
 
-        expected[CITATION_DB_NAME] = model.title = n()
+        expected[NAME] = model.title = n()
         self.assertEqual(expected, model.to_json())
diff --git a/tests/test_parse/test_parse_bel.py b/tests/test_parse/test_parse_bel.py
index 292714b0..bc8db812 100644
--- a/tests/test_parse/test_parse_bel.py
+++ b/tests/test_parse/test_parse_bel.py
@@ -8,11 +8,13 @@ import unittest
 
 from pybel import BELGraph
 from pybel.constants import (
-    ABUNDANCE, ACTIVITY, BEL_DEFAULT_NAMESPACE, BIOPROCESS, COMPLEX, COMPOSITE, CONCEPT, DEGRADATION,
+    ABUNDANCE, ACTIVITY, BIOPROCESS, CELL_SECRETION, CELL_SURFACE_EXPRESSION, 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, POPULATION, PRODUCTS, PROTEIN, RANGE_3P, RANGE_5P,
-    REACTANTS, REACTION, RELATION, RNA, SUBJECT, TARGET, TO_LOC, TRANSLOCATION, VARIANTS,
+    NAMESPACE, SOURCE, TARGET, TARGET_MODIFIER, PARTNER_3P, PARTNER_5P, PART_OF, PATHOLOGY, POPULATION, PRODUCTS,
+    PROTEIN,
+    RANGE_3P, RANGE_5P,
+    REACTANTS, REACTION, RELATION, RNA, SOURCE_MODIFIER, TO_LOC, TRANSLOCATION, VARIANTS,
 )
 from pybel.dsl import (
     Fragment, Population, abundance, bioprocess, cell_surface_expression, complex_abundance, composite_abundance,
@@ -20,9 +22,9 @@ from pybel.dsl import (
     protein_fusion, reaction, rna, rna_fusion, secretion, translocation,
 )
 from pybel.dsl.namespaces import hgnc
-from pybel.language import Entity
+from pybel.exceptions import MalformedTranslocationWarning
+from pybel.language import Entity, activity_mapping
 from pybel.parser import BELParser
-from pybel.parser.exc import MalformedTranslocationWarning
 from pybel.parser.parse_bel import modifier_po_to_dict
 from tests.constants import TestTokenParserBase, assert_has_edge, assert_has_node, update_provenance
 
@@ -1540,7 +1542,7 @@ class TestActivity(TestTokenParserBase):
         statement = 'act(p(HGNC:AKT1))'
         result = self.parser.activity.parseString(statement)
 
-        expected_result = [ACTIVITY, [PROTEIN, ['HGNC', 'AKT1']]]
+        expected_result = [ACTIVITY, PROTEIN, ['HGNC', 'AKT1']]
         self.assertEqual(expected_result, result.asList())
 
         mod = modifier_po_to_dict(result)
@@ -1556,16 +1558,11 @@ class TestActivity(TestTokenParserBase):
 
         expected_dict = {
             MODIFIER: ACTIVITY,
-            EFFECT: {
-                NAME: 'kin',
-                NAMESPACE: BEL_DEFAULT_NAMESPACE,
-            },
-            TARGET: {
-                FUNCTION: PROTEIN,
-                CONCEPT: {
-                    NAMESPACE: 'HGNC',
-                    NAME: 'AKT1',
-                },
+            EFFECT: activity_mapping['kin'],
+            FUNCTION: PROTEIN,
+            CONCEPT: {
+                NAMESPACE: 'HGNC',
+                NAME: 'AKT1',
             },
         }
         self.assertEqual(expected_dict, result.asDict())
@@ -1573,10 +1570,7 @@ class TestActivity(TestTokenParserBase):
         mod = modifier_po_to_dict(result)
         expected_mod = {
             MODIFIER: ACTIVITY,
-            EFFECT: {
-                NAME: 'kin',
-                NAMESPACE: BEL_DEFAULT_NAMESPACE,
-            },
+            EFFECT: activity_mapping['kin'],
         }
         self.assertEqual(expected_mod, mod)
 
@@ -1587,16 +1581,11 @@ class TestActivity(TestTokenParserBase):
 
         expected_dict = {
             MODIFIER: ACTIVITY,
-            EFFECT: {
-                NAME: 'cat',
-                NAMESPACE: BEL_DEFAULT_NAMESPACE
-            },
-            TARGET: {
-                FUNCTION: PROTEIN,
-                CONCEPT: {
-                    NAMESPACE: 'HGNC',
-                    NAME: 'AKT1',
-                },
+            EFFECT: activity_mapping['cat'],
+            FUNCTION: PROTEIN,
+            CONCEPT: {
+                NAMESPACE: 'HGNC',
+                NAME: 'AKT1',
             },
         }
         self.assertEqual(expected_dict, result.asDict())
@@ -1604,10 +1593,7 @@ class TestActivity(TestTokenParserBase):
         mod = modifier_po_to_dict(result)
         expected_mod = {
             MODIFIER: ACTIVITY,
-            EFFECT: {
-                NAME: 'cat',
-                NAMESPACE: BEL_DEFAULT_NAMESPACE
-            },
+            EFFECT: activity_mapping['cat'],
         }
         self.assertEqual(expected_mod, mod)
 
@@ -1620,14 +1606,12 @@ class TestActivity(TestTokenParserBase):
             MODIFIER: ACTIVITY,
             EFFECT: {
                 NAMESPACE: 'GO',
-                NAME: 'catalytic activity'
+                NAME: 'catalytic activity',
             },
-            TARGET: {
-                FUNCTION: PROTEIN,
-                CONCEPT: {
-                    NAMESPACE: 'HGNC',
-                    NAME: 'AKT1',
-                },
+            FUNCTION: PROTEIN,
+            CONCEPT: {
+                NAMESPACE: 'HGNC',
+                NAME: 'AKT1',
             },
         }
         self.assertEqual(expected_dict, result.asDict())
@@ -1649,16 +1633,11 @@ class TestActivity(TestTokenParserBase):
 
         expected_dict = {
             MODIFIER: ACTIVITY,
-            EFFECT: {
-                NAME: 'kin',
-                NAMESPACE: BEL_DEFAULT_NAMESPACE
-            },
-            TARGET: {
-                FUNCTION: PROTEIN,
-                CONCEPT: {
-                    NAMESPACE: 'HGNC',
-                    NAME: 'AKT1',
-                },
+            EFFECT: activity_mapping['kin'],
+            FUNCTION: PROTEIN,
+            CONCEPT: {
+                NAMESPACE: 'HGNC',
+                NAME: 'AKT1',
             },
         }
         self.assertEqual(expected_dict, result.asDict())
@@ -1666,10 +1645,7 @@ class TestActivity(TestTokenParserBase):
         mod = modifier_po_to_dict(result)
         expected_mod = {
             MODIFIER: ACTIVITY,
-            EFFECT: {
-                NAME: 'kin',
-                NAMESPACE: BEL_DEFAULT_NAMESPACE
-            },
+            EFFECT: activity_mapping['kin'],
         }
         self.assertEqual(expected_mod, mod)
 
@@ -1723,12 +1699,10 @@ class TestTranslocationPermissive(unittest.TestCase):
 
         expected_dict = {
             MODIFIER: TRANSLOCATION,
-            TARGET: {
-                FUNCTION: PROTEIN,
-                CONCEPT: {
-                    NAMESPACE: 'HGNC',
-                    NAME: 'EGFR',
-                },
+            FUNCTION: PROTEIN,
+            CONCEPT: {
+                NAMESPACE: 'HGNC',
+                NAME: 'EGFR',
             },
         }
 
@@ -1754,7 +1728,7 @@ class TestTranslocationPermissive(unittest.TestCase):
         result = self.parser.relation.parseString(statement)
 
         expected_dict = {
-            SUBJECT: {
+            SOURCE: {
                 FUNCTION: ABUNDANCE,
                 CONCEPT: {
                     NAMESPACE: 'CHEBI',
@@ -1762,13 +1736,11 @@ class TestTranslocationPermissive(unittest.TestCase):
                 },
             },
             RELATION: DIRECTLY_INCREASES,
-            OBJECT: {
-                TARGET: {
-                    FUNCTION: ABUNDANCE,
-                    CONCEPT: {
-                        NAMESPACE: 'CHEBI',
-                        NAME: 'calcium(2+)',
-                    },
+            TARGET: {
+                FUNCTION: ABUNDANCE,
+                CONCEPT: {
+                    NAMESPACE: 'CHEBI',
+                    NAME: 'calcium(2+)',
                 },
                 MODIFIER: TRANSLOCATION,
             }
@@ -1783,7 +1755,7 @@ class TestTranslocationPermissive(unittest.TestCase):
 
         expected_annotations = {
             RELATION: DIRECTLY_INCREASES,
-            OBJECT: {
+            TARGET_MODIFIER: {
                 MODIFIER: TRANSLOCATION,
             }
         }
@@ -1801,17 +1773,15 @@ class TestTransformation(TestTokenParserBase):
         statement = 'deg(p(HGNC:AKT1))'
         result = self.parser.transformation.parseString(statement)
 
-        expected_result = [DEGRADATION, [PROTEIN, ['HGNC', 'AKT1']]]
+        expected_result = [DEGRADATION, PROTEIN, ['HGNC', 'AKT1']]
         self.assertEqual(expected_result, result.asList())
 
         expected_dict = {
             MODIFIER: DEGRADATION,
-            TARGET: {
-                FUNCTION: PROTEIN,
-                CONCEPT: {
-                    NAMESPACE: 'HGNC',
-                    NAME: 'AKT1',
-                },
+            FUNCTION: PROTEIN,
+            CONCEPT: {
+                NAMESPACE: 'HGNC',
+                NAME: 'AKT1',
             },
         }
         self.assertEqual(expected_dict, result.asDict())
@@ -1829,12 +1799,10 @@ class TestTransformation(TestTokenParserBase):
 
         expected_dict = {
             MODIFIER: DEGRADATION,
-            TARGET: {
-                FUNCTION: PROTEIN,
-                CONCEPT: {
-                    NAMESPACE: 'HGNC',
-                    NAME: 'EGFR',
-                },
+            FUNCTION: PROTEIN,
+            CONCEPT: {
+                NAMESPACE: 'HGNC',
+                NAME: 'EGFR',
             },
         }
         self.assertEqual(expected_dict, result.asDict())
@@ -1855,12 +1823,10 @@ class TestTransformation(TestTokenParserBase):
 
         expected_dict = {
             MODIFIER: TRANSLOCATION,
-            TARGET: {
-                FUNCTION: PROTEIN,
-                CONCEPT: {
-                    NAMESPACE: 'HGNC',
-                    NAME: 'EGFR',
-                },
+            FUNCTION: PROTEIN,
+            CONCEPT: {
+                NAMESPACE: 'HGNC',
+                NAME: 'EGFR',
             },
             EFFECT: {
                 FROM_LOC: {NAMESPACE: 'GO', NAME: 'cell surface', },
@@ -1888,12 +1854,10 @@ class TestTransformation(TestTokenParserBase):
 
         expected_dict = {
             MODIFIER: TRANSLOCATION,
-            TARGET: {
-                FUNCTION: PROTEIN,
-                CONCEPT: {
-                    NAMESPACE: 'HGNC',
-                    NAME: 'EGFR',
-                },
+            FUNCTION: PROTEIN,
+            CONCEPT: {
+                NAMESPACE: 'HGNC',
+                NAME: 'EGFR',
             },
             EFFECT: {
                 FROM_LOC: {NAMESPACE: 'GO', NAME: 'cell surface'},
@@ -1926,7 +1890,7 @@ class TestTransformation(TestTokenParserBase):
         statement = 'sec(p(HGNC:EGFR))'
         result = self.parser.transformation.parseString(statement)
 
-        expected_result = ['CellSecretion', [PROTEIN, ['HGNC', 'EGFR']]]
+        expected_result = [CELL_SECRETION, PROTEIN, ['HGNC', 'EGFR']]
         self.assertEqual(expected_result, result.asList())
 
         mod = modifier_po_to_dict(result)
@@ -1941,7 +1905,7 @@ class TestTransformation(TestTokenParserBase):
         statement = 'surf(p(HGNC:EGFR))'
         result = self.parser.transformation.parseString(statement)
 
-        expected_result = ['CellSurfaceExpression', [PROTEIN, ['HGNC', 'EGFR']]]
+        expected_result = [CELL_SURFACE_EXPRESSION, PROTEIN, ['HGNC', 'EGFR']]
         self.assertEqual(expected_result, result.asList())
 
         expected_mod = cell_surface_expression()
diff --git a/tests/test_parse/test_parse_bel_relations.py b/tests/test_parse/test_parse_bel_relations.py
index dbc83dbf..3ddbd9b5 100644
--- a/tests/test_parse/test_parse_bel_relations.py
+++ b/tests/test_parse/test_parse_bel_relations.py
@@ -10,21 +10,22 @@ from pyparsing import ParseException
 from pybel import BELGraph
 from pybel.canonicalize import edge_to_bel
 from pybel.constants import (
-    ABUNDANCE, ACTIVITY, ANNOTATIONS, BEL_DEFAULT_NAMESPACE, BIOPROCESS, CAUSES_NO_CHANGE, CITATION, COMPLEX,
-    COMPOSITE, CONCEPT, CORRELATION, DECREASES, DIRECTLY_DECREASES, DIRECTLY_INCREASES, EFFECT, EQUIVALENT_TO, EVIDENCE,
-    FROM_LOC, FUNCTION, GENE, GMOD, HAS_PRODUCT, HAS_REACTANT, HAS_VARIANT, HGVS, INCREASES, IS_A, KIND, LOCATION,
-    MEMBERS, MODIFIER, NAME, NAMESPACE, NEGATIVE_CORRELATION, NO_CORRELATION, OBJECT, ORTHOLOGOUS, PART_OF, PATHOLOGY,
+    ABUNDANCE, ACTIVITY, ANNOTATIONS, BEL_DEFAULT_NAMESPACE, BIOPROCESS, CAUSES_NO_CHANGE, CITATION, COMPLEX, COMPOSITE,
+    CONCEPT, CORRELATION, DECREASES, DIRECTLY_DECREASES, DIRECTLY_INCREASES, EFFECT, EQUIVALENT_TO, EVIDENCE, FROM_LOC,
+    FUNCTION, GENE, GMOD, HAS_PRODUCT, HAS_REACTANT, HAS_VARIANT, HGVS, INCREASES, IS_A, KIND, LOCATION, MEMBERS,
+    MODIFIER, NAME, NAMESPACE, NEGATIVE_CORRELATION, NO_CORRELATION, ORTHOLOGOUS, PART_OF, PATHOLOGY,
     POSITIVE_CORRELATION, PRODUCTS, PROTEIN, RATE_LIMITING_STEP_OF, REACTANTS, REACTION, REGULATES, RELATION, RNA,
-    SUBJECT, SUBPROCESS_OF, TARGET, TO_LOC, TRANSCRIBED_TO, TRANSLATED_TO, TRANSLOCATION, VARIANTS,
+    SOURCE, SOURCE_MODIFIER, SUBPROCESS_OF, TARGET, TARGET_MODIFIER, TO_LOC, TRANSCRIBED_TO, TRANSLATED_TO,
+    TRANSLOCATION, VARIANTS,
 )
 from pybel.dsl import (
     ComplexAbundance, Pathology, Protein, Rna, abundance, activity, bioprocess, complex_abundance, composite_abundance,
     gene, gmod, hgvs, named_complex_abundance, pmod, protein, reaction, rna,
 )
 from pybel.dsl.namespaces import hgnc
-from pybel.language import Entity
+from pybel.exceptions import MissingNamespaceNameWarning, NestedRelationWarning, UndefinedNamespaceWarning
+from pybel.language import Entity, activity_mapping
 from pybel.parser import BELParser
-from pybel.parser.exc import MissingNamespaceNameWarning, NestedRelationWarning, UndefinedNamespaceWarning
 from tests.constants import TestTokenParserBase, test_citation_dict, test_evidence_text
 
 logger = logging.getLogger(__name__)
@@ -139,7 +140,7 @@ class TestRelations(TestTokenParserBase):
         statement = 'a(CHEBI:"lead atom") -> g(HGNC:APP, gmod(Me))'
         result = self.parser.relation.parseString(statement)
         expected_dict = {
-            OBJECT: {
+            TARGET: {
                 FUNCTION: GENE,
                 CONCEPT: {
                     NAMESPACE: 'HGNC',
@@ -151,12 +152,12 @@ class TestRelations(TestTokenParserBase):
                         CONCEPT: {
                             NAMESPACE: BEL_DEFAULT_NAMESPACE,
                             NAME: 'Me',
-                        },
+                        }
                     },
                 ],
             },
             RELATION: INCREASES,
-            SUBJECT: {
+            SOURCE: {
                 FUNCTION: ABUNDANCE,
                 CONCEPT: {
                     NAMESPACE: 'CHEBI',
@@ -180,7 +181,7 @@ class TestRelations(TestTokenParserBase):
         result = self.parser.relation.parseString(statement)
 
         expected_dict = {
-            SUBJECT: {
+            SOURCE: {
                 FUNCTION: ABUNDANCE,
                 CONCEPT: {
                     NAMESPACE: 'CHEBI',
@@ -188,13 +189,11 @@ class TestRelations(TestTokenParserBase):
                 },
             },
             RELATION: DIRECTLY_INCREASES,
-            OBJECT: {
-                TARGET: {
-                    FUNCTION: ABUNDANCE,
-                    CONCEPT: {
-                        NAMESPACE: 'CHEBI',
-                        NAME: 'calcium(2+)',
-                    },
+            TARGET: {
+                FUNCTION: ABUNDANCE,
+                CONCEPT: {
+                    NAMESPACE: 'CHEBI',
+                    NAME: 'calcium(2+)',
                 },
                 MODIFIER: TRANSLOCATION,
                 EFFECT: {
@@ -213,7 +212,7 @@ class TestRelations(TestTokenParserBase):
 
         expected_annotations = {
             RELATION: DIRECTLY_INCREASES,
-            OBJECT: {
+            TARGET_MODIFIER: {
                 MODIFIER: TRANSLOCATION,
                 EFFECT: {
                     FROM_LOC: {NAMESPACE: 'MESH', NAME: 'Cell Membrane'},
@@ -233,23 +232,18 @@ class TestRelations(TestTokenParserBase):
         result = self.parser.relation.parseString(statement)
 
         expected_dict = {
-            SUBJECT: {
+            SOURCE: {
                 MODIFIER: ACTIVITY,
-                TARGET: {
-                    FUNCTION: PROTEIN,
-                    CONCEPT: {
-                        NAMESPACE: 'FPLX',
-                        NAME: 'CAPN',
-                    },
-                    LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}
-                },
-                EFFECT: {
-                    NAME: 'pep',
-                    NAMESPACE: BEL_DEFAULT_NAMESPACE
+                FUNCTION: PROTEIN,
+                CONCEPT: {
+                    NAMESPACE: 'FPLX',
+                    NAME: 'CAPN',
                 },
+                LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'},
+                EFFECT: activity_mapping['pep'],
             },
             RELATION: DECREASES,
-            OBJECT: {
+            TARGET: {
                 FUNCTION: REACTION,
                 REACTANTS: [
                     {
@@ -290,12 +284,9 @@ class TestRelations(TestTokenParserBase):
 
         expected_edge_attributes = {
             RELATION: DECREASES,
-            SUBJECT: {
+            SOURCE_MODIFIER: {
                 MODIFIER: ACTIVITY,
-                EFFECT: {
-                    NAME: 'pep',
-                    NAMESPACE: BEL_DEFAULT_NAMESPACE,
-                },
+                EFFECT: activity_mapping['pep'],
                 LOCATION: {
                     NAMESPACE: 'GO',
                     NAME: 'intracellular',
@@ -303,8 +294,10 @@ class TestRelations(TestTokenParserBase):
             }
         }
 
-        self.assertEqual(expected_edge_attributes[SUBJECT],
-                         activity(name='pep', location=Entity(name='intracellular', namespace='GO')))
+        self.assertEqual(
+            expected_edge_attributes[SOURCE_MODIFIER],
+            activity(name='pep', location=Entity(name='intracellular', namespace='GO')),
+        )
 
         self.assert_has_edge(sub, obj, **expected_edge_attributes)
 
@@ -316,7 +309,7 @@ class TestRelations(TestTokenParserBase):
         result = self.parser.relation.parseString(statement)
 
         expected_dict = {
-            SUBJECT: {
+            SOURCE: {
                 FUNCTION: PROTEIN,
                 CONCEPT: {
                     NAMESPACE: 'HGNC',
@@ -325,7 +318,7 @@ class TestRelations(TestTokenParserBase):
                 LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}
             },
             RELATION: DIRECTLY_DECREASES,
-            OBJECT: {
+            TARGET: {
                 FUNCTION: ABUNDANCE,
                 CONCEPT: {
                     NAMESPACE: 'CHEBI',
@@ -342,7 +335,7 @@ class TestRelations(TestTokenParserBase):
         self.assert_has_node(obj)
 
         expected_attrs = {
-            SUBJECT: {
+            SOURCE_MODIFIER: {
                 LOCATION: {NAMESPACE: 'GO', NAME: 'intracellular'}
             },
             RELATION: DIRECTLY_DECREASES,
@@ -365,7 +358,7 @@ class TestRelations(TestTokenParserBase):
         result = self.parser.relation.parseString(statement)
 
         expected_dict = {
-            SUBJECT: {
+            SOURCE: {
                 FUNCTION: GENE,
                 CONCEPT: {
                     NAMESPACE: 'HGNC',
@@ -377,7 +370,7 @@ class TestRelations(TestTokenParserBase):
                 }
             },
             RELATION: DIRECTLY_DECREASES,
-            OBJECT: {
+            TARGET: {
                 FUNCTION: ABUNDANCE,
                 CONCEPT: {
                     NAMESPACE: 'CHEBI',
@@ -394,7 +387,7 @@ class TestRelations(TestTokenParserBase):
         self.assert_has_node(obj)
 
         expected_attrs = {
-            SUBJECT: {
+            SOURCE_MODIFIER: {
                 LOCATION: {
                     NAMESPACE: 'GO',
                     NAME: 'intracellular'
@@ -416,22 +409,17 @@ class TestRelations(TestTokenParserBase):
         result = self.parser.relation.parseString(statement)
 
         expected_dict = {
-            SUBJECT: {
+            SOURCE: {
                 MODIFIER: ACTIVITY,
-                TARGET: {
-                    FUNCTION: PROTEIN,
-                    CONCEPT: {
-                        NAMESPACE: 'HGNC',
-                        NAME: 'HMGCR',
-                    },
-                },
-                EFFECT: {
-                    NAME: 'cat',
-                    NAMESPACE: BEL_DEFAULT_NAMESPACE
+                FUNCTION: PROTEIN,
+                CONCEPT: {
+                    NAMESPACE: 'HGNC',
+                    NAME: 'HMGCR',
                 },
+                EFFECT: activity_mapping['cat'],
             },
             RELATION: RATE_LIMITING_STEP_OF,
-            OBJECT: {
+            TARGET: {
                 FUNCTION: BIOPROCESS,
                 CONCEPT: {
                     NAMESPACE: 'GO',
@@ -458,7 +446,7 @@ class TestRelations(TestTokenParserBase):
         result = self.parser.relation.parseString(statement)
 
         expected_dict = {
-            SUBJECT: {
+            SOURCE: {
                 FUNCTION: GENE,
                 CONCEPT: {
                     NAMESPACE: 'HGNC',
@@ -472,7 +460,7 @@ class TestRelations(TestTokenParserBase):
                 ],
             },
             RELATION: CAUSES_NO_CHANGE,
-            OBJECT: {
+            TARGET: {
                 FUNCTION: PATHOLOGY,
                 CONCEPT: {
                     NAMESPACE: 'MESH',
@@ -500,39 +488,29 @@ class TestRelations(TestTokenParserBase):
         result = self.parser.relation.parseString(statement)
 
         expected_dict = {
-            SUBJECT: {
+            SOURCE: {
                 MODIFIER: ACTIVITY,
-                EFFECT: {
-                    NAME: 'pep',
-                    NAMESPACE: BEL_DEFAULT_NAMESPACE
-                },
-                TARGET: {
-                    FUNCTION: COMPLEX,
-                    MEMBERS: [
-                        {
-                            FUNCTION: PROTEIN,
-                            CONCEPT: {NAMESPACE: 'HGNC', NAME: 'F3'}
-                        },
-                        {
-                            FUNCTION: PROTEIN,
-                            CONCEPT: {NAMESPACE: 'HGNC', NAME: 'F7'}
-                        },
-                    ]
-                }
+                EFFECT: activity_mapping['pep'],
+                FUNCTION: COMPLEX,
+                MEMBERS: [
+                    {
+                        FUNCTION: PROTEIN,
+                        CONCEPT: {NAMESPACE: 'HGNC', NAME: 'F3'}
+                    },
+                    {
+                        FUNCTION: PROTEIN,
+                        CONCEPT: {NAMESPACE: 'HGNC', NAME: 'F7'}
+                    },
+                ],
             },
             RELATION: REGULATES,
-            OBJECT: {
+            TARGET: {
                 MODIFIER: ACTIVITY,
-                EFFECT: {
-                    NAME: 'pep',
-                    NAMESPACE: BEL_DEFAULT_NAMESPACE
-                },
-                TARGET: {
-                    FUNCTION: PROTEIN,
-                    CONCEPT: {
-                        NAMESPACE: 'HGNC',
-                        NAME: 'F9',
-                    },
+                EFFECT: activity_mapping['pep'],
+                FUNCTION: PROTEIN,
+                CONCEPT: {
+                    NAMESPACE: 'HGNC',
+                    NAME: 'F9',
                 },
             },
         }
@@ -585,22 +563,17 @@ class TestRelations(TestTokenParserBase):
         result = self.parser.relation.parseString(statement)
 
         expected_dict = {
-            SUBJECT: {
+            SOURCE: {
                 MODIFIER: ACTIVITY,
-                EFFECT: {
-                    NAME: 'kin',
-                    NAMESPACE: BEL_DEFAULT_NAMESPACE
-                },
-                TARGET: {
-                    FUNCTION: PROTEIN,
-                    CONCEPT: {
-                        NAMESPACE: 'FPLX',
-                        NAME: 'GSK3',
-                    },
+                EFFECT: activity_mapping['kin'],
+                FUNCTION: PROTEIN,
+                CONCEPT: {
+                    NAMESPACE: 'FPLX',
+                    NAME: 'GSK3',
                 },
             },
             RELATION: NEGATIVE_CORRELATION,
-            OBJECT: {
+            TARGET: {
                 FUNCTION: PROTEIN,
                 CONCEPT: {
                     NAMESPACE: 'HGNC',
@@ -628,7 +601,7 @@ class TestRelations(TestTokenParserBase):
         result = self.parser.relation.parseString(statement)
 
         expected_dict = {
-            SUBJECT: {
+            SOURCE: {
                 FUNCTION: PROTEIN,
                 CONCEPT: {
                     NAMESPACE: 'HGNC',
@@ -637,19 +610,14 @@ class TestRelations(TestTokenParserBase):
                 VARIANTS: [pmod('Ph', position=9, code='Ser')]
             },
             RELATION: POSITIVE_CORRELATION,
-            OBJECT: {
+            TARGET: {
                 MODIFIER: ACTIVITY,
-                TARGET: {
-                    FUNCTION: PROTEIN,
-                    CONCEPT: {
-                        NAMESPACE: 'HGNC',
-                        NAME: 'GSK3B',
-                    },
+                FUNCTION: PROTEIN,
+                CONCEPT: {
+                    NAMESPACE: 'HGNC',
+                    NAME: 'GSK3B',
                 },
-                EFFECT: {
-                    NAME: 'kin',
-                    NAMESPACE: BEL_DEFAULT_NAMESPACE
-                }
+                EFFECT: activity_mapping['kin'],
             },
         }
         self.assertEqual(expected_dict, result.asDict())
@@ -712,7 +680,7 @@ class TestRelations(TestTokenParserBase):
 
         # [[RNA, ['HGNC', 'AKT1']], TRANSLATED_TO, [PROTEIN, ['HGNC', 'AKT1']]]
         expected_result = {
-            SUBJECT: {
+            SOURCE: {
                 FUNCTION: RNA,
                 CONCEPT: {
                     NAMESPACE: 'HGNC',
@@ -724,7 +692,7 @@ class TestRelations(TestTokenParserBase):
                 }
             },
             RELATION: TRANSLATED_TO,
-            OBJECT: {
+            TARGET: {
                 FUNCTION: PROTEIN,
                 CONCEPT: {
                     NAMESPACE: 'HGNC',
@@ -845,7 +813,7 @@ class TestRelations(TestTokenParserBase):
         result = self.parser.relation.parseString(statement)
 
         expected_result = {
-            SUBJECT: {
+            SOURCE: {
                 FUNCTION: GENE,
                 CONCEPT: {
                     NAMESPACE: 'dbSNP',
@@ -853,7 +821,7 @@ class TestRelations(TestTokenParserBase):
                 },
             },
             RELATION: EQUIVALENT_TO,
-            OBJECT: {
+            TARGET: {
                 FUNCTION: GENE,
                 CONCEPT: {
                     NAMESPACE: 'HGNC',
diff --git a/tests/test_parse/test_parse_control.py b/tests/test_parse/test_parse_control.py
index 499ce17f..3dd7398b 100644
--- a/tests/test_parse/test_parse_control.py
+++ b/tests/test_parse/test_parse_control.py
@@ -5,13 +5,13 @@ import re
 import unittest
 from random import randint
 
-from pybel.constants import (ANNOTATIONS, CITATION, CITATION_DB, CITATION_IDENTIFIER, CITATION_TYPE_PUBMED, EVIDENCE)
-from pybel.parser import ControlParser
-from pybel.parser.exc import (
+from pybel.constants import ANNOTATIONS, CITATION, CITATION_TYPE_PUBMED, EVIDENCE, IDENTIFIER, NAMESPACE
+from pybel.exceptions import (
     CitationTooLongException, CitationTooShortException, IllegalAnnotationValueWarning, InvalidCitationType,
     InvalidPubMedIdentifierWarning, MissingAnnotationKeyWarning, MissingAnnotationRegexWarning,
     UndefinedAnnotationWarning,
 )
+from pybel.parser import ControlParser
 from pybel.parser.parse_control import set_citation_stub
 from pybel.testing.utils import n
 from tests.constants import SET_CITATION_TEST, test_citation_dict
@@ -176,8 +176,8 @@ class TestParseControl2(TestParseControl):
 
     def test_citation_short(self):
         self.parser.parseString(SET_CITATION_TEST)
-        self.assertEqual(test_citation_dict[CITATION_IDENTIFIER], self.parser.citation_db_id)
-        self.assertEqual(test_citation_dict[CITATION_DB], self.parser.citation_db)
+        self.assertEqual(test_citation_dict[IDENTIFIER], self.parser.citation_db_id)
+        self.assertEqual(test_citation_dict[NAMESPACE], self.parser.citation_db)
 
         expected_annotations = {
             EVIDENCE: None,
@@ -200,8 +200,8 @@ class TestParseControl2(TestParseControl):
             EVIDENCE: None,
             ANNOTATIONS: {},
             CITATION: {
-                CITATION_DB: CITATION_TYPE_PUBMED,
-                CITATION_IDENTIFIER: '12928037',
+                NAMESPACE: CITATION_TYPE_PUBMED,
+                IDENTIFIER: '12928037',
             },
         }
 
@@ -218,8 +218,8 @@ class TestParseControl2(TestParseControl):
             EVIDENCE: None,
             ANNOTATIONS: {},
             CITATION: {
-                CITATION_DB: CITATION_TYPE_PUBMED,
-                CITATION_IDENTIFIER: '12928037',
+                NAMESPACE: CITATION_TYPE_PUBMED,
+                IDENTIFIER: '12928037',
             },
         }
 
diff --git a/tests/test_parse/test_parse_identifier.py b/tests/test_parse/test_parse_identifier.py
index e8553923..cd180863 100644
--- a/tests/test_parse/test_parse_identifier.py
+++ b/tests/test_parse/test_parse_identifier.py
@@ -4,8 +4,8 @@ import re
 import unittest
 
 from pybel.constants import DIRTY
+from pybel.exceptions import MissingNamespaceRegexWarning, NakedNameWarning
 from pybel.parser import ConceptParser
-from pybel.parser.exc import MissingNamespaceRegexWarning, NakedNameWarning
 
 
 class _ParserMixin(unittest.TestCase):
diff --git a/tests/test_parse/test_parse_metadata.py b/tests/test_parse/test_parse_metadata.py
index 98c76ee8..0dc0836e 100644
--- a/tests/test_parse/test_parse_metadata.py
+++ b/tests/test_parse/test_parse_metadata.py
@@ -9,10 +9,10 @@ import unittest
 from pathlib import Path
 
 from bel_resources.constants import ANNOTATION_URL_FMT, NAMESPACE_URL_FMT
-from pybel.parser import MetadataParser
-from pybel.parser.exc import (
+from pybel.exceptions import (
     InvalidMetadataException, RedefinedAnnotationError, RedefinedNamespaceError, VersionFormatWarning,
 )
+from pybel.parser import MetadataParser
 from pybel.resources import HGNC_URL
 from pybel.testing.cases import FleetingTemporaryCacheMixin
 from pybel.testing.constants import test_an_1, test_ns_1
@@ -21,7 +21,6 @@ from tests.constants import HGNC_KEYWORD, MESH_DISEASES_KEYWORD, MESH_DISEASES_U
 
 logging.getLogger("requests").setLevel(logging.WARNING)
 
-
 LOCAL_TEST_PATH = os.path.expanduser("~/dev/pybel/src/pybel/testing/resources/belns/hgnc-names.belns")
 
 
@@ -56,7 +55,6 @@ class TestParseMetadata(FleetingTemporaryCacheMixin):
     @mock_bel_resources
     def test_annotation_name_persistience_1(self, mock_get):
         """Test that an annotation defined by a URL can't be overwritten by a definition by a list."""
-
         s = ANNOTATION_URL_FMT.format(MESH_DISEASES_KEYWORD, MESH_DISEASES_URL)
         self.parser.parseString(s)
         self.assertIn(MESH_DISEASES_KEYWORD, self.parser.annotation_to_term)
diff --git a/tests/test_parse/test_user_api.py b/tests/test_parse/test_user_api.py
new file mode 100644
index 00000000..30a0de5c
--- /dev/null
+++ b/tests/test_parse/test_user_api.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+
+"""Test the user API."""
+
+import unittest
+
+import pybel
+
+
+class TestParse(unittest.TestCase):
+    """Test user API."""
+
+    def test_bel_statement(self):
+        x = pybel.parse('p(hgnc:1234) -> p(hgnc:1235)')
+
+    def test_citation(self):
+        x = pybel.parse('SET Citation = pmid:1234')
+
+    def test_control(self):
+        x = pybel.parse('SET Test = "5"')
+
+    def test_control_multiple(self):
+        x = pybel.parse('SET Test = {"1","2","3"}')
diff --git a/tests/test_struct/test_filters/test_edge_predicate_builders.py b/tests/test_struct/test_filters/test_edge_predicate_builders.py
index 2f7253e3..b5d77e7c 100644
--- a/tests/test_struct/test_filters/test_edge_predicate_builders.py
+++ b/tests/test_struct/test_filters/test_edge_predicate_builders.py
@@ -8,7 +8,7 @@ from pybel.constants import CITATION, CITATION_AUTHORS, CITATION_TYPE_PUBMED
 from pybel.struct.filters.edge_predicate_builders import (
     build_author_inclusion_filter, build_pmid_inclusion_filter,
 )
-from pybel.utils import citation_dict
+from pybel.language import citation_dict
 
 pmid1 = '1'
 pmid2 = '2'
@@ -27,11 +27,11 @@ class TestEdgePredicateBuilders(unittest.TestCase):
         pmid_inclusion_filter = build_pmid_inclusion_filter(pmid1)
 
         self.assertTrue(pmid_inclusion_filter({
-            CITATION: citation_dict(db=CITATION_TYPE_PUBMED, db_id=pmid1),
+            CITATION: citation_dict(namespace=CITATION_TYPE_PUBMED, identifier=pmid1),
         }))
 
         self.assertFalse(pmid_inclusion_filter({
-            CITATION: citation_dict(db=CITATION_TYPE_PUBMED, db_id=pmid2),
+            CITATION: citation_dict(namespace=CITATION_TYPE_PUBMED, identifier=pmid2),
         }))
 
     def test_build_pmid_set_inclusion_filter(self):
@@ -40,15 +40,15 @@ class TestEdgePredicateBuilders(unittest.TestCase):
         pmid_inclusion_filter = build_pmid_inclusion_filter(pmids)
 
         self.assertTrue(pmid_inclusion_filter({
-            CITATION: citation_dict(db=CITATION_TYPE_PUBMED, db_id=pmid1),
+            CITATION: citation_dict(namespace=CITATION_TYPE_PUBMED, identifier=pmid1),
         }))
 
         self.assertTrue(pmid_inclusion_filter({
-            CITATION: citation_dict(db=CITATION_TYPE_PUBMED, db_id=pmid2),
+            CITATION: citation_dict(namespace=CITATION_TYPE_PUBMED, identifier=pmid2),
         }))
 
         self.assertFalse(pmid_inclusion_filter({
-            CITATION: citation_dict(db=CITATION_TYPE_PUBMED, db_id=pmid3),
+            CITATION: citation_dict(namespace=CITATION_TYPE_PUBMED, identifier=pmid3),
         }))
 
     def test_build_author_inclusion_filter(self):
@@ -56,15 +56,15 @@ class TestEdgePredicateBuilders(unittest.TestCase):
         author_inclusion_filter = build_author_inclusion_filter(author1)
 
         self.assertTrue(author_inclusion_filter({
-            CITATION: citation_dict(db=CITATION_TYPE_PUBMED, db_id=pmid3, authors=[author1]),
+            CITATION: citation_dict(namespace=CITATION_TYPE_PUBMED, identifier=pmid3, authors=[author1]),
         }))
 
         self.assertTrue(author_inclusion_filter({
-            CITATION: citation_dict(db=CITATION_TYPE_PUBMED, db_id=pmid3, authors=[author1, author2]),
+            CITATION: citation_dict(namespace=CITATION_TYPE_PUBMED, identifier=pmid3, authors=[author1, author2]),
         }))
 
         self.assertFalse(author_inclusion_filter({
-            CITATION: citation_dict(db=CITATION_TYPE_PUBMED, db_id=pmid3, authors=[author3]),
+            CITATION: citation_dict(namespace=CITATION_TYPE_PUBMED, identifier=pmid3, authors=[author3]),
         }))
 
     def test_build_author_set_inclusion_filter(self):
diff --git a/tests/test_struct/test_filters/test_node_predicates.py b/tests/test_struct/test_filters/test_node_predicates.py
index 0db93e1f..94e478f6 100644
--- a/tests/test_struct/test_filters/test_node_predicates.py
+++ b/tests/test_struct/test_filters/test_node_predicates.py
@@ -6,9 +6,9 @@ import unittest
 
 from pybel import BELGraph
 from pybel.constants import (
-    ACTIVITY, ANNOTATIONS, ASSOCIATION, CAUSES_NO_CHANGE, CITATION, CITATION_AUTHORS, CITATION_DB, CITATION_IDENTIFIER,
-    CITATION_TYPE_ONLINE, CITATION_TYPE_PUBMED, DECREASES, DEGRADATION, DIRECTLY_DECREASES, DIRECTLY_INCREASES,
-    EVIDENCE, GMOD, INCREASES, LOCATION, MODIFIER, OBJECT, POLAR_RELATIONS, POSITIVE_CORRELATION, RELATION, SUBJECT,
+    ACTIVITY, ANNOTATIONS, ASSOCIATION, CAUSES_NO_CHANGE, CITATION, CITATION_AUTHORS, CITATION_TYPE_ONLINE,
+    CITATION_TYPE_PUBMED, DECREASES, DEGRADATION, DIRECTLY_DECREASES, DIRECTLY_INCREASES, EVIDENCE, GMOD, IDENTIFIER,
+    INCREASES, LOCATION, MODIFIER, NAMESPACE, TARGET_MODIFIER, POLAR_RELATIONS, POSITIVE_CORRELATION, RELATION, SOURCE_MODIFIER,
     TRANSLOCATION,
 )
 from pybel.dsl import (
@@ -149,7 +149,7 @@ class TestNodePredicates(unittest.TestCase):
             u,
             v,
             citation={
-                CITATION_DB: CITATION_TYPE_ONLINE, CITATION_IDENTIFIER: 'https://www.ncbi.nlm.nih.gov/gene/3290'
+                NAMESPACE: CITATION_TYPE_ONLINE, IDENTIFIER: 'https://www.ncbi.nlm.nih.gov/gene/3290'
             },
             evidence="Entrez Gene Summary: Human: The protein encoded by this gene is a microsomal enzyme that "
                      "catalyzes the conversion of the stress hormone cortisol to the inactive metabolite cortisone. "
@@ -158,8 +158,8 @@ class TestNodePredicates(unittest.TestCase):
                      "gene has been associated with obesity and insulin resistance in children. Two transcript "
                      "variants encoding the same protein have been found for this gene.",
             annotations={'Species': '9606'},
-            subject_modifier=activity('cat'),
-            object_modifier=degradation()
+            source_modifier=activity('cat'),
+            target_modifier=degradation(),
         )
 
         self.assertFalse(is_translocated(g, u))
@@ -183,7 +183,7 @@ class TestNodePredicates(unittest.TestCase):
             evidence="Although found predominantly in the cytoplasm and, less abundantly, in the nucleus, VCP can be "
                      "translocated from the nucleus after stimulation with epidermal growth factor.",
             annotations={'Species': '9606'},
-            object_modifier=translocation(
+            target_modifier=translocation(
                 from_loc=Entity(namespace='GO', identifier='0005634'),
                 to_loc=Entity(namespace='GO', identifier='0005737'),
             )
@@ -217,7 +217,7 @@ class TestNodePredicates(unittest.TestCase):
                      'expression and cellular infiltration....Recombinant cytokines were used, ... to treat naive '
                      'BALB/c mice.',
             annotations={'Species': '10090', 'MeSH': 'bronchoalveolar lavage fluid'},
-            object_modifier=secretion()
+            target_modifier=secretion(),
         )
 
         self.assertFalse(is_translocated(g, u))
@@ -245,7 +245,7 @@ class TestNodePredicates(unittest.TestCase):
             evidence='S100B protein is also secreted by astrocytes and acts on these cells to stimulate nitric oxide '
                      'secretion in an autocrine manner.',
             annotations={'Species': '10090', 'Cell': 'astrocyte'},
-            subject_modifier=secretion()
+            source_modifier=secretion()
         )
 
         self.assertTrue(is_translocated(g, u))
@@ -419,8 +419,8 @@ class TestEdgePredicate(unittest.TestCase):
         self.assertTrue(has_provenance({CITATION: {}, EVIDENCE: ''}))
 
     def test_has_pubmed(self):
-        self.assertTrue(has_pubmed({CITATION: {CITATION_DB: CITATION_TYPE_PUBMED}}))
-        self.assertFalse(has_pubmed({CITATION: {CITATION_DB: CITATION_TYPE_ONLINE}}))
+        self.assertTrue(has_pubmed({CITATION: {NAMESPACE: CITATION_TYPE_PUBMED}}))
+        self.assertFalse(has_pubmed({CITATION: {NAMESPACE: CITATION_TYPE_ONLINE}}))
         self.assertFalse(has_pubmed({}))
 
     def test_has_authors(self):
@@ -481,37 +481,37 @@ class TestEdgePredicate(unittest.TestCase):
         self.assertTrue(is_increase_or_decrease(g, p3, p4, 0))
 
     def test_has_degradation(self):
-        self.assertTrue(edge_has_degradation({SUBJECT: {MODIFIER: DEGRADATION}}))
-        self.assertTrue(edge_has_degradation({OBJECT: {MODIFIER: DEGRADATION}}))
+        self.assertTrue(edge_has_degradation({SOURCE_MODIFIER: {MODIFIER: DEGRADATION}}))
+        self.assertTrue(edge_has_degradation({TARGET_MODIFIER: {MODIFIER: DEGRADATION}}))
 
-        self.assertFalse(edge_has_degradation({SUBJECT: {MODIFIER: TRANSLOCATION}}))
-        self.assertFalse(edge_has_degradation({SUBJECT: {MODIFIER: ACTIVITY}}))
-        self.assertFalse(edge_has_degradation({SUBJECT: {LOCATION: None}}))
-        self.assertFalse(edge_has_degradation({OBJECT: {MODIFIER: TRANSLOCATION}}))
-        self.assertFalse(edge_has_degradation({OBJECT: {MODIFIER: ACTIVITY}}))
-        self.assertFalse(edge_has_degradation({OBJECT: {LOCATION: None}}))
+        self.assertFalse(edge_has_degradation({SOURCE_MODIFIER: {MODIFIER: TRANSLOCATION}}))
+        self.assertFalse(edge_has_degradation({SOURCE_MODIFIER: {MODIFIER: ACTIVITY}}))
+        self.assertFalse(edge_has_degradation({SOURCE_MODIFIER: {LOCATION: None}}))
+        self.assertFalse(edge_has_degradation({TARGET_MODIFIER: {MODIFIER: TRANSLOCATION}}))
+        self.assertFalse(edge_has_degradation({TARGET_MODIFIER: {MODIFIER: ACTIVITY}}))
+        self.assertFalse(edge_has_degradation({TARGET_MODIFIER: {LOCATION: None}}))
 
     def test_has_translocation(self):
-        self.assertTrue(edge_has_translocation({SUBJECT: {MODIFIER: TRANSLOCATION}}))
-        self.assertTrue(edge_has_translocation({OBJECT: {MODIFIER: TRANSLOCATION}}))
+        self.assertTrue(edge_has_translocation({SOURCE_MODIFIER: {MODIFIER: TRANSLOCATION}}))
+        self.assertTrue(edge_has_translocation({TARGET_MODIFIER: {MODIFIER: TRANSLOCATION}}))
 
-        self.assertFalse(edge_has_translocation({SUBJECT: {MODIFIER: ACTIVITY}}))
-        self.assertFalse(edge_has_translocation({SUBJECT: {LOCATION: None}}))
-        self.assertFalse(edge_has_translocation({SUBJECT: {MODIFIER: DEGRADATION}}))
-        self.assertFalse(edge_has_translocation({OBJECT: {MODIFIER: ACTIVITY}}))
-        self.assertFalse(edge_has_translocation({OBJECT: {LOCATION: None}}))
-        self.assertFalse(edge_has_translocation({OBJECT: {MODIFIER: DEGRADATION}}))
+        self.assertFalse(edge_has_translocation({SOURCE_MODIFIER: {MODIFIER: ACTIVITY}}))
+        self.assertFalse(edge_has_translocation({SOURCE_MODIFIER: {LOCATION: None}}))
+        self.assertFalse(edge_has_translocation({SOURCE_MODIFIER: {MODIFIER: DEGRADATION}}))
+        self.assertFalse(edge_has_translocation({TARGET_MODIFIER: {MODIFIER: ACTIVITY}}))
+        self.assertFalse(edge_has_translocation({TARGET_MODIFIER: {LOCATION: None}}))
+        self.assertFalse(edge_has_translocation({TARGET_MODIFIER: {MODIFIER: DEGRADATION}}))
 
     def test_has_activity(self):
-        self.assertTrue(edge_has_activity({SUBJECT: {MODIFIER: ACTIVITY}}))
-        self.assertTrue(edge_has_activity({OBJECT: {MODIFIER: ACTIVITY}}))
-
-        self.assertFalse(edge_has_activity({SUBJECT: {MODIFIER: TRANSLOCATION}}))
-        self.assertFalse(edge_has_activity({OBJECT: {MODIFIER: TRANSLOCATION}}))
-        self.assertFalse(edge_has_activity({SUBJECT: {LOCATION: None}}))
-        self.assertFalse(edge_has_activity({SUBJECT: {MODIFIER: DEGRADATION}}))
-        self.assertFalse(edge_has_activity({OBJECT: {LOCATION: None}}))
-        self.assertFalse(edge_has_activity({OBJECT: {MODIFIER: DEGRADATION}}))
+        self.assertTrue(edge_has_activity({SOURCE_MODIFIER: {MODIFIER: ACTIVITY}}))
+        self.assertTrue(edge_has_activity({TARGET_MODIFIER: {MODIFIER: ACTIVITY}}))
+
+        self.assertFalse(edge_has_activity({SOURCE_MODIFIER: {MODIFIER: TRANSLOCATION}}))
+        self.assertFalse(edge_has_activity({TARGET_MODIFIER: {MODIFIER: TRANSLOCATION}}))
+        self.assertFalse(edge_has_activity({SOURCE_MODIFIER: {LOCATION: None}}))
+        self.assertFalse(edge_has_activity({SOURCE_MODIFIER: {MODIFIER: DEGRADATION}}))
+        self.assertFalse(edge_has_activity({TARGET_MODIFIER: {LOCATION: None}}))
+        self.assertFalse(edge_has_activity({TARGET_MODIFIER: {MODIFIER: DEGRADATION}}))
 
     def test_has_annotation(self):
         self.assertFalse(edge_has_annotation({}, 'Subgraph'))
diff --git a/tests/test_struct/test_struct_graph.py b/tests/test_struct/test_struct_graph.py
index e31722b3..d9860541 100644
--- a/tests/test_struct/test_struct_graph.py
+++ b/tests/test_struct/test_struct_graph.py
@@ -11,7 +11,7 @@ from io import StringIO
 import pybel
 import pybel.examples
 from pybel import BELGraph
-from pybel.constants import CITATION_DB, CITATION_IDENTIFIER, CITATION_TYPE_PUBMED
+from pybel.constants import CITATION_TYPE_PUBMED, IDENTIFIER, NAMESPACE
 from pybel.dsl import hgvs, protein
 from pybel.io.api import InvalidExtensionError
 from pybel.testing.utils import n
@@ -101,7 +101,7 @@ class TestStruct(unittest.TestCase):
 Name                   Sialic Acid Graph
 Version                1.0.0
 Number of Nodes        9
-Number of Namespaces   3
+Number of Namespaces   4
 Number of Edges        11
 Number of Annotations  2
 Number of Citations    1
@@ -117,10 +117,12 @@ Protein           7
 Complex           1
 Abundance         1
 
-Namespace (2)      Count
+Namespace (4)      Count
 ---------------  -------
-hgnc                   7
-chebi                  1
+hgnc                   8
+chebi                  2
+go                     2
+bel                    1
 
 Edge Type (7)                        Count
 ---------------------------------  -------
@@ -179,10 +181,10 @@ class TestGetGraphProperties(unittest.TestCase):
 
         self.assertIsNotNone(citation)
         self.assertIsInstance(citation, dict)
-        self.assertIn(CITATION_DB, citation)
-        self.assertEqual(CITATION_TYPE_PUBMED, citation[CITATION_DB])
-        self.assertIn(CITATION_IDENTIFIER, citation)
-        self.assertEqual(test_pmid, citation[CITATION_IDENTIFIER])
+        self.assertIn(NAMESPACE, citation)
+        self.assertEqual(CITATION_TYPE_PUBMED, citation[NAMESPACE])
+        self.assertIn(IDENTIFIER, citation)
+        self.assertEqual(test_pmid, citation[IDENTIFIER])
 
         evidence = self.graph.get_edge_evidence(test_source, test_target, test_key)
 
diff --git a/tests/test_struct/test_summary/test_errors.py b/tests/test_struct/test_summary/test_errors.py
index b5abb6d4..2360feb0 100644
--- a/tests/test_struct/test_summary/test_errors.py
+++ b/tests/test_struct/test_summary/test_errors.py
@@ -5,7 +5,7 @@
 import unittest
 
 from pybel import BELGraph
-from pybel.parser.exc import NakedNameWarning, UndefinedAnnotationWarning
+from pybel.exceptions import NakedNameWarning, UndefinedAnnotationWarning
 from pybel.struct.summary import count_error_types, count_naked_names, get_naked_names
 from pybel.testing.utils import n
 
diff --git a/tests/test_struct/test_summary/test_summary_nodes.py b/tests/test_struct/test_summary/test_summary_nodes.py
index f59b5f55..0fdde437 100644
--- a/tests/test_struct/test_summary/test_summary_nodes.py
+++ b/tests/test_struct/test_summary/test_summary_nodes.py
@@ -6,7 +6,7 @@ import unittest
 from collections import Counter
 
 from pybel import BELGraph
-from pybel.constants import ABUNDANCE, BIOPROCESS, COMPLEX, PROTEIN
+from pybel.constants import ABUNDANCE, BEL_DEFAULT_NAMESPACE, BIOPROCESS, COMPLEX, PROTEIN
 from pybel.dsl import fusion_range, pathology, protein, protein_fusion
 from pybel.examples import egf_graph, sialic_acid_graph
 from pybel.struct.summary.node_summary import (
@@ -46,7 +46,8 @@ class TestSummary(unittest.TestCase):
         namespace_result = {
             'hgnc': 8,
             'chebi': 2,
-            'bel': 3,
+            BEL_DEFAULT_NAMESPACE: 1,
+            'go': 2
         }
         self.assertEqual(set(namespace_result), get_namespaces(sialic_acid_graph))
         self.assertEqual(Counter(namespace_result), count_namespaces(sialic_acid_graph))
@@ -75,8 +76,7 @@ class TestSummary(unittest.TestCase):
         """Test getting and counting namespaces' contents on the EGF graph."""
         result = {
             'hgnc': 15,
-            'go': 3,
-            'bel': 4,
+            'go': 7,
         }
 
         self.assertEqual(set(result), get_namespaces(egf_graph))
diff --git a/tests/test_struct/test_transformations/test_induction.py b/tests/test_struct/test_transformations/test_induction.py
index a2eb8e07..ef6dbef0 100644
--- a/tests/test_struct/test_transformations/test_induction.py
+++ b/tests/test_struct/test_transformations/test_induction.py
@@ -6,9 +6,7 @@ import string
 import unittest
 
 from pybel import BELGraph
-from pybel.constants import (
-    CITATION_AUTHORS, CITATION_IDENTIFIER, CITATION_DB, CITATION_TYPE_PUBMED,
-)
+from pybel.constants import CITATION_AUTHORS, CITATION_TYPE_PUBMED, IDENTIFIER, NAMESPACE
 from pybel.dsl import BaseEntity, gene, protein, rna
 from pybel.struct.mutation.expansion import expand_upstream_causal
 from pybel.struct.mutation.induction import get_subgraph_by_annotation_value
@@ -248,13 +246,13 @@ class TestEdgePredicateBuilders(TestGraphMixin):
         a1, a2, a3, a4, a5 = n(), n(), n(), n(), n()
 
         c1 = {
-            CITATION_DB: CITATION_TYPE_PUBMED,
-            CITATION_IDENTIFIER: n(),
+            NAMESPACE: CITATION_TYPE_PUBMED,
+            IDENTIFIER: n(),
             CITATION_AUTHORS: [a1, a2, a3]
         }
         c2 = {
-            CITATION_DB: CITATION_TYPE_PUBMED,
-            CITATION_IDENTIFIER: n(),
+            NAMESPACE: CITATION_TYPE_PUBMED,
+            IDENTIFIER: n(),
             CITATION_AUTHORS: [a1, a4]
         }
 
@@ -307,13 +305,13 @@ class TestEdgePredicateBuilders(TestGraphMixin):
         a1, a2, a3, a4 = n(), n(), n(), n()
 
         c1 = {
-            CITATION_DB: CITATION_TYPE_PUBMED,
-            CITATION_IDENTIFIER: n(),
+            NAMESPACE: CITATION_TYPE_PUBMED,
+            IDENTIFIER: n(),
             CITATION_AUTHORS: [a1, a2, a3]
         }
         c2 = {
-            CITATION_DB: CITATION_TYPE_PUBMED,
-            CITATION_IDENTIFIER: n(),
+            NAMESPACE: CITATION_TYPE_PUBMED,
+            IDENTIFIER: n(),
             CITATION_AUTHORS: [a1, a4]
         }
 
diff --git a/tests/test_struct/test_transformations/test_metadata.py b/tests/test_struct/test_transformations/test_metadata.py
index fdb5c575..31dfa8a5 100644
--- a/tests/test_struct/test_transformations/test_metadata.py
+++ b/tests/test_struct/test_transformations/test_metadata.py
@@ -6,7 +6,7 @@ import unittest
 
 from pybel import BELGraph
 from pybel.constants import (
-    ANNOTATIONS, CITATION, CITATION_AUTHORS, CITATION_DATE, CITATION_IDENTIFIER, CITATION_DB, CITATION_TYPE_PUBMED,
+    ANNOTATIONS, CITATION, CITATION_AUTHORS, CITATION_DATE, CITATION_TYPE_PUBMED, IDENTIFIER, NAMESPACE,
 )
 from pybel.dsl import protein
 from pybel.examples import sialic_acid_graph
@@ -100,8 +100,8 @@ class TestMetadata(unittest.TestCase):
             x,
             y,
             citation={
-                CITATION_DB: CITATION_TYPE_PUBMED,
-                CITATION_IDENTIFIER: '12345678',
+                NAMESPACE: CITATION_TYPE_PUBMED,
+                IDENTIFIER: '12345678',
                 CITATION_DATE: '2018-12-10',
             },
             evidence='Fake',
@@ -116,7 +116,7 @@ class TestMetadata(unittest.TestCase):
 
         for k in k1, k2:
             self.assertIn(CITATION, graph[x][y][k])
-            self.assertIn(CITATION_DB, graph[x][y][k][CITATION])
-            self.assertIn(CITATION_IDENTIFIER, graph[x][y][k][CITATION])
+            self.assertIn(NAMESPACE, graph[x][y][k][CITATION])
+            self.assertIn(IDENTIFIER, graph[x][y][k][CITATION])
             self.assertNotIn(CITATION_DATE, graph[x][y][k][CITATION])
             self.assertNotIn(CITATION_AUTHORS, graph[x][y][k][CITATION])
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 10611720..5e7ce828 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -4,7 +4,7 @@
 
 import unittest
 
-from pybel.parser.exc import PlaceholderAminoAcidWarning
+from pybel.exceptions import PlaceholderAminoAcidWarning
 from pybel.parser.modifiers.constants import amino_acid
 from pybel.parser.utils import nest
 from pybel.utils import expand_dict, flatten_dict, tokenize_version