Repository: pybel/pybel
Pull request #454 (2020-07-06 21:09:28)

Update insertions
This PR makes insertion of namespaces/annotations much faster. It also finishes removing all references to the BEL default namespace, which is no longer necessary as of #455 since all entries in it are automatically upgraded to the corresponding GO terms.

codecov[bot]: # [Codecov](https://codecov.io/gh/pybel/pybel/pull/454?src=pr&el=h1) Report
> Merging [#454](https://codecov.io/gh/pybel/pybel/pull/454?src=pr&el=desc) into [master](https://codecov.io/gh/pybel/pybel/commit/4ab0a6ad4183f960c42da2d443889ce540712d46&el=desc) will **increase** coverage by `0.04%`.
> The diff coverage is `91.02%`.

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

```diff
@@            Coverage Diff             @@
##           master     #454      +/-   ##
==========================================
+ Coverage   79.10%   79.14%   +0.04%     
==========================================
  Files         171      171              
  Lines        9039     9050      +11     
  Branches     1307     1314       +7     
==========================================
+ Hits         7150     7163      +13     
+ Misses       1608     1606       -2     
  Partials      281      281              
```


| [Impacted Files](https://codecov.io/gh/pybel/pybel/pull/454?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/pybel/manager/models.py](https://codecov.io/gh/pybel/pybel/pull/454/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL21hbmFnZXIvbW9kZWxzLnB5) | `88.54% <ø> (ø)` | |
| [src/pybel/manager/cache\_manager.py](https://codecov.io/gh/pybel/pybel/pull/454/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL21hbmFnZXIvY2FjaGVfbWFuYWdlci5weQ==) | `80.60% <90.16%> (+0.15%)` | :arrow_up: |
| [src/pybel/parser/parse\_metadata.py](https://codecov.io/gh/pybel/pybel/pull/454/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL3BhcnNlci9wYXJzZV9tZXRhZGF0YS5weQ==) | `94.11% <93.33%> (+1.75%)` | :arrow_up: |
| [src/pybel/io/line\_utils.py](https://codecov.io/gh/pybel/pybel/pull/454/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2lvL2xpbmVfdXRpbHMucHk=) | `69.09% <100.00%> (+0.57%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pybel/pybel/pull/454?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/454?src=pr&el=footer). Last update [4ab0a6a...3b73367](https://codecov.io/gh/pybel/pybel/pull/454?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/pybel/canonicalize.py b/src/pybel/canonicalize.py
index 5121e3f3..a01a3209 100644
--- a/src/pybel/canonicalize.py
+++ b/src/pybel/canonicalize.py
@@ -14,10 +14,9 @@ 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_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,
+    ACTIVITY, ANNOTATIONS, CITATION, CITATION_TYPE_PUBMED, DEGRADATION, EFFECT, EVIDENCE, FROM_LOC, 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
@@ -136,16 +135,6 @@ def _decanonicalize_edge_node(
 
         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:
-                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:
-                warnings.warn('deprecated output of BEL default namespace', DeprecationWarning)
-                return f'surf({node_str})'
-            raise ValueError
-
         return f"tloc({node_str}, fromLoc({from_loc_data}), toLoc({to_loc_data}))"
 
     raise ValueError('invalid modifier: {}'.format(modifier))
diff --git a/src/pybel/constants.py b/src/pybel/constants.py
index a78cf9ce..d4b388fe 100644
--- a/src/pybel/constants.py
+++ b/src/pybel/constants.py
@@ -23,9 +23,6 @@ def get_cache_connection() -> str:
 PYBEL_CONTEXT_TAG = 'pybel_context'
 PYBEL_AUTOEVIDENCE = 'Automatically added by PyBEL'
 
-#: The default namespace given to entities in the BEL language
-BEL_DEFAULT_NAMESPACE = 'bel'
-
 CITATION_TYPE_BOOK = 'Book'
 CITATION_TYPE_PUBMED = 'PubMed'
 CITATION_TYPE_PMC = 'PubMed Central'
diff --git a/src/pybel/dsl/edges.py b/src/pybel/dsl/edges.py
index 0e5e594d..6e3a8be1 100644
--- a/src/pybel/dsl/edges.py
+++ b/src/pybel/dsl/edges.py
@@ -6,7 +6,7 @@ import warnings
 from typing import Dict, Optional, Union
 
 from ..constants import (
-    ACTIVITY, BEL_DEFAULT_NAMESPACE, CELL_SURFACE, DEGRADATION, EFFECT, EXTRACELLULAR, FROM_LOC, INTRACELLULAR,
+    ACTIVITY, CELL_SURFACE, DEGRADATION, EFFECT, EXTRACELLULAR, FROM_LOC, INTRACELLULAR,
     LOCATION, MODIFIER, NAME, NAMESPACE, TO_LOC, TRANSLOCATION,
 )
 from ..language import Entity, activity_mapping, compartment_mapping
@@ -58,7 +58,7 @@ def activity(
     """
     rv = _modifier_helper(ACTIVITY, location=location)
 
-    if namespace == BEL_DEFAULT_NAMESPACE or (name and not namespace):
+    if name and not namespace:
         rv[EFFECT] = activity_mapping[name]
     elif not name and not namespace and not identifier:
         pass
@@ -92,16 +92,10 @@ def translocation(
     rv = _modifier_helper(TRANSLOCATION)
     if isinstance(from_loc, str):
         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 = 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
 
diff --git a/src/pybel/io/line_utils.py b/src/pybel/io/line_utils.py
index 8767d96d..df7ea5ac 100644
--- a/src/pybel/io/line_utils.py
+++ b/src/pybel/io/line_utils.py
@@ -225,6 +225,9 @@ def parse_definitions(
 
     logger.info('Finished parsing definitions section in %.02f seconds', time.time() - parse_definitions_start_time)
 
+    metadata_parser.ensure_resources()
+    logger.info('Finished ensuring namespaces in cache')
+
 
 def parse_statements(
     graph: BELGraph,
diff --git a/src/pybel/language.py b/src/pybel/language.py
index 9d83758f..34f9e32d 100644
--- a/src/pybel/language.py
+++ b/src/pybel/language.py
@@ -8,8 +8,7 @@ This module contains mappings between PyBEL's internal constants and BEL languag
 from typing import Optional
 
 from .constants import (
-    ABUNDANCE, BEL_DEFAULT_NAMESPACE, BIOPROCESS, CELL_SURFACE, COMPLEX, COMPOSITE, EXTRACELLULAR, GENE, IDENTIFIER,
-    INTRACELLULAR, MIRNA, NAME, NAMESPACE, PATHOLOGY, PROTEIN, RNA, TRANSCRIBED_TO, TRANSLATED_TO,
+    ABUNDANCE, BIOPROCESS, CELL_SURFACE, COMPLEX, COMPOSITE, EXTRACELLULAR, GENE, IDENTIFIER, INTRACELLULAR, MIRNA, NAME, NAMESPACE, PATHOLOGY, PROTEIN, RNA, TRANSCRIBED_TO, TRANSLATED_TO,
 )
 from .utils import ensure_quotes
 
@@ -73,9 +72,6 @@ class Entity(dict):
     @property
     def curie(self) -> str:
         """Return this entity as a CURIE."""
-        if self[NAMESPACE] == BEL_DEFAULT_NAMESPACE:
-            return self[NAME]
-
         return '{}:{}'.format(
             self.namespace,
             ensure_quotes(self.identifier if self.identifier else self.name),
@@ -553,9 +549,6 @@ 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."""
diff --git a/src/pybel/manager/cache_manager.py b/src/pybel/manager/cache_manager.py
index cb240c31..40fbe9aa 100644
--- a/src/pybel/manager/cache_manager.py
+++ b/src/pybel/manager/cache_manager.py
@@ -8,9 +8,9 @@ enable this option, but can specify a database location if they choose.
 
 import logging
 import time
-from itertools import chain
-from typing import Any, Iterable, List, Mapping, Optional, Set, Tuple
+from typing import Iterable, List, Mapping, Optional, Set, Tuple
 
+import pandas as pd
 import requests
 import sqlalchemy
 from sqlalchemy import and_, exists, func
@@ -28,14 +28,11 @@ 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_TYPE_PUBMED, EVIDENCE, IDENTIFIER, METADATA_INSERT_KEYS,
-    NAMESPACE, RELATION, SOURCE_MODIFIER, TARGET_MODIFIER, UNQUALIFIED_EDGES, belns_encodings, get_cache_connection,
+    ANNOTATIONS, 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 (
-    BEL_DEFAULT_NAMESPACE_URL, BEL_DEFAULT_NAMESPACE_VERSION, Entity, activity_mapping, compartment_mapping,
-    gmod_mappings, pmod_mappings,
-)
+from ..language import Entity
 from ..struct.graph import AnnotationsDict, BELGraph
 from ..struct.operations import union
 from ..typing import EdgeData
@@ -101,14 +98,6 @@ def _clean_bel_namespace_values(bel_resource):
     }
 
 
-def _normalize_url(graph: BELGraph, keyword: str) -> Optional[str]:  # FIXME move to utilities and unit test
-    """Normalize a URL for the BEL graph."""
-    if keyword == BEL_DEFAULT_NAMESPACE and BEL_DEFAULT_NAMESPACE not in graph.namespace_url:
-        return BEL_DEFAULT_NAMESPACE_URL
-
-    return graph.namespace_url.get(keyword)
-
-
 class NamespaceManager(BaseManager):
     """Manages BEL namespaces."""
 
@@ -151,80 +140,86 @@ class NamespaceManager(BaseManager):
         filt = and_(Namespace.keyword == keyword, Namespace.version == version)
         return self.session.query(Namespace).filter(filt).one_or_none()
 
-    def ensure_default_namespace(self) -> Namespace:
-        """Get or create the BEL default namespace."""
-        namespace = self.get_namespace_by_keyword_version(BEL_DEFAULT_NAMESPACE, BEL_DEFAULT_NAMESPACE_VERSION)
-
-        if namespace is None:
-            namespace = Namespace(
-                name='BEL Default Namespace',
-                keyword=BEL_DEFAULT_NAMESPACE,
-                version=BEL_DEFAULT_NAMESPACE_VERSION,
-                url=BEL_DEFAULT_NAMESPACE_URL,
-            )
-
-            for name in set(chain(pmod_mappings, gmod_mappings, activity_mapping, compartment_mapping)):
-                entry = NamespaceEntry(name=name, namespace=namespace)
-                self.session.add(entry)
-
-            self.session.add(namespace)
-            self.session.commit()
-
-        return namespace
-
-    def get_or_create_namespace(self, url: str) -> Namespace:
-        """Insert the namespace file at the given location to the cache.
-
-        If not cachable, returns the dict of the values of this namespace.
-
-        :raises: pybel.resources.exc.ResourceError
-        """
-        result = self.get_namespace_by_url(url)
+    def _ensure_namespace_urls(
+        self,
+        urls: Iterable[str],
+        use_tqdm: bool = True,
+        is_annotation: bool = False,
+    ) -> List[Namespace]:
+        ext = 'belanno' if is_annotation else 'belns'
 
-        if result is not None:
-            return result
+        rv = []
+        url_to_namespace = {}
+        url_to_values = {}
+        url_to_name_to_id = {}
 
-        t = time.time()
+        if use_tqdm:
+            urls = tqdm(urls, desc='downloading namespaces')
+        for url in urls:
+            result = self.get_namespace_by_url(url)
+            if result:
+                rv.append(result)
+                continue
+            bel_resource = get_bel_resource(url)
+            _clean_bel_namespace_values(bel_resource)
+            url_to_values[url] = bel_resource['Values']
 
-        bel_resource = get_bel_resource(url)
+            if is_annotation:
+                namespace_kwargs = _get_annotation_insert_values(bel_resource)
+            else:
+                namespace_kwargs = _get_namespace_insert_values(bel_resource)
+            result = url_to_namespace[url] = Namespace(url=url, **namespace_kwargs)
+            rv.append(result)
+            if url.endswith(f'-names.{ext}'):
+                mapping_url = url[:-len(f'-names.{ext}')] + f'.{ext}.mapping'
+                try:
+                    res = requests.get(mapping_url)
+                    res.raise_for_status()
+                except requests.exceptions.HTTPError:
+                    logger.warning('No mappings found for %s', url)
+                else:
+                    mappings = res.json()
+                    logger.debug('got %d mappings', len(mappings))
+                    url_to_name_to_id[url] = {v: k for k, v in res.json().items()}
 
-        _clean_bel_namespace_values(bel_resource)
+        self.session.add_all(url_to_namespace.values())
+        self.session.commit()
 
-        values = bel_resource['Values']
+        url_to_id = {url: namespace.id for url, namespace in url_to_namespace.items()}
 
-        namespace_insert_values = _get_namespace_insert_values(bel_resource)
+        rows = []
+        it = url_to_values.items()
+        if use_tqdm:
+            it = tqdm(it, desc='making namespace entry table')
 
-        name_to_id = {}
-        if url.endswith('-names.belns'):
-            mapping_url = url[:-len('-names.belns')] + '.belns.mapping'
-            try:
-                res = requests.get(mapping_url)
-                res.raise_for_status()
-            except requests.exceptions.HTTPError:
-                logger.warning('No mappings found for %s', url)
-            else:
-                mappings = res.json()
-                logger.debug('got %d mappings', len(mappings))
-                name_to_id.update({v: k for k, v in res.json().items()})
+        if is_annotation:
+            for url, values in it:
+                for name, identifier in values.items():
+                    if not name:
+                        continue
+                    rows.append((url_to_id[url], name, None, identifier))  # TODO is this a fair assumption?
+        else:
+            for url, values in it:
+                name_to_id = url_to_name_to_id.get(url, {})
+                for name, encoding in values.items():
+                    if not name:
+                        continue
+                    rows.append((url_to_id[url], name, encoding, name_to_id.get(name)))
+        df = pd.DataFrame(rows, columns=['namespace_id', 'name', 'encoding', 'identifier'])
+        df.to_sql(NamespaceEntry.__tablename__, con=self.engine, if_exists='append', index=False)
 
-        namespace = Namespace(
-            url=url,
-            **namespace_insert_values,
-        )
+        self.session.commit()
 
-        logger.debug('building NamespaceEntry instances')
-        namespace.entries = [
-            NamespaceEntry(name=name, encoding=encoding, identifier=name_to_id.get(name))
-            for name, encoding in values.items()
-        ]
+        return rv
 
-        self.session.add(namespace)
+    def get_or_create_namespace(self, url: str) -> Namespace:
+        """Insert the namespace file at the given location to the cache.
 
-        logger.debug('committing namespace')
-        self.session.commit()
+        If not cachable, returns the dict of the values of this namespace.
 
-        logger.info('inserted namespace: %s (%d terms in %.2f seconds)', url, len(values), time.time() - t)
-        return namespace
+        :raises: pybel.resources.exc.ResourceError
+        """
+        return self._ensure_namespace_urls([url])[0]
 
     def get_namespace_by_keyword_pattern(self, keyword: str, pattern: str) -> Optional[Namespace]:
         """Get a namespace with a given keyword and pattern."""
@@ -319,51 +314,7 @@ class NamespaceManager(BaseManager):
 
         :raises: pybel.resources.exc.ResourceError
         """
-        result = self.get_namespace_by_url(url)
-
-        if result is not None:
-            return result
-
-        t = time.time()
-
-        bel_resource = get_bel_resource(url)
-
-        result = Namespace(
-            url=url,
-            is_annotation=True,
-            **_get_annotation_insert_values(bel_resource),
-        )
-        result.entries = [
-            NamespaceEntry(name=name, identifier=label)
-            for name, label in bel_resource['Values'].items()
-            if name
-        ]
-
-        self.session.add(result)
-        self.session.commit()
-
-        logger.info(
-            'inserted annotation: %s (%d terms in %.2f seconds)', url, len(bel_resource['Values']),
-            time.time() - t,
-        )
-
-        return result
-
-    def get_annotation_entry_names(self, url: str) -> Set[str]:
-        """Return a dict of annotations and their labels for the given annotation file.
-
-        :param url: the location of the annotation file
-        """
-        annotation = self.get_or_create_annotation(url)
-        return {
-            x.name
-            for x in self.session.query(NamespaceEntry.name).filter(NamespaceEntry.namespace == annotation)
-        }
-
-    def get_namespace_encoding(self, url: str) -> Mapping[str, str]:
-        annotation = self.get_or_create_annotation(url)
-        namespace_filter = NamespaceEntry.namespace == annotation
-        return dict(self.session.query(NamespaceEntry.name, NamespaceEntry.encoding).filter(namespace_filter))
+        return self._ensure_namespace_urls([url], is_annotation=True)[0]
 
     def get_annotation_entries_by_names(self, url: str, names: Iterable[str]) -> List[NamespaceEntry]:
         """Get annotation entries by URL and names.
@@ -569,7 +520,6 @@ class InsertManager(NamespaceManager, LookupManager):
         self,
         graph: BELGraph,
         use_tqdm: bool = True,
-        tqdm_kwargs: Optional[Mapping[str, Any]] = None,
     ) -> Network:
         """Insert a graph in the database and returns the corresponding Network model.
 
@@ -586,21 +536,12 @@ class InsertManager(NamespaceManager, LookupManager):
         t = time.time()
 
         namespace_urls = graph.namespace_url.values()
-        if use_tqdm:
-            namespace_urls = tqdm(namespace_urls, **(tqdm_kwargs or {}))
-        for namespace_url in namespace_urls:
-            if use_tqdm:
-                namespace_urls.write('inserting namespace: {}'.format(namespace_url))
-            self.get_or_create_namespace(namespace_url)
-
+        self._ensure_namespace_urls(namespace_urls, use_tqdm=use_tqdm)
         for keyword, pattern in graph.namespace_pattern.items():
             self.ensure_regex_namespace(keyword, pattern)
 
         annotation_urls = graph.annotation_url.values()
-        if use_tqdm:
-            annotation_urls = tqdm(annotation_urls, desc='annotations')
-        for annotation_url in annotation_urls:
-            self.get_or_create_annotation(annotation_url)
+        self._ensure_namespace_urls(annotation_urls, use_tqdm=use_tqdm, is_annotation=True)
 
         network = Network(**{
             key: value
diff --git a/src/pybel/manager/models.py b/src/pybel/manager/models.py
index de3ffebf..70f75798 100644
--- a/src/pybel/manager/models.py
+++ b/src/pybel/manager/models.py
@@ -192,7 +192,7 @@ class NamespaceEntry(Base):
         return cls.name.contains(name_query)
 
     def __str__(self):
-        return '[{namespace_id}]{namespace_name}:[{identifier}]{name}'.format(
+        return '[id={namespace_id}] {namespace_name}:{identifier} ! {name}'.format(
             namespace_id=self.namespace.id,
             namespace_name=self.namespace.keyword,
             identifier=self.identifier,
diff --git a/src/pybel/parser/parse_metadata.py b/src/pybel/parser/parse_metadata.py
index 2a34dd0e..d6588a50 100644
--- a/src/pybel/parser/parse_metadata.py
+++ b/src/pybel/parser/parse_metadata.py
@@ -142,7 +142,7 @@ class MetadataParser(BaseParser):
             self.namespace_pattern,
         ]).setName('BEL Metadata')
 
-        super(MetadataParser, self).__init__(self.language)
+        super().__init__(self.language)
 
     def handle_document(self, line: str, position: int, tokens: ParseResults) -> ParseResults:
         """Handle statements like ``SET DOCUMENT X = "Y"``.
@@ -196,13 +196,24 @@ class MetadataParser(BaseParser):
 
         self.namespace_url_dict[namespace_keyword] = url
 
+        return tokens
+
+    def ensure_resources(self):
+        """Load all namespaces/annotations that have been encountered so far during parsing."""
         if self.skip_validation:
-            return tokens
+            return
 
-        namespace = self.manager.get_or_create_namespace(url)
-        self.namespace_to_term_to_encoding[namespace_keyword] = namespace.get_term_to_encodings()
+        if self.namespace_url_dict:
+            keywords, urls = zip(*self.namespace_url_dict.items())
+            namespaces = self.manager._ensure_namespace_urls(urls)
+            for keyword, namespace in zip(keywords, namespaces):
+                self.namespace_to_term_to_encoding[keyword] = namespace.get_term_to_encodings()
 
-        return tokens
+        if self.annotation_url_dict:
+            keywords, urls = zip(*self.annotation_url_dict.items())
+            namespaces = self.manager._ensure_namespace_urls(urls, is_annotation=True)
+            for keyword, namespace in zip(keywords, namespaces):
+                self.annotation_to_term[keyword] = {entry.name for entry in namespace.entries}
 
     def handle_namespace_pattern(self, line: str, position: int, tokens: ParseResults) -> ParseResults:
         """Handle statements like ``DEFINE NAMESPACE X AS PATTERN "Y"``.
@@ -229,15 +240,7 @@ class MetadataParser(BaseParser):
         """
         keyword = tokens['name']
         self.raise_for_redefined_annotation(line, position, keyword)
-
-        url = tokens['url']
-        self.annotation_url_dict[keyword] = url
-
-        if self.skip_validation:
-            return tokens
-
-        self.annotation_to_term[keyword] = self.manager.get_annotation_entry_names(url)
-
+        self.annotation_url_dict[keyword] = tokens['url']
         return tokens
 
     def handle_annotation_list(self, line: str, position: int, tokens: ParseResults) -> ParseResults:


Test patch:
diff --git a/src/pybel/testing/utils.py b/src/pybel/testing/utils.py
index cf4eadf9..511b726c 100644
--- a/src/pybel/testing/utils.py
+++ b/src/pybel/testing/utils.py
@@ -6,7 +6,6 @@ from uuid import uuid4
 
 from requests.compat import urlparse
 
-from ..constants import BEL_DEFAULT_NAMESPACE
 from ..manager import Manager
 from ..manager.models import Namespace, NamespaceEntry
 from ..struct import BELGraph
@@ -34,9 +33,6 @@ def n() -> str:
 def make_dummy_namespaces(manager: Manager, graph: BELGraph) -> None:
     """Make dummy namespaces for the test."""
     for keyword, names in get_names(graph).items():
-        if keyword == BEL_DEFAULT_NAMESPACE:
-            continue
-
         graph.namespace_url[keyword] = url = n()
 
         namespace = Namespace(keyword=keyword, url=url)
diff --git a/tests/test_canonicalization.py b/tests/test_canonicalization.py
index b11b0451..b5fba0a0 100644
--- a/tests/test_canonicalization.py
+++ b/tests/test_canonicalization.py
@@ -7,7 +7,7 @@ from typing import Iterable
 
 from pybel import BELGraph
 from pybel.canonicalize import _to_bel_lines_body, postpend_location
-from pybel.constants import BEL_DEFAULT_NAMESPACE, EXTRACELLULAR, INTRACELLULAR, MODIFIER
+from pybel.constants import EXTRACELLULAR, INTRACELLULAR, MODIFIER
 from pybel.dsl import (
     Abundance, BiologicalProcess, ComplexAbundance, CompositeAbundance, EnumeratedFusionRange, Fragment, Gene,
     GeneFusion, GeneModification, Hgvs, MicroRna, NamedComplexAbundance, Pathology, Protein, ProteinModification,
@@ -185,7 +185,7 @@ class TestCanonicalizeEdge(unittest.TestCase):
         )
 
         c4 = self.add_edge(
-            source_modifier=activity('tport', namespace=BEL_DEFAULT_NAMESPACE),
+            source_modifier=activity(namespace='go', name='transporter activity', identifier='0005215'),
         )
 
         self.assertEqual(c1, c2)
diff --git a/tests/test_parse/test_parse_metadata.py b/tests/test_parse/test_parse_metadata.py
index 0dc0836e..af71c512 100644
--- a/tests/test_parse/test_parse_metadata.py
+++ b/tests/test_parse/test_parse_metadata.py
@@ -44,6 +44,7 @@ class TestParseMetadata(FleetingTemporaryCacheMixin):
         """Test that a namespace defined by a URL can't be overwritten by a definition by another URL."""
         s = NAMESPACE_URL_FMT.format(HGNC_KEYWORD, HGNC_URL)
         self.parser.parseString(s)
+        self.parser.ensure_resources()
         help_check_hgnc(self, self.parser.namespace_to_term_to_encoding)
 
         s = NAMESPACE_URL_FMT.format(HGNC_KEYWORD, 'XXXXX')
@@ -57,6 +58,8 @@ class TestParseMetadata(FleetingTemporaryCacheMixin):
         """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.parser.ensure_resources()
+
         self.assertIn(MESH_DISEASES_KEYWORD, self.parser.annotation_to_term)
 
         s = 'DEFINE ANNOTATION {} AS LIST {{"A","B","C"}}'.format(MESH_DISEASES_KEYWORD)
@@ -95,6 +98,7 @@ class TestParseMetadata(FleetingTemporaryCacheMixin):
             'DEFINE ANNOTATION TextLocation AS LIST {"Abstract","Results","Legend","Review"}'
         ]
         self.parser.parse_lines(lines)
+        self.parser.ensure_resources()
 
         self.assertIn(MESH_DISEASES_KEYWORD, self.parser.annotation_to_term)
         self.assertIn(HGNC_KEYWORD, self.parser.namespace_to_term_to_encoding)
@@ -128,6 +132,7 @@ class TestParseMetadata(FleetingTemporaryCacheMixin):
         """Tests parsing a namespace by file URL"""
         s = NAMESPACE_URL_FMT.format('TESTNS1', test_ns_1)
         self.parser.parseString(s)
+        self.parser.ensure_resources()
 
         expected_values = {
             'TestValue1': {'O'},
@@ -150,6 +155,7 @@ class TestParseMetadata(FleetingTemporaryCacheMixin):
         url = Path(test_an_1).as_uri()
         line = ANNOTATION_URL_FMT.format(keyword, url)
         self.parser.parseString(line)
+        self.parser.ensure_resources()
 
         expected_values = {
             'TestAnnot1': 'O',
@@ -159,7 +165,9 @@ class TestParseMetadata(FleetingTemporaryCacheMixin):
             'TestAnnot5': 'O'
         }
 
-        self.assertEqual(set(expected_values), self.parser.manager.get_annotation_entry_names(url))
+        annotation = self.parser.manager.get_namespace_by_url(url)
+        self.assertIsNotNone(annotation)
+        self.assertEqual(set(expected_values), {e.name for e in annotation.entries})
 
     def test_parse_annotation_pattern(self):
         s = r'DEFINE ANNOTATION Test AS PATTERN "\w+"'
diff --git a/tests/test_struct/test_summary/test_summary_nodes.py b/tests/test_struct/test_summary/test_summary_nodes.py
index 2a52a3d8..2668a810 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, BEL_DEFAULT_NAMESPACE, BIOPROCESS, COMPLEX, PROTEIN
+from pybel.constants import ABUNDANCE, 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 (