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

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

```diff
@@            Coverage Diff             @@
##           master     #465      +/-   ##
==========================================
+ Coverage   78.22%   78.30%   +0.07%     
==========================================
  Files         176      176              
  Lines        9404     9443      +39     
  Branches     1362     1364       +2     
==========================================
+ Hits         7356     7394      +38     
- Misses       1761     1762       +1     
  Partials      287      287              
```


| [Impacted Files](https://codecov.io/gh/pybel/pybel/pull/465?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/pybel/dsl/node\_classes.py](https://codecov.io/gh/pybel/pybel/pull/465/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2RzbC9ub2RlX2NsYXNzZXMucHk=) | `92.12% <71.42%> (-0.51%)` | :arrow_down: |
| [src/pybel/io/triples/converters.py](https://codecov.io/gh/pybel/pybel/pull/465/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2lvL3RyaXBsZXMvY29udmVydGVycy5weQ==) | `93.77% <84.61%> (+0.23%)` | :arrow_up: |
| [src/pybel/\_\_init\_\_.py](https://codecov.io/gh/pybel/pybel/pull/465/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL19faW5pdF9fLnB5) | `100.00% <100.00%> (ø)` | |
| [src/pybel/io/\_\_init\_\_.py](https://codecov.io/gh/pybel/pybel/pull/465/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2lvL19faW5pdF9fLnB5) | `100.00% <100.00%> (ø)` | |
| [src/pybel/io/gpickle.py](https://codecov.io/gh/pybel/pybel/pull/465/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2lvL2dwaWNrbGUucHk=) | `95.65% <100.00%> (+3.65%)` | :arrow_up: |
| [src/pybel/io/indra.py](https://codecov.io/gh/pybel/pybel/pull/465/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL2lvL2luZHJhLnB5) | `47.50% <100.00%> (+4.25%)` | :arrow_up: |
| [src/pybel/manager/models.py](https://codecov.io/gh/pybel/pybel/pull/465/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL21hbmFnZXIvbW9kZWxzLnB5) | `88.59% <100.00%> (+0.04%)` | :arrow_up: |
| [src/pybel/parser/parse\_concept.py](https://codecov.io/gh/pybel/pybel/pull/465/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL3BhcnNlci9wYXJzZV9jb25jZXB0LnB5) | `96.05% <100.00%> (ø)` | |
| [src/pybel/parser/parse\_metadata.py](https://codecov.io/gh/pybel/pybel/pull/465/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL3BhcnNlci9wYXJzZV9tZXRhZGF0YS5weQ==) | `94.11% <100.00%> (ø)` | |
| [src/pybel/parser/utils.py](https://codecov.io/gh/pybel/pybel/pull/465/diff?src=pr&el=tree#diff-c3JjL3B5YmVsL3BhcnNlci91dGlscy5weQ==) | `100.00% <100.00%> (ø)` | |
| ... and [2 more](https://codecov.io/gh/pybel/pybel/pull/465/diff?src=pr&el=tree-more) | |

------

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

diff --git a/src/pybel/tokens.py b/src/pybel/tokens.py
index 2a43df6b..9c34502d 100644
--- a/src/pybel/tokens.py
+++ b/src/pybel/tokens.py
@@ -2,7 +2,9 @@
 
 """This module helps handle node data dictionaries."""
 
-from typing import List
+from typing import Any, List, Mapping, Union
+
+from pyparsing import ParseResults
 
 from .constants import (
     CONCEPT, FRAGMENT, FRAGMENT_DESCRIPTION, FRAGMENT_START, FRAGMENT_STOP, FUNCTION, FUSION, FUSION_MISSING,
@@ -34,6 +36,8 @@ def parse_result_to_dsl(tokens) -> BaseEntity:
         return _variant_po_to_dict(tokens)
 
     elif MEMBERS in tokens:
+        if CONCEPT in tokens:
+            return _list_po_with_concept_to_dict(tokens)
         return _list_po_to_dict(tokens)
 
     elif FUSION in tokens:
@@ -196,6 +200,25 @@ def _reaction_po_to_dict(tokens) -> Reaction:
     )
 
 
+def _list_po_with_concept_to_dict(tokens: Union[ParseResults, Mapping[str, Any]]) -> ListAbundance:
+    """Convert a list parse object to a node.
+
+    :type tokens: ParseResult
+    """
+    func = tokens[FUNCTION]
+    dsl = FUNC_TO_LIST_DSL[func]
+    members = _parse_tokens_list(tokens[MEMBERS])
+
+    concept = tokens[CONCEPT]
+    return dsl(
+        members=members,
+        namespace=concept[NAMESPACE],
+        name=concept.get(NAME),
+        identifier=concept.get(IDENTIFIER),
+        xrefs=tokens.get(XREFS),
+    )
+
+
 def _list_po_to_dict(tokens) -> ListAbundance:
     """Convert a list parse object to a node.
 

pybel.tokens.parse_result_to_dsl doesn't capture 'concept' information
### Current behavior
When passing a pybel Entity to `parse_result_to_dsl()`, the 'concept' entries don't get mapped to a corresponding 'entity' parameter in the returned node. For example, when passing `{'function': 'Complex', 'members': [{}, {}], 'concept': {'namespace': 'hgnc', 'name': 'ca'}}` to the function, the BEL complex returned has an `entity` value of `None`, as shown in the following image.

![image](https://user-images.githubusercontent.com/45149156/90273727-69e91900-de2d-11ea-9b09-fbd28a0f55ac.png)

### Expected behavior
Expected the function to return an `entity` value consistent with the node schema defined in the schema sub-module.

diff --git a/tests/test_dsl.py b/tests/test_dsl.py
index 76d8a9e9..bb177dd0 100644
--- a/tests/test_dsl.py
+++ b/tests/test_dsl.py
@@ -4,6 +4,7 @@
 
 import unittest
 
+import pybel.constants as pc
 from pybel import BELGraph
 from pybel.constants import NAME
 from pybel.dsl import (
@@ -12,6 +13,7 @@ from pybel.dsl import (
 )
 from pybel.language import Entity
 from pybel.testing.utils import n
+from pybel.tokens import parse_result_to_dsl
 from pybel.utils import ensure_quotes
 
 
@@ -210,5 +212,20 @@ class TestCentralDogma(unittest.TestCase):
             Reaction([], [])
 
 
+class TestParse(unittest.TestCase):
+    """Test that :func:`parse_result_to_dsl` works correctly."""
+
+    def test_named_complex(self):
+        x = ComplexAbundance(namespace='a', identifier='b', members=[
+            Protein(namespace='c', identifier='d'),
+            Protein(namespace='c', identifier='e'),
+        ])
+
+        y = parse_result_to_dsl(dict(x))
+        self.assertIsInstance(y, ComplexAbundance)
+        self.assertIn(pc.MEMBERS, y)
+        self.assertIn(pc.CONCEPT, y)
+
+
 if __name__ == '__main__':
     unittest.main()

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
3.8
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==25.3.0
bel-resources==0.0.3
certifi==2025.1.31
charset-normalizer==3.4.1
click==8.1.8
click-plugins==1.1.1
coverage==7.6.1
exceptiongroup==1.2.2
execnet==2.1.1
greenlet==3.1.1
humanize==4.10.0
idna==3.10
importlib_resources==6.4.5
iniconfig==2.1.0
jsonschema==4.23.0
jsonschema-specifications==2023.12.1
more-itertools==10.5.0
multisplitby==0.0.1
networkx==3.1
numpy==1.24.4
packaging==24.2
pandas==2.0.3
pkgutil_resolve_name==1.3.10
pluggy==1.5.0
-e git+https://github.com/pybel/pybel.git@81f400f64cdbe56f55a310fd37054a20703e3a94#egg=pybel
pyparsing==3.1.4
pytest==8.3.5
pytest-asyncio==0.24.0
pytest-cov==5.0.0
pytest-mock==3.14.0
pytest-xdist==3.6.1
python-dateutil==2.9.0.post0
pytz==2025.2
referencing==0.35.1
requests==2.32.3
requests-file==2.1.0
rpds-py==0.20.1
six==1.17.0
SQLAlchemy==2.0.40
tabulate==0.9.0
tomli==2.2.1
tqdm==4.67.1
typing_extensions==4.13.0
tzdata==2025.2
urllib3==2.2.3
zipp==3.20.2

name: pybel
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=24.2=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==25.3.0
      - bel-resources==0.0.3
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - click==8.1.8
      - click-plugins==1.1.1
      - coverage==7.6.1
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - greenlet==3.1.1
      - humanize==4.10.0
      - idna==3.10
      - importlib-resources==6.4.5
      - iniconfig==2.1.0
      - jsonschema==4.23.0
      - jsonschema-specifications==2023.12.1
      - more-itertools==10.5.0
      - multisplitby==0.0.1
      - networkx==3.1
      - numpy==1.24.4
      - packaging==24.2
      - pandas==2.0.3
      - pkgutil-resolve-name==1.3.10
      - pluggy==1.5.0
      - pyparsing==3.1.4
      - pytest==8.3.5
      - pytest-asyncio==0.24.0
      - pytest-cov==5.0.0
      - pytest-mock==3.14.0
      - pytest-xdist==3.6.1
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - referencing==0.35.1
      - requests==2.32.3
      - requests-file==2.1.0
      - rpds-py==0.20.1
      - six==1.17.0
      - sqlalchemy==2.0.40
      - tabulate==0.9.0
      - tomli==2.2.1
      - tqdm==4.67.1
      - typing-extensions==4.13.0
      - tzdata==2025.2
      - urllib3==2.2.3
      - zipp==3.20.2
prefix: /opt/conda/envs/pybel

tests/test_dsl.py::TestParse::test_named_complex
tests/test_dsl.py::TestDSL::test_GeneFusion tests/test_dsl.py::TestDSL::test_abundance_as_bel tests/test_dsl.py::TestDSL::test_abundance_as_bel_quoted tests/test_dsl.py::TestDSL::test_add_identified_node tests/test_dsl.py::TestDSL::test_add_named_node tests/test_dsl.py::TestDSL::test_add_robust_node tests/test_dsl.py::TestDSL::test_as_tuple tests/test_dsl.py::TestDSL::test_complex_with_name tests/test_dsl.py::TestDSL::test_empty_complex tests/test_dsl.py::TestDSL::test_empty_composite tests/test_dsl.py::TestDSL::test_gene_fusion_missing_explicit tests/test_dsl.py::TestDSL::test_gene_fusion_missing_implicit tests/test_dsl.py::TestDSL::test_missing_information tests/test_dsl.py::TestDSL::test_str_has_both tests/test_dsl.py::TestDSL::test_str_has_identifier tests/test_dsl.py::TestCentralDogma::test_get_parent tests/test_dsl.py::TestCentralDogma::test_list_abundance_has_contents tests/test_dsl.py::TestCentralDogma::test_reaction_has_contents tests/test_dsl.py::TestCentralDogma::test_with_variants tests/test_dsl.py::TestCentralDogma::test_with_variants_list
src/pybel/tokens.py
src/pybel/tokens.py