pax_global_header00006660000000000000000000000064135635256410014524gustar00rootroot0000000000000052 comment=e410d364d3ed53f1b6daa0d2e1a88d4766fb6b20 bel-resources-0.0.3/000077500000000000000000000000001356352564100142765ustar00rootroot00000000000000bel-resources-0.0.3/.bumpversion.cfg000066400000000000000000000013541356352564100174110ustar00rootroot00000000000000[bumpversion] current_version = 0.0.3 commit = True tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(?:-(?P[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+(?P[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))? serialize = {major}.{minor}.{patch}-{release}+{build} {major}.{minor}.{patch}+{build} {major}.{minor}.{patch}-{release} {major}.{minor}.{patch} [bumpversion:part:release] optional_value = production first_value = dev values = dev production [bumpverion:part:build] values = [0-9A-Za-z-]+ [bumpversion:file:setup.cfg] search = version = {current_version} replace = version = {new_version} [bumpversion:file:src/bel_resources/constants.py] search = VERSION = '{current_version}' replace = VERSION = '{new_version}' bel-resources-0.0.3/.flake8000066400000000000000000000010511356352564100154460ustar00rootroot00000000000000######################### # Flake8 Configuration # # (.flake8) # ######################### [flake8] exclude = *.egg-info, *.pyc, .cache, .eggs .git, .tox, __pycache__, build, dist, docs/source/conf.py, format = ${cyan}%(path)s${reset}:${yellow_bold}%(row)d${reset}:${green_bold}%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s ignore = max-line-length = 120 # flake8-import-order application-import-names = bel_resources tests import-order-style = pycharm # mccabe max-complexity = 10 bel-resources-0.0.3/.gitignore000066400000000000000000000023011356352564100162620ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # IDEA .idea bel-resources-0.0.3/.travis.yml000066400000000000000000000012351356352564100164100ustar00rootroot00000000000000sudo: false cache: pip language: python python: - 3.6 - 3.5 stages: - lint - docs - test env: - TOXENV=py jobs: include: # lint stage - stage: lint env: TOXENV=manifest - env: TOXENV=flake8 - env: TOXENV=pyroma - env: TOXENV=xenon - env: TOXENV=mypy # docs stage - stage: docs env: TOXENV=doc8 - env: TOXENV=readme matrix: allow_failures: - env: TOXENV=mypy - env: TOXENV=xenon install: - sh -c 'if [ "$TOXENV" = "py" ]; then pip install tox codecov coverage; else pip install tox; fi' script: - tox after_success: - sh -c 'if [ "$TOXENV" = "py" ]; then tox -e coverage-report; codecov; fi' bel-resources-0.0.3/LICENSE000066400000000000000000000020641356352564100153050ustar00rootroot00000000000000MIT License Copyright (c) 2019 Charles Tapley Hoyt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. bel-resources-0.0.3/MANIFEST.in000066400000000000000000000004521356352564100160350ustar00rootroot00000000000000graft src graft tests recursive-include docs/source *.py recursive-include docs/source *.rst include docs/Makefile global-exclude *.py[cod] __pycache__ *.so *.dylib .DS_Store *.gpickle exclude .bumpversion.cfg .dockerignore Dockerfile include *.rst *.txt *.yml LICENSE *.ini .coveragerc .flake8 bel-resources-0.0.3/README.rst000066400000000000000000000024441356352564100157710ustar00rootroot00000000000000BEL Resources |build| |coverage| |zenodo| ========================================= Utilities for BEL resource files. Installation |pypi_version| |python_versions| |pypi_license| ------------------------------------------------------------ Install from `PyPI `_ with: .. code-block:: bash $ pip install bel-resources or get the latest code from `GitHub `_ with: .. code-block:: bash $ git clone https://github.com/pybel/bel-resources.git $ cd bel-resources $ pip install -e . .. |build| image:: https://travis-ci.com/pybel/bel-resources.svg?branch=master :target: https://travis-ci.com/pybel/bel-resources .. |coverage| image:: https://codecov.io/gh/pybel/bel-resources/branch/master/graph/badge.svg :target: https://codecov.io/gh/pybel/bel-resources .. |python_versions| image:: https://img.shields.io/pypi/pyversions/bel-resources.svg :alt: Stable Supported Python Versions .. |pypi_version| image:: https://img.shields.io/pypi/v/bel-resources.svg :alt: Current version on PyPI .. |pypi_license| image:: https://img.shields.io/pypi/l/bel-resources.svg :alt: MIT License .. |zenodo| image:: https://zenodo.org/badge/164254633.svg :target: https://zenodo.org/badge/latestdoi/164254633 bel-resources-0.0.3/setup.cfg000066400000000000000000000035631356352564100161260ustar00rootroot00000000000000########################## # Setup.py Configuration # ########################## [metadata] name = bel_resources version = 0.0.3 description = Utilities for BEL resource files. long_description = file: README.rst license = MIT license_file = LICENSE url = https://github.com/cthoyt/bel-resources download_url = https://github.com/cthoyt/bel-resources/releases project_urls = Bug Tracker = https://github.com/cthoyt/bel-resources/issues Source Code = https://github.com/cthoyt/bel-resources author = Charles Tapley Hoyt author_email = cthoyt@gmail.com maintainer = Charles Tapley Hoyt maintainer_email = cthoyt@gmail.com classifiers = Development Status :: 1 - Planning Intended Audience :: Developers License :: OSI Approved :: MIT License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3 :: Only keywords = Biological Expression Language BEL Semantic Web Networks Biology Systems Biology Ontologies [options] install_requires = requests requests_file multisplitby python_requires = >=3.5 tests_require = tox packages = find: package_dir = = src zip_safe = true [options.packages.find] where = src [options.extras_require] obo = obonet [options.entry_points] console_scripts = bel-resources = bel_resources.cli:main ###################### # Doc8 Configuration # # (doc8.ini) # ###################### [doc8] max-line-length = 120 ########################## # Coverage Configuration # # (.coveragerc) # ########################## [coverage:run] branch = True source = bel-resources [coverage:paths] source = src/bel_resources .tox/*/lib/python*/site-packages/bel_resources [coverage:report] show_missing = True bel-resources-0.0.3/setup.py000066400000000000000000000001631356352564100160100ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Setup module.""" from setuptools import setup if __name__ == '__main__': setup() bel-resources-0.0.3/src/000077500000000000000000000000001356352564100150655ustar00rootroot00000000000000bel-resources-0.0.3/src/bel_resources/000077500000000000000000000000001356352564100177215ustar00rootroot00000000000000bel-resources-0.0.3/src/bel_resources/__init__.py000066400000000000000000000011211356352564100220250ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Utilities for downloading, reading, and writing BEL script, namespace files, and annotation files.""" from .exc import EmptyResourceError, InvalidResourceError, MissingResourceError, ResourceError # noqa: F401 from .read_document import split_file_to_annotations_and_definitions # noqa: F401 from .read_utils import get_bel_resource, get_lines, parse_bel_resource # noqa: F401 from .write_annotation import write_annotation # noqa: F401 from .write_document import make_knowledge_header # noqa: F401 from .write_namespace import write_namespace # noqa: F401 bel-resources-0.0.3/src/bel_resources/__main__.py000066400000000000000000000005151356352564100220140ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Entrypoint module, in case you use ``python3 -m bel_resources``. Why does this file exist, and why ``__main__``? For more info, read: - https://www.python.org/dev/peps/pep-0338/ - https://docs.python.org/3/using/cmdline.html#cmdoption-m """ from .cli import main if __name__ == '__main__': main() bel-resources-0.0.3/src/bel_resources/cli.py000066400000000000000000000072211356352564100210440ustar00rootroot00000000000000# -*- coding: utf-8 -*- """A command line interface for BEL Resources. Why does this file exist, and why not put this in ``__main__``? You might be tempted to import things from ``__main__`` later, but that will cause problems--the code will get executed twice: - When you run `python3 -m bel_resources` python will execute ``__main__.py`` as a script. That means there won't be any ``bel_resources.__main__`` in ``sys.modules``. - When you import __main__ it will get executed again (as a module) because there's no ``bel_resources.__main__`` in ``sys.modules``. Also see (1) from http://click.pocoo.org/5/setuptools/#setuptools-integration """ import sys from getpass import getuser import click from bel_resources import parse_bel_resource, write_annotation, write_namespace from bel_resources.constants import NAMESPACE_DOMAIN_OTHER @click.group() @click.version_option() def main(): """Command Line Interface for BEL Resources.""" @main.group() def namespace(): """Namespace file utilities.""" @namespace.command() @click.argument('name') @click.argument('keyword') @click.argument('domain') @click.argument('citation') @click.option('--author', default=getuser()) @click.option('--description') @click.option('--species') @click.option('--version') @click.option('--contact') @click.option('--license') @click.option('--values', default=sys.stdin, help="A file containing the list of names") @click.option('--output', type=click.File('w'), default=sys.stdout) def write(name, keyword, domain, citation, author, description, species, version, contact, license, values, output): """Build a namespace from items.""" write_namespace( name, keyword, domain, author, citation, values, namespace_description=description, namespace_species=species, namespace_version=version, author_contact=contact, author_copyright=license, file=output, ) @namespace.command() @click.option('-f', '--file', type=click.File('r'), default=sys.stdin, help="Path to input BEL Namespace file") @click.option('-o', '--output', type=click.File('w'), default=sys.stdout, help="Path to output converted BEL Annotation file") def convert_to_annotation(file, output): """Convert a namespace file to an annotation file.""" resource = parse_bel_resource(file) write_annotation( keyword=resource['Namespace']['Keyword'], values={k: '' for k in resource['Values']}, citation_name=resource['Citation']['NameString'], description=resource['Namespace']['DescriptionString'], file=output, ) @main.group() def annotation(): """Annotation file utilities.""" @annotation.command() @click.option('-f', '--file', type=click.File('r'), default=sys.stdin, help="Path to input BEL Namespace file") @click.option('-o', '--output', type=click.File('w'), default=sys.stdout, help="Path to output converted BEL Namespace file") @click.option('--keyword', help="Set custom keyword. useful if the annotation keyword is too long") @click.option('--author', default=getuser()) def convert_to_namespace(file, output, keyword, author): """Convert an annotation file to a namespace file.""" resource = parse_bel_resource(file) write_namespace( namespace_keyword=(keyword or resource['AnnotationDefinition']['Keyword']), namespace_name=resource['AnnotationDefinition']['Keyword'], namespace_description=resource['AnnotationDefinition']['DescriptionString'], author_name=author, namespace_domain=NAMESPACE_DOMAIN_OTHER, values=resource['Values'], citation_name=resource['Citation']['NameString'], file=output, ) bel-resources-0.0.3/src/bel_resources/constants.py000066400000000000000000000022761356352564100223160ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Constants for reading and writing BEL script, namespace files, and annotation files.""" import re from typing import Iterable VERSION = '0.0.3' METADATA_LINE_RE = re.compile(r"(SET\s+DOCUMENT|DEFINE\s+NAMESPACE|DEFINE\s+ANNOTATION)") NAMESPACE_URL_FMT = 'DEFINE NAMESPACE {} AS URL "{}"' NAMESPACE_PATTERN_FMT = 'DEFINE NAMESPACE {} AS PATTERN "{}"' ANNOTATION_URL_FMT = 'DEFINE ANNOTATION {} AS URL "{}"' ANNOTATION_PATTERN_FMT = 'DEFINE ANNOTATION {} AS PATTERN "{}"' ANNOTATION_LIST_FMT = 'DEFINE ANNOTATION {} AS LIST {{{}}}' def format_annotation_list(annotation: str, values: Iterable[str]) -> str: """Generate an annotation list definition.""" return ANNOTATION_LIST_FMT.format(annotation, ', '.join('"{}"'.format(e) for e in sorted(values))) NAMESPACE_DOMAIN_BIOPROCESS = 'BiologicalProcess' NAMESPACE_DOMAIN_CHEMICAL = 'Chemical' NAMESPACE_DOMAIN_GENE = 'Gene and Gene Products' NAMESPACE_DOMAIN_OTHER = 'Other' #: The valid namespace types #: .. seealso:: https://wiki.openbel.org/display/BELNA/Custom+Namespaces NAMESPACE_DOMAIN_TYPES = { NAMESPACE_DOMAIN_BIOPROCESS, NAMESPACE_DOMAIN_CHEMICAL, NAMESPACE_DOMAIN_GENE, NAMESPACE_DOMAIN_OTHER } bel-resources-0.0.3/src/bel_resources/exc.py000066400000000000000000000023031356352564100210500ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Exceptions for downloading, reading, and writing BEL script, namespace files, and annotation files.""" class ResourceError(ValueError): """A base class for resource errors.""" def __init__(self, location: str): # noqa: D107 """Initialize the ResourceError. :param location: The URL location of the BEL resource """ super().__init__(location) @property def location(self): # noqa: D401 """The URL location of the BEL resource.""" return self.args[0] class MissingResourceError(ResourceError): """Raised when trying to download a file that doesn't exist anymore.""" def __str__(self): # noqa: D105 return "Can't locate resource: {}".format(self.location) class InvalidResourceError(ResourceError): """Raised when downloading a file that is not actually a BEL resource file.""" def __str__(self): # noqa: D105 return 'URL does not point to a BEL resource: {}'.format(self.location) class EmptyResourceError(ResourceError): """Raised when downloading an empty file.""" def __str__(self): # noqa: D105 return 'Downloaded empty resource at {}'.format(self.location) bel-resources-0.0.3/src/bel_resources/github.py000066400000000000000000000031021356352564100215510ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Tools for BEL namespaces on GitHub.""" import sys import requests FILE_API_URL = 'https://api.github.com/repos/{owner}/{repo}/commits?path={path}' RAW_URL = 'https://raw.githubusercontent.com/{owner}/{repo}/{sha}/{path}' def get_github_hash(owner: str, repo: str, path: str) -> str: """Get the SHA hash corresponding to the most recent update to the BEL namespace file on a GitHub repository.""" url = FILE_API_URL.format(owner=owner, repo=repo, path=path.lstrip('/')) res = requests.get(url) res_json = res.json() most_recent_commit = res_json[0] return most_recent_commit['sha'] def get_github_url(owner: str, repo: str, path: str) -> str: """Get the URL corresponding to the most recent update to the BEL namespace file on a GitHub repository.""" sha = get_github_hash(owner, repo, path) return RAW_URL.format(owner=owner, repo=repo, sha=sha, path=path.lstrip('/')) def get_conso_names_url() -> str: """Get the URL for the most recent version of Curation of Neurodegeneration Supporting Ontology (CONSO) names.""" return get_github_url('pharmacome', 'conso', 'export/conso-names.belns') def get_conso_identifiers_url() -> str: """Get the URL for the most recent version of CONSO identifiers.""" return get_github_url('pharmacome', 'conso', 'export/conso.belns') def get_famplex_url() -> str: """Get the URL for the most recent version of FamPlex names.""" return get_github_url('sorgerlab', 'famplex', 'export/famplex.belns') if __name__ == '__main__': print(get_github_hash(*sys.argv[1:4])) bel-resources-0.0.3/src/bel_resources/obo.py000066400000000000000000000066751356352564100210700ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Utilities for handling OBO.""" from typing import Callable, Optional, TextIO, Union import networkx as nx import obonet from tqdm import tqdm from .write_annotation import write_annotation from .write_namespace import write_namespace __all__ = [ 'convert_obo_to_belns', 'convert_obo_graph_to_belns', 'convert_obo_to_belanno', 'convert_obo_graph_to_belanno', ] EncodingFunction = Callable[[nx.MultiDiGraph, str], str] def convert_obo_to_belns( url: str, path: str, use_names: bool = False, encoding: Union[str, EncodingFunction] = None, ) -> None: """Convert an OBO file to a BEL namespace.""" graph = obonet.read_obo(url) with open(path, 'w') as file: convert_obo_graph_to_belns(graph, file=file, use_names=use_names, encoding=encoding) def convert_obo_graph_to_belns( graph: nx.MultiDiGraph, file: Optional[TextIO] = None, use_names: bool = False, encoding: Union[None, str, EncodingFunction] = None, process_identifiers: Optional[Callable[[str], str]] = None, ) -> None: """Convert a graph from :mod:`obonet` to a BELNS file.""" name = graph.graph['name'] if name.endswith('.obo'): name = name[:-len('.obo')] ontology = graph.graph['ontology'] if encoding is None: encoding = '' if isinstance(encoding, str): x = encoding def encoding(_, __) -> str: """Encode a BEL node.""" return x if use_names: values = { data['name']: encoding(graph, node) for node, data in graph.nodes(data=True) if node.upper().startswith(ontology.upper() + ':') } elif process_identifiers is not None: values = { process_identifiers(node): encoding(graph, node) for node in graph if node.upper().startswith(ontology.upper() + ':') } else: values = { node: encoding(graph, node) for node in graph if node.upper().startswith(ontology.upper() + ':') } if not values: c = sum(node.upper().startswith(ontology.upper() + ':') for node in graph) raise ValueError('No values for {} found. Shuld be: {}'.format(ontology, c)) write_namespace( values=values, namespace_name=name, namespace_keyword=ontology, namespace_domain=None, namespace_version=graph.graph['data-version'], file=file, ) def convert_obo_to_belanno(url: str, path: str): """Convert an OBO file to a BEL annotation.""" graph = obonet.read_obo(url) with open(path, 'w') as file: convert_obo_graph_to_belanno(graph, file=file) def convert_obo_graph_to_belanno( graph: nx.MultiDiGraph, file: Optional[TextIO] = None, ) -> None: """Convert an OBO graph to a BEL annotation.""" keyword = graph.graph['name'] ontology = graph.graph['ontology'] values = ( (data['name'], data.get('description', '')) for node, data in tqdm(graph.nodes(data=True)) if node.upper().startswith(ontology.upper() + ':') ) write_annotation( values=values, file=file, citation_name=keyword, description=keyword, keyword=keyword, ) if __name__ == '__main__': convert_obo_to_belns( url='http://purl.obolibrary.org/obo/doid.obo', path='/Users/cthoyt/Desktop/doid-names.belns', use_names=True, encoding='O', ) bel-resources-0.0.3/src/bel_resources/read_document.py000066400000000000000000000061761356352564100231160ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Utilities for reading BEL Script.""" import logging from typing import Iterable, Iterator, Tuple from multisplitby import multi_split_by from .constants import METADATA_LINE_RE __all__ = [ 'split_file_to_annotations_and_definitions', ] log = logging.getLogger(__name__) EnumLine = Tuple[int, str] EnumLines = Iterable[EnumLine] def split_file_to_annotations_and_definitions(lines: Iterable[str]) -> Tuple[EnumLines, EnumLines, EnumLines]: """Enumerate a line iterable and splits into 3 parts.""" enum_lines = sanitize_file_lines(lines) metadata, definitions, statements = multi_split_by(enum_lines, [_predicate_1, _predicate_2]) return metadata, definitions, statements def _predicate_1(line: EnumLine) -> bool: return not line[1].startswith('SET DOCUMENT') def _predicate_2(line: EnumLine) -> bool: return not METADATA_LINE_RE.match(line[1]) def sanitize_file_lines(lines: Iterable[str]) -> EnumLines: """Enumerate a line iterator and returns the pairs of (line number, line) that are cleaned.""" line_iterator = sanitize_file_line_iter(lines) for line_number, line in line_iterator: if line.endswith('\\'): log.log(4, 'Multiline quote starting on line: %d', line_number) line = line.strip('\\').strip() next_line_number, next_line = next(line_iterator) while next_line.endswith('\\'): log.log(3, 'Extending line: %s', next_line) line += " " + next_line.strip('\\').strip() next_line_number, next_line = next(line_iterator) line += " " + next_line.strip() log.log(3, 'Final line: %s', line) elif 1 == line.count('"'): log.log(4, 'PyBEL013 Missing new line escapes [line: %d]', line_number) next_line_number, next_line = next(line_iterator) next_line = next_line.strip() while not next_line.endswith('"'): log.log(3, 'Extending line: %s', next_line) line = '{} {}'.format(line.strip(), next_line) next_line_number, next_line = next(line_iterator) next_line = next_line.strip() line = '{} {}'.format(line, next_line) log.log(3, 'Final line: %s', line) comment_loc = line.rfind(' //') if 0 <= comment_loc: line = line[:comment_loc] yield line_number, line def sanitize_file_line_iter(file: Iterable[str], note_char: str = ':') -> Iterator[EnumLine]: """Clean a line iterator by removing extra whitespace, blank lines, comment lines, and log nodes. :param file: An iterable over the lines in a BEL Script :param note_char: The character sequence denoting a special note :returns: An iterator over the line number and the lines that should be processed """ for line_number, line in enumerate(file, start=1): line = line.strip() if not line: continue if line[0] == '#': if len(line) > 1 and line[1] == note_char: log.info('NOTE: Line %d: %s', line_number, line) continue yield line_number, line bel-resources-0.0.3/src/bel_resources/read_utils.py000066400000000000000000000055321356352564100224330ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Shared utilities for reading BEL namespace and annotation files.""" import logging import os from configparser import ConfigParser from typing import Dict, Iterable, List, Tuple import requests.exceptions from .exc import EmptyResourceError, InvalidResourceError, MissingResourceError from .utils import download, is_url __all__ = [ 'parse_bel_resource', 'get_lines', 'get_bel_resource', ] log = logging.getLogger(__name__) def get_bel_resource(location: str) -> Dict: """Load, download, and parse a config file from the given url or file path. :param location: The URL or file path to a BELNS, BELANNO, or BELEQ file to download and parse :return: A config-style dictionary representing the BEL config file :raises: ResourceError """ log.debug('getting resource: %s', location) try: lines = get_lines(location) except requests.exceptions.HTTPError as e: raise MissingResourceError(location) from e try: result = parse_bel_resource(lines) except ValueError as e: raise InvalidResourceError(location) from e if not result['Values']: raise EmptyResourceError(location) return result def parse_bel_resource(lines: Iterable[str]) -> Dict: """Parse a BEL config (BELNS, BELANNO, or BELEQ) file from the given line iterator over the file. :param lines: An iterable over the lines in a BEL config file :return: A config-style dictionary representing the BEL config file """ lines = list(lines) value_line = 1 + max( index for index, line in enumerate(lines) if '[Values]' == line.strip() ) metadata_config = ConfigParser(strict=False) metadata_config.optionxform = lambda option: option metadata_config.read_file(lines[:value_line]) delimiter = metadata_config['Processing']['DelimiterString'] value_dict = dict( _get_bel_resource_kvp(line, delimiter) for line in lines[value_line:] ) res = { key: dict(values) for key, values in metadata_config.items() } res['Values'] = value_dict return res def _get_bel_resource_kvp(line: str, delimiter: str) -> Tuple[str, str]: """Get a BEL resource key/value pair.""" split_line = line.rsplit(delimiter, 1) key = split_line[0].strip() value = split_line[1].strip() if 2 == len(split_line) else None return key, value def get_lines(location: str) -> List[str]: """Get the lines from a location. :param location: The URL location to download or a file path to open. File path expands user. :raises: requests.exceptions.HTTPError """ if is_url(location): res = download(location) return list(line.decode('utf-8', errors='ignore').strip() for line in res.iter_lines()) with open(os.path.expanduser(location)) as f: return list(f) bel-resources-0.0.3/src/bel_resources/utils.py000066400000000000000000000015021356352564100214310ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Utilities for reading and writing BEL script, namespace files, and annotation files.""" import time from urllib.parse import urlparse import requests from requests_file import FileAdapter __all__ = [ 'get_iso_8601_date', 'is_url', 'download', ] def get_iso_8601_date() -> str: """Get the current date as a string in YYYYMMDD format.""" return time.strftime('%Y%m%d') def is_url(s: str) -> bool: """Check if a string is a valid URL.""" return urlparse(s).scheme != "" def download(url: str) -> requests.Response: """Download an URL or file using :py:mod:`requests`. :raises: requests.exceptions.HTTPError """ session = requests.Session() session.mount('file://', FileAdapter()) res = session.get(url) res.raise_for_status() return res bel-resources-0.0.3/src/bel_resources/write_annotation.py000066400000000000000000000104701356352564100236610ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Utilities for writing BEL annotation files.""" import time from itertools import chain from typing import Iterable, Mapping, Optional, TextIO, Tuple, Union from .utils import get_iso_8601_date from .write_utils import DATETIME_FMT, iter_author_header, iter_body, iter_citation_header, iter_properties_header __all__ = [ 'write_annotation', ] def write_annotation(keyword: str, values: Union[Iterable[Tuple[str, str]], Mapping[str, str]], citation_name: str, description: str, usage: Optional[str] = None, version: Optional[str] = None, created: Optional[str] = None, author_name: Optional[str] = None, author_copyright: Optional[str] = None, author_contact: Optional[str] = None, case_sensitive: bool = True, delimiter: str = '|', cacheable: bool = True, file: Optional[TextIO] = None, ) -> None: """Write a BEL annotation (BELANNO) to a file. :param keyword: The annotation keyword :param values: A dictionary of {name: label} or iterable of pairs of name, label :param citation_name: The citation name :param description: A description of this annotation :param usage: How to use this annotation :param version: The version of this annotation. Defaults to date in ``YYYYMMDD`` format. :param created: The annotation's public timestamp, ISO 8601 datetime :param author_name: The author's name :param author_copyright: The copyright information for this annotation. Defaults to ``Other/Proprietary`` :param author_contact: The contact information for the author of this annotation. :param case_sensitive: Should this config file be interpreted as case-sensitive? :param delimiter: The delimiter between names and labels in this config file :param cacheable: Should this config file be cached? :param file: A writable file or file-like """ if isinstance(values, Mapping): values = values.items() elif not isinstance(values, Iterable): raise TypeError('values are not iterable: {}'.format(values)) nominal_lines = iter_annotation_nominal( keyword, description=description, usage=usage, version=version, created=created, ) header_lines = iter_author_header( name=author_name, contact=author_contact, copyright_str=author_copyright, ) citation_lines = iter_citation_header( name=citation_name, ) property_lines = iter_properties_header( case_sensitive=case_sensitive, delimiter=delimiter, cacheable=cacheable, ) body_lines = iter_body( values=values, delimiter=delimiter, ) for line in chain(nominal_lines, header_lines, citation_lines, property_lines, body_lines): print(line, file=file) def iter_annotation_nominal(keyword: str, description: Optional[str] = None, usage: Optional[str] = None, version: Optional[str] = None, created: Optional[str] = None, ) -> Iterable[str]: """Iterate over the lines of the ``[AnnotationDefinition]`` section of a BELANNO file. :param keyword: Preferred BEL Keyword, maximum length of 8 :param description: A description of this annotation :param usage: How to use this annotation :param version: Namespace version. Defaults to date in ``YYYYMMDD`` format. :param created: Namespace public timestamp, ISO 8601 datetime :return: A iterator over the lines for the ``[AnnotationDefinition]`` section """ yield '[AnnotationDefinition]' yield 'Keyword={}'.format(keyword) yield 'TypeString={}'.format('list') yield 'VersionString={}'.format(version if version else get_iso_8601_date()) yield 'CreatedDateTime={}'.format(created if created else time.strftime(DATETIME_FMT)) if description is not None: yield 'DescriptionString={}'.format(description.strip().replace('\n', '')) if usage is not None: yield 'UsageString={}'.format(usage.strip().replace('\n', '')) yield '' bel-resources-0.0.3/src/bel_resources/write_document.py000066400000000000000000000160261356352564100233300ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Utilities for reading BEL Script.""" import time from typing import Iterable, Mapping, Optional, Set from .constants import ( ANNOTATION_PATTERN_FMT, ANNOTATION_URL_FMT, NAMESPACE_PATTERN_FMT, NAMESPACE_URL_FMT, format_annotation_list, ) __all__ = [ 'make_knowledge_header', ] def make_knowledge_header(name: str, version: Optional[str] = None, description: Optional[str] = None, authors: Optional[str] = None, contact: Optional[str] = None, copyright: Optional[str] = None, licenses: Optional[str] = None, disclaimer: Optional[str] = None, namespace_url: Optional[Mapping[str, str]] = None, namespace_patterns: Optional[Mapping[str, str]] = None, annotation_url: Optional[Mapping[str, str]] = None, annotation_patterns: Optional[Mapping[str, str]] = None, annotation_list: Optional[Mapping[str, Set[str]]] = None, ) -> Iterable[str]: """Iterate over lines for the header of a BEL document, with standard document metadata and definitions. :param name: The unique name for this BEL document :param version: The version. Defaults to current date in format ``YYYYMMDD``. :param description: A description of the contents of this document :param authors: The authors of this document :param contact: The email address of the maintainer :param copyright: Copyright information about this document :param licenses: The license applied to this document :param disclaimer: The disclaimer for this document :param namespace_url: an optional dictionary of {str name: str URL} of namespaces :param namespace_patterns: An optional dictionary of {str name: str regex} namespaces :param annotation_url: An optional dictionary of {str name: str URL} of annotations :param annotation_patterns: An optional dictionary of {str name: str regex} of regex annotations :param annotation_list: An optional dictionary of {str name: set of names} of list annotations """ yield from make_document_metadata( name=name, contact=contact, description=description, authors=authors, version=version, copyright=copyright, licenses=licenses, disclaimer=disclaimer, ) yield from make_document_namespaces( namespace_url=namespace_url, namespace_patterns=namespace_patterns, ) yield from make_document_annotations( annotation_url=annotation_url, annotation_patterns=annotation_patterns, annotation_list=annotation_list, ) yield '#' * 80 yield '#| Statements' yield '#' * 80 def make_document_metadata(name: str, version: Optional[str] = None, contact: Optional[str] = None, description: Optional[str] = None, authors: Optional[str] = None, copyright: Optional[str] = None, licenses: Optional[str] = None, disclaimer: Optional[str] = None, ) -> Iterable[str]: """Iterate over the lines for the document metadata section of a BEL document. :param name: The unique name for this BEL document :param version: The version. Defaults to the current date in ``YYYYMMDD`` format. :param description: A description of the contents of this document :param authors: The authors of this document :param contact: The email address of the maintainer :param copyright: Copyright information about this document :param licenses: The license applied to this document :param disclaimer: The disclaimer for this document """ yield '#' * 80 yield '#| Metadata' yield '#' * 80 + '\n' yield 'SET DOCUMENT Name = "{}"'.format(name) yield 'SET DOCUMENT Version = "{}"'.format(version or time.strftime('%Y%m%d')) if description: yield 'SET DOCUMENT Description = "{}"'.format(description.replace('\n', '')) if authors: yield 'SET DOCUMENT Authors = "{}"'.format(authors) if contact: yield 'SET DOCUMENT ContactInfo = "{}"'.format(contact) if licenses: yield 'SET DOCUMENT Licenses = "{}"'.format(licenses) if copyright: yield 'SET DOCUMENT Copyright = "{}"'.format(copyright) if disclaimer: yield 'SET DOCUMENT Disclaimer = "{}"'.format(disclaimer) yield '' def make_document_namespaces(namespace_url: Optional[Mapping[str, str]] = None, namespace_patterns: Optional[Mapping[str, str]] = None, ) -> Iterable[str]: """Iterate over lines for the namespace definitions. :param namespace_url: dictionary of {str name: str URL} of namespaces :param namespace_patterns: A dictionary of {str name: str regex} """ yield '#' * 80 yield '#| Namespaces' yield '#' * 80 if namespace_url: yield '\n# Enumerated Namespaces' yield '# ---------------------' for name, url in sorted(namespace_url.items()): yield NAMESPACE_URL_FMT.format(name, url) if namespace_patterns: yield '\n# Regular Expression Namespaces' yield '# -----------------------------' for name, pattern in sorted(namespace_patterns.items()): yield NAMESPACE_PATTERN_FMT.format(name, pattern) yield '' def make_document_annotations(annotation_url: Optional[Mapping[str, str]] = None, annotation_patterns: Optional[Mapping[str, str]] = None, annotation_list: Optional[Mapping[str, Set[str]]] = None, ) -> Iterable[str]: """Iterate over lines for the annotation definitions. :param annotation_url: A dictionary of {str name: str URL} of annotations :param annotation_patterns: A dictionary of {str name: str regex} :param annotation_list: A dictionary of {str name: set of name str} """ if annotation_url or annotation_patterns or annotation_list: yield '#' * 80 yield '#| Annotations' yield '#' * 80 if annotation_url: yield '\n# Enumerated Annotations' yield '# ----------------------' for name, url in sorted(annotation_url.items()): yield ANNOTATION_URL_FMT.format(name, url) if annotation_patterns: yield '\n# Regular Expression Annotations' yield '# ------------------------------' for name, pattern in sorted(annotation_patterns.items()): yield ANNOTATION_PATTERN_FMT.format(name, pattern) if annotation_list: yield '\n# Locally Defined Annotations' yield '# ---------------------------' for annotation, values in sorted(annotation_list.items()): yield format_annotation_list(annotation, values) yield '' bel-resources-0.0.3/src/bel_resources/write_namespace.py000066400000000000000000000135741356352564100234530ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Utilities for writing BEL namespace files.""" import time from itertools import chain from typing import Iterable, Mapping, Optional, TextIO, Tuple, Union from .constants import NAMESPACE_DOMAIN_OTHER, NAMESPACE_DOMAIN_TYPES from .utils import get_iso_8601_date from .write_utils import DATETIME_FMT, iter_author_header, iter_body, iter_citation_header, iter_properties_header __all__ = [ 'write_namespace', ] def write_namespace( values: Union[Iterable[Tuple[str, str]], Mapping[str, str]], namespace_name: str, namespace_keyword: str, namespace_domain: Optional[str] = None, author_name: Optional[str] = None, citation_name: Optional[str] = None, namespace_description: Optional[str] = None, namespace_species: Optional[str] = None, namespace_version: Optional[str] = None, namespace_query_url: Optional[str] = None, namespace_created: Optional[str] = None, author_contact: Optional[str] = None, author_copyright: Optional[str] = None, citation_description: Optional[str] = None, citation_url: Optional[str] = None, citation_version: Optional[str] = None, citation_date: Optional[str] = None, case_sensitive: bool = True, delimiter: str = '|', cacheable: bool = True, file: Optional[TextIO] = None, ) -> None: """Write a BEL namespace (BELNS) to a file. :param values: A dictionary of values to their encodings or iterable of pairs of values and their encodings :param namespace_name: The namespace nam :param namespace_keyword: Preferred BEL Keyword, maximum length of 8 (corresponds to MIRIAM namespace) :param namespace_domain: One of: :data:`pybel.constants.NAMESPACE_DOMAIN_BIOPROCESS`, :data:`pybel.constants.NAMESPACE_DOMAIN_CHEMICAL`, :data:`pybel.constants.NAMESPACE_DOMAIN_GENE`, or :data:`pybel.constants.NAMESPACE_DOMAIN_OTHER` :param author_name: The namespace's authors :param citation_name: The name of the citation :param namespace_query_url: HTTP URL to query for details on namespace values (must be valid URL) :param namespace_description: Namespace description :param namespace_species: Comma-separated list of species taxonomy id's :param namespace_version: Namespace version :param namespace_created: Namespace public timestamp, ISO 8601 datetime :param author_contact: Namespace author's contact info/email address :param author_copyright: Namespace's copyright/license information :param citation_description: Citation description :param citation_url: URL to more citation information :param citation_version: Citation version :param citation_date: Citation publish timestamp, ISO 8601 Date :param case_sensitive: Should this config file be interpreted as case-sensitive? :param delimiter: The delimiter between names and labels in this config file :param cacheable: Should this config file be cached? :param file: A writable file or file-like """ header_lines = iter_namespace_nominal( namespace_name, namespace_keyword, namespace_domain=namespace_domain, query_url=namespace_query_url, description=namespace_description, species=namespace_species, version=namespace_version, created=namespace_created, ) author_lines = iter_author_header( author_name, contact=author_contact, copyright_str=author_copyright, ) citation_lines = iter_citation_header( citation_name, description=citation_description, url=citation_url, version=citation_version, date=citation_date, ) property_lines = iter_properties_header( case_sensitive=case_sensitive, delimiter=delimiter, cacheable=cacheable, ) body_lines = iter_body( values, delimiter=delimiter, ) for line in chain(header_lines, author_lines, citation_lines, property_lines, body_lines): print(line, file=file) def iter_namespace_nominal( name: str, keyword: str, namespace_domain: Optional[str] = None, query_url: Optional[str] = None, description: Optional[str] = None, species: Optional[str] = None, version: Optional[str] = None, created: Optional[str] = None, ) -> Iterable[str]: """Iterate over the lines of the ``[Namespace]`` section of a BELNS file. :param name: The namespace name :param keyword: Preferred BEL Keyword, maximum length of 8 :param namespace_domain: One of: :data:`pybel.constants.NAMESPACE_DOMAIN_BIOPROCESS`, :data:`pybel.constants.NAMESPACE_DOMAIN_CHEMICAL`, :data:`pybel.constants.NAMESPACE_DOMAIN_GENE`, or :data:`pybel.constants.NAMESPACE_DOMAIN_OTHER` :param query_url: HTTP URL to query for details on namespace values (must be valid URL) :param description: Namespace description :param species: Comma-separated list of species taxonomy id's :param version: Namespace version. Defaults to current date in ``YYYYMMDD`` format. :param created: Namespace public timestamp, ISO 8601 datetime. Defaults to current time. """ if namespace_domain is None: namespace_domain = NAMESPACE_DOMAIN_OTHER elif namespace_domain not in NAMESPACE_DOMAIN_TYPES: raise ValueError('Invalid domain: {}. Should be one of: {}'.format(namespace_domain, NAMESPACE_DOMAIN_TYPES)) yield '[Namespace]' yield 'Keyword={}'.format(keyword) yield 'NameString={}'.format(name) yield 'DomainString={}'.format(namespace_domain) yield 'VersionString={}'.format(version if version else get_iso_8601_date()) yield 'CreatedDateTime={}'.format(created if created else time.strftime(DATETIME_FMT)) if description: yield 'DescriptionString={}'.format(description.strip().replace('\n', '')) if species is not None: yield 'SpeciesString={}'.format(species) if query_url is not None: yield 'QueryValueURL={}'.format(query_url) yield '' bel-resources-0.0.3/src/bel_resources/write_utils.py000066400000000000000000000070101356352564100226430ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Shared utilities for writing BEL namespace and annotation files.""" import getpass from typing import Iterable, Mapping, Optional, Tuple, Union DATETIME_FMT = '%Y-%m-%dT%H:%M:%S' def iter_author_header(name: Optional[str] = None, contact: Optional[str] = None, copyright_str: Optional[str] = None, ) -> Iterable[str]: """Iterate over the lines of the ``[Author]`` section of a BELNS file. :param name: Namespace's authors :param contact: Namespace author's contact info/email address :param copyright_str: Namespace's copyright/license information. Defaults to ``Other/Proprietary`` """ yield '[Author]' yield 'NameString={}'.format(name if name is not None else getpass.getuser()) yield 'CopyrightString={}'.format('Other/Proprietary' if copyright_str is None else copyright_str) if contact is not None: yield 'ContactInfoString={}'.format(contact) yield '' def iter_citation_header(name: str, description: Optional[str] = None, url: Optional[str] = None, version: Optional[str] = None, date: Optional[str] = None, ) -> Iterable[str]: """Iterate over the lines of the ``[Citation]`` section of a BEL config file. :param name: Citation name :param description: Citation description :param url: URL to more citation information :param version: Citation version :param date: Citation publish timestamp, ISO 8601 Date """ yield '[Citation]' yield 'NameString={}'.format(name) if date is not None: yield 'PublishedDate={}'.format(date) if version is not None: yield 'PublishedVersionString={}'.format(version) if description is not None: yield 'DescriptionString={}'.format(description) if url is not None: yield 'ReferenceURL={}'.format(url) yield '' def iter_properties_header(case_sensitive: bool = True, delimiter: str = '|', cacheable: bool = True, ) -> Iterable[str]: """Iterate over the lines of the ``[Processing]`` section of a BEL config file. :param case_sensitive: Should this config file be interpreted as case-sensitive? :param delimiter: The delimiter between names and labels in this config file :param cacheable: Should this config file be cached? """ yield '[Processing]' yield 'CaseSensitiveFlag={}'.format('yes' if case_sensitive else 'no') yield 'DelimiterString={}'.format(delimiter) yield 'CacheableFlag={}'.format('yes' if cacheable else 'no') yield '' def iter_body(values: Union[Iterable[Tuple[str, str]], Mapping[str, str]], delimiter: str = '|', ) -> Iterable[str]: """Iterate over the lines of the ``[Values]`` section of a BEL resource file. :param values: A dictionary of labels to their encodings :param delimiter: The delimiter between names and labels in this config file """ if isinstance(values, Mapping): values = values.items() elif not isinstance(values, Iterable): raise TypeError('values are not iterable: {}'.format(values)) yield '[Values]' for key, value in sorted(values): if not key: continue key = str(key).strip() if not key: continue yield '{}{}{}'.format(key, delimiter, ''.join(sorted(value))) yield '' bel-resources-0.0.3/tests/000077500000000000000000000000001356352564100154405ustar00rootroot00000000000000bel-resources-0.0.3/tests/__init__.py000066400000000000000000000000671356352564100175540ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Tests for multigroupby.""" bel-resources-0.0.3/tests/constants.py000066400000000000000000000007031356352564100200260ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Testing resources for PyBEL.""" import os HERE = os.path.dirname(os.path.realpath(__file__)) RESOURCES_DIRECTORY_PATH = os.path.join(HERE, 'resources') TEST_ANNOTATION_PATH = os.path.join(RESOURCES_DIRECTORY_PATH, 'test_an_1.belanno') assert os.path.exists(TEST_ANNOTATION_PATH) TEST_NAMESPACE_EMPTY_PATH = os.path.join(RESOURCES_DIRECTORY_PATH, 'test_ns_empty.belns') assert os.path.exists(TEST_NAMESPACE_EMPTY_PATH) bel-resources-0.0.3/tests/examples.py000066400000000000000000000035351356352564100176360ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Example BEL scripts.""" __all__ = [ 'simple', ] simple = """################################################################################## # Document Properties Section SET DOCUMENT Name = "PyBEL Test Simple" SET DOCUMENT Description = "Made for testing PyBEL parsing" SET DOCUMENT Version = "1.6.0" SET DOCUMENT Copyright = "Copyright (c) Charles Tapley Hoyt. All Rights Reserved." SET DOCUMENT Authors = "Charles Tapley Hoyt" SET DOCUMENT Licenses = "WTF License" SET DOCUMENT ContactInfo = "cthoyt@gmail.com" SET DOCUMENT Project = "PyBEL Testing" ################################################################################## # Definitions Section DEFINE NAMESPACE CHEBI AS URL \ "https://owncloud.scai.fraunhofer.de/index.php/s/JsfpQvkdx3Y5EMx/download?path=chebi.belns" DEFINE NAMESPACE HGNC AS URL \ "https://owncloud.scai.fraunhofer.de/index.php/s/JsfpQvkdx3Y5EMx/download?path=hgnc-human-genes.belns" DEFINE ANNOTATION Species AS \ URL "https://owncloud.scai.fraunhofer.de/index.php/s/JsfpQvkdx3Y5EMx/download?path=species-taxonomy-id.belanno" DEFINE ANNOTATION CellLine AS \ URL "https://owncloud.scai.fraunhofer.de/index.php/s/JsfpQvkdx3Y5EMx/download?path=cell-line.belanno" ################################################################################## # Statements Section SET STATEMENT_GROUP = "Group 1" SET Citation = {"PubMed","That one article from last week","123455","2012-01-31","Example Author|Example Author2"} SET Species = "9606" SET Evidence = "Evidence 1 \ w extra notes" p(HGNC:AKT1) -> p(HGNC:EGFR) SET Evidence = "Evidence 2" SET CellLine = "10B9 cell" p(HGNC:EGFR) -| p(HGNC:FADD) p(HGNC:EGFR) =| p(HGNC:CASP8) SET Citation = {"PubMed","That other article from last week","123456"} SET Species = "10116" SET Evidence = "Evidence 3" p(HGNC:FADD) -> p(HGNC:CASP8) p(HGNC:AKT1) -- p(HGNC:CASP8) """ bel-resources-0.0.3/tests/mocks.py000066400000000000000000000027671356352564100171420ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Mocks for PyBEL testing.""" from unittest import mock from tests.constants import TEST_ANNOTATION_PATH, TEST_NAMESPACE_EMPTY_PATH __all__ = [ 'MockResponse', 'MockSession', 'mock_bel_resources', ] class MockResponse: """See http://stackoverflow.com/questions/15753390/python-mock-requests-and-the-response.""" def __init__(self, url_to_mock: str): """Build a mock for the requests Response object.""" if url_to_mock.endswith('test_an_1.belanno'): self.path = TEST_ANNOTATION_PATH elif url_to_mock.endswith('test_ns_empty.belns'): self.path = TEST_NAMESPACE_EMPTY_PATH else: raise ValueError def iter_lines(self): """Iterate the lines of the mock file.""" with open(self.path, 'rb') as file: yield from file def raise_for_status(self): """Mock raising an error, by not doing anything at all.""" class MockSession: """Patches the session object so requests can be redirected through the filesystem without rewriting BEL files.""" def mount(self, prefix, adapter): """Mock mounting an adapter by not doing anything.""" @staticmethod def get(url: str): """Mock getting a URL by returning a mock response.""" return MockResponse(url) def close(self): """Mock closing a connection by not doing anything.""" mock_bel_resources = mock.patch('bel_resources.utils.requests.Session', side_effect=MockSession) bel-resources-0.0.3/tests/resources/000077500000000000000000000000001356352564100174525ustar00rootroot00000000000000bel-resources-0.0.3/tests/resources/test_an_1.belanno000066400000000000000000000012641356352564100226720ustar00rootroot00000000000000[AnnotationDefinition] Keyword=TESTAN1 TypeString=list NameString=Test Annotations 1 for PyBEL DomainString=TestAn1 SpeciesString=all DescriptionString=Test Annotations 1 for PyBEL to make a subset of useful BEL VersionString=1.0.0 CreatedDateTime=2016-09-17T20:50:00 [Author] NameString=Charles Tapley Hoyt CopyrightString=Copyright (c) Charles Tapley Hoyt. All Rights Reserved. ContactInfoString=cthoyt@gmail.com [Citation] NameString=Test Annotations 1 for PyBEL DescriptionString=Test Annotations 1 for PyBEL to make a subset of useful BEL [Processing] CaseSensitiveFlag=no DelimiterString=| CacheableFlag=yes [Values] TestAnnot1|O TestAnnot2|O TestAnnot3|O TestAnnot4|O TestAnnot5|O bel-resources-0.0.3/tests/resources/test_ns_empty.belns000066400000000000000000000011301356352564100233670ustar00rootroot00000000000000[Namespace] Keyword=TESTNSEMPTY NameString=Test Empty Namespace for PyBEL DomainString=TestNs1 SpeciesString=all DescriptionString=Test Namespace 1 for PyBEL to make a subset of useful BEL VersionString=1.0.0 CreatedDateTime=2016-09-17T20:50:00 [Author] NameString=Charles Tapley Hoyt CopyrightString=Copyright (c) Charles Tapley Hoyt. All Rights Reserved. ContactInfoString=cthoyt@gmail.com [Citation] NameString=Test Namespace 1 for PyBEL DescriptionString=Test Namespace 1 for PyBEL to make a subset of useful BEL [Processing] CaseSensitiveFlag=no DelimiterString=| CacheableFlag=yes [Values] bel-resources-0.0.3/tests/test_bel_resources.py000066400000000000000000000165471356352564100217220ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Tests for utilities for BEL resources.""" import time import unittest from bel_resources import EmptyResourceError, get_bel_resource, split_file_to_annotations_and_definitions from bel_resources.read_document import sanitize_file_lines from bel_resources.utils import get_iso_8601_date from tests.constants import TEST_ANNOTATION_PATH, TEST_NAMESPACE_EMPTY_PATH from tests.examples import simple from tests.mocks import mock_bel_resources class TestUtils(unittest.TestCase): """Test utilities.""" def test_get_date(self): """Test getting the date.""" d = get_iso_8601_date() self.assertIsInstance(d, str) self.assertEqual(d[:4], time.strftime('%Y')) self.assertEqual(d[4:6], time.strftime('%m')) self.assertEqual(d[6:8], time.strftime('%d')) class TestBELResources(unittest.TestCase): """Test utilities for BEL resources.""" def test_raises_on_empty(self): """Test that an error is thrown if an empty resource is downloaded.""" with self.assertRaises(EmptyResourceError): get_bel_resource(TEST_NAMESPACE_EMPTY_PATH) def test_raises_on_missing(self): """Test that an error is thrown if a non-existent resource is specified.""" # TODO def test_raises_on_invalid(self): """Test that an error is thrown if the resource is malformed.""" # TODO def _help_test_annotation(self, res): expected_values = { 'TestAnnot1': 'O', 'TestAnnot2': 'O', 'TestAnnot3': 'O', 'TestAnnot4': 'O', 'TestAnnot5': 'O', } self.assertEqual(expected_values, res['Values']) def test_get_from_path(self): """Test downloading a resource from a file path.""" res = get_bel_resource(TEST_ANNOTATION_PATH) self._help_test_annotation(res) def test_get_from_url(self): """Test downloading a resource by URL.""" with mock_bel_resources: res = get_bel_resource('https://example.com/test_an_1.belanno') self._help_test_annotation(res) class TestSplitLines(unittest.TestCase): """Test splitting file into annotations and definitions.""" def test_parts(self): """Test splitting file into annotations and definitions.""" lines = simple.splitlines() docs, definitions, statements = split_file_to_annotations_and_definitions(lines) self.assertEqual(8, len(list(docs))) self.assertEqual(4, len(list(definitions))) self.assertEqual(14, len(list(statements))) class TestSanitizeLines(unittest.TestCase): """Tests for :py:func:`sanitize_file_lines`.""" def test_count(self): """Test that the right number of lines are retrieved.""" lines = simple.splitlines() lines = list(sanitize_file_lines(lines)) self.assertEqual(26, len(lines)) def _help_test_line(self, statement: str, expect: str) -> None: lines = list(sanitize_file_lines(statement.split('\n'))) self.assertEqual(1, len(lines)) line = lines[0][1] self.assertEqual(expect, line) def test_already_correct(self): """Test when the line is already correct.""" expect = statement = '''SET Evidence = "1.1.1 Easy case"''' self._help_test_line(statement, expect) def test_line_break_operator_no_whitespace(self): """Test when a backward slash break is use without whitespace.""" statement = '''SET Evidence = "3.1 Backward slash break test \\ second line"''' expect = '''SET Evidence = "3.1 Backward slash break test second line"''' self._help_test_line(statement, expect) def test_line_break_operator_with_whitespace(self): """Test when a backward slash break is use with whitespace.""" statement = '''SET Evidence = "3.2 Backward slash break test with whitespace \\ second line"''' expect = '''SET Evidence = "3.2 Backward slash break test with whitespace second line"''' self._help_test_line(statement, expect) def test_line_break_operator_multiple(self): """Test when multiple backward slash break are used.""" statement = '''SET Evidence = "3.3 Backward slash break test \\ second line \\ third line"''' expect = '''SET Evidence = "3.3 Backward slash break test second line third line"''' self._help_test_line(statement, expect) def test_missing_line_break(self): """Test when a backward slash break is omitted in a new line.""" statement = '''SET Evidence = "4.1 Malformed line breakcase second line"''' expect = '''SET Evidence = "4.1 Malformed line breakcase second line"''' self._help_test_line(statement, expect) def test_missing_line_break_2(self): """Test a real example.""" statement = '''SET Evidence = "The phosphorylation of S6K at Thr389, which is the TORC1-mediated site, was not inhibited in the SIN1-/- cells (Figure 5A)."''' expect = ( 'SET Evidence = "The phosphorylation of S6K at Thr389, which is the TORC1-mediated site, was not ' 'inhibited in the SIN1-/- cells (Figure 5A)."' ) self._help_test_line(statement, expect) def test_multiple_missing_line_breaks(self): """Test when a backward slash break is omitted in multiple new lines.""" statement = '''SET Evidence = "4.2 Malformed line breakcase second line third line"''' expect = '''SET Evidence = "4.2 Malformed line breakcase second line third line"''' self._help_test_line(statement, expect) def test_multiple_missing_line_breaks_2(self): """Test forgotten delimiters.""" s = [ 'SET Evidence = "Something', 'or other', 'or other"' ] result = list(sanitize_file_lines(s)) expect = [(1, 'SET Evidence = "Something or other or other"')] self.assertEqual(expect, result) def test_line_numbers(self): """Test the line number follow-through.""" statements = [ '# Set document-defined annotation values\n', 'SET Species = 9606', 'SET Tissue = "t-cells"', '# Create an Evidence Line for a block of BEL Statements', 'SET Evidence = "Here we show that interfereon-alpha (IFNalpha) is a potent producer \\', 'of SOCS expression in human T cells, as high expression of CIS, SOCS-1, SOCS-2, \\', 'and SOCS-3 was detectable after IFNalpha stimulation. After 4 h of stimulation \\', 'CIS, SOCS-1, and SOCS-3 had ret' ] result = list(sanitize_file_lines(statements)) expect = [ (2, 'SET Species = 9606'), (3, 'SET Tissue = "t-cells"'), (5, 'SET Evidence = "Here we show that interfereon-alpha (IFNalpha) is a potent producer of SOCS expression ' 'in human T cells, as high expression of CIS, SOCS-1, SOCS-2, and SOCS-3 was detectable after IFNalpha ' 'stimulation. After 4 h of stimulation CIS, SOCS-1, and SOCS-3 had ret') ] self.assertEqual(expect, result) def test_sanitize_comment(self): """Test that comments are sanitized.""" s = [ 'SET Evidence = "yada yada yada" //this is a comment' ] result = list(sanitize_file_lines(s)) expect = [(1, 'SET Evidence = "yada yada yada"')] self.assertEqual(expect, result) bel-resources-0.0.3/tox.ini000066400000000000000000000042661356352564100156210ustar00rootroot00000000000000[tox] envlist = coverage-clean manifest flake8 mypy xenon pyroma readme doc8 py coverage-report [testenv] commands = coverage run -p -m pytest tests {posargs} passenv = TRAVIS CI deps = coverage pytest whitelist_externals = /bin/cat /bin/cp /bin/mkdir [testenv:coverage-clean] deps = coverage skip_install = true commands = coverage erase [testenv:manifest] deps = check-manifest skip_install = true commands = check-manifest [testenv:flake8] skip_install = true deps = flake8 flake8-docstrings>=0.2.7 flake8-import-order>=0.9 pep8-naming flake8-colors commands = flake8 src/bel_resources/ tests/ setup.py [testenv:xenon] deps = xenon skip_install = true commands = xenon --max-average A --max-modules A --max-absolute B . description = Run the xenon tool to monitor code complexity. [testenv:mypy] deps = mypy skip_install = true commands = mypy --ignore-missing-imports src/bel_resources/ description = Run the mypy tool to check static typing on the project. [testenv:pyroma] deps = pygments pyroma skip_install = true commands = pyroma --min=10 . description = Run the pyroma tool to check the project's package friendliness. [testenv:readme] commands = rst-lint README.rst skip_install = true deps = restructuredtext_lint pygments [testenv:doc8] skip_install = true deps = sphinx doc8 commands = doc8 README.rst [testenv:coverage-report] deps = coverage skip_install = true commands = coverage combine coverage report #################### # Deployment tools # #################### [testenv:bumpversion] commands = bumpversion {posargs} passenv = HOME skip_install = true deps = bumpversion [testenv:build] skip_install = true deps = wheel setuptools commands = python setup.py -q sdist bdist_wheel [testenv:release] skip_install = true deps = {[testenv:build]deps} twine >= 1.5.0 commands = {[testenv:build]commands} twine upload --skip-existing dist/* [testenv:finish] skip_install = true passenv = HOME deps = {[testenv:release]deps} bumpversion commands = bumpversion release {[testenv:release]commands} git push bumpversion patch