pax_global_header00006660000000000000000000000064133453042170014514gustar00rootroot0000000000000052 comment=502bec819d8a9184cc81b987f1e3fa304d21032d ChemSpiPy-2.0.0/000077500000000000000000000000001334530421700133545ustar00rootroot00000000000000ChemSpiPy-2.0.0/.bumpversion.cfg000066400000000000000000000003771334530421700164730ustar00rootroot00000000000000[bumpversion] current_version = 2.0.0 commit = True tag = True [bumpversion:file:setup.py] [bumpversion:file:chemspipy/__init__.py] [bumpversion:file:docs/guide/install.rst] [bumpversion:file:docs/guide/advanced.rst] [bumpversion:file:docs/conf.py] ChemSpiPy-2.0.0/.gitignore000066400000000000000000000001761334530421700153500ustar00rootroot00000000000000__pycache__/ *.py[cod] /docs/_build/ /dist/ *.egg-info/ .coverage .coverage.* .cache .pytest_cache/ .ipynb_checkpoints .idea/ ChemSpiPy-2.0.0/.readthedocs.yml000066400000000000000000000002041334530421700164360ustar00rootroot00000000000000build: image: latest conda: file: environment.yml formats: - epub - pdf python: version: 3.6 pip_install: true ChemSpiPy-2.0.0/.travis.yml000066400000000000000000000006151334530421700154670ustar00rootroot00000000000000language: python python: - "2.7" - "3.5" - "3.6" env: global: - secure: "N/t4txw1k9bOUsLQWQOpzdJpMAdFFzzJqN2rWiijMJPEC9E1meoKMzFYr4kgNjInhVfkud8+3fOHZL/Ns4MLWexf1vsG1NFvrXSBuBD6MlPKYe77bb9WTmRvWfLSDg6F5BP/1uFjwebj4USN14RWlxyIgmsC1+bdCFVN2Wktg4k=" install: - pip install coveralls pytest requests six script: - coverage run --source=chemspipy -m pytest after_success: - coveralls ChemSpiPy-2.0.0/CHANGELOG.md000066400000000000000000000042611334530421700151700ustar00rootroot00000000000000# Change Log ## [v2.0.0](https://github.com/mcs07/ChemSpiPy/tree/v2.0.0) (2018-09-09) [Full Changelog](https://github.com/mcs07/ChemSpiPy/compare/v1.0.5...v2.0.0) **Implemented enhancements:** - Access "data source" information through `ChemSpider` [\#3](https://github.com/mcs07/ChemSpiPy/issues/3) - Switch to new RSC REST API [\#12](https://github.com/mcs07/ChemSpiPy/pull/12) ([mcs07](https://github.com/mcs07)) **Fixed bugs:** - Authentication problem [\#11](https://github.com/mcs07/ChemSpiPy/issues/11) **Closed issues:** - Make conda-forge recipe [\#13](https://github.com/mcs07/ChemSpiPy/issues/13) - return \# of data sources in search results [\#10](https://github.com/mcs07/ChemSpiPy/issues/10) **Merged pull requests:** - Update docs for new RSC REST API [\#14](https://github.com/mcs07/ChemSpiPy/pull/14) ([mcs07](https://github.com/mcs07)) ## [v1.0.5](https://github.com/mcs07/ChemSpiPy/tree/v1.0.5) (2017-03-29) [Full Changelog](https://github.com/mcs07/ChemSpiPy/compare/v1.0.4...v1.0.5) **Implemented enhancements:** - Switch to pytest [\#7](https://github.com/mcs07/ChemSpiPy/pull/7) ([mcs07](https://github.com/mcs07)) **Fixed bugs:** - Add support for https [\#5](https://github.com/mcs07/ChemSpiPy/issues/5) - Use https by default - fixes \#5 [\#6](https://github.com/mcs07/ChemSpiPy/pull/6) ([mcs07](https://github.com/mcs07)) **Merged pull requests:** - Improve handling of invalid tokens [\#4](https://github.com/mcs07/ChemSpiPy/pull/4) ([mcs07](https://github.com/mcs07)) ## [v1.0.4](https://github.com/mcs07/ChemSpiPy/tree/v1.0.4) (2015-06-13) [Full Changelog](https://github.com/mcs07/ChemSpiPy/compare/v1.0.3...v1.0.4) ## [v1.0.3](https://github.com/mcs07/ChemSpiPy/tree/v1.0.3) (2015-03-05) [Full Changelog](https://github.com/mcs07/ChemSpiPy/compare/v1.0.2...v1.0.3) ## [v1.0.2](https://github.com/mcs07/ChemSpiPy/tree/v1.0.2) (2015-03-04) [Full Changelog](https://github.com/mcs07/ChemSpiPy/compare/v1.0.1...v1.0.2) ## [v1.0.1](https://github.com/mcs07/ChemSpiPy/tree/v1.0.1) (2014-09-15) **Implemented enhancements:** - Fix for UTF-8 encoding error \(and some other tweaks\)... [\#2](https://github.com/mcs07/ChemSpiPy/pull/2) ([nickfyson](https://github.com/nickfyson)) ChemSpiPy-2.0.0/CONTRIBUTING.rst000066400000000000000000000040111334530421700160110ustar00rootroot00000000000000Contributing ============ .. sectionauthor:: Matt Swain Contributions of any kind are greatly appreciated! Feedback -------- The `Issue Tracker`_ is the best place to post any feature ideas, requests and bug reports. Contributing ------------ If you are able to contribute changes yourself, just fork the `source code`_ on GitHub, make changes and file a pull request. All contributions are welcome, no matter how big or small. Quick guide to contributing ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. `Fork the ChemSpiPy repository on GitHub`_, then clone your fork to your local machine:: git clone https://github.com//ChemSpiPy.git cd ChemSpiPy 2. Install the development requirements into a `conda environment`_:: conda env create -n chemspipy -f environment.yml source activate chemspipy 3. Create a new branch for your changes:: git checkout -b 4. Make your changes or additions. Ideally add some tests and ensure they pass by running:: pytest 5. Commit your changes and push to your fork on GitHub:: git add . git commit -m "" git push origin 4. `Submit a pull request`_. Tips ~~~~ - Follow the `PEP8`_ style guide. - Include docstrings as described in `PEP257`_. - Try and include tests that cover your changes. - Try to write `good commit messages`_. - Read the GitHub help page on `Using pull requests`_. .. _`Issue Tracker`: https://github.com/mcs07/ChemSpiPy/issues .. _`source code`: https://github.com/mcs07/ChemSpiPy .. _`Fork the ChemSpiPy repository on GitHub`: https://github.com/mcs07/ChemSpiPy/fork .. _`conda environment`: https://conda.io/docs/ .. _`Submit a pull request`: https://github.com/mcs07/ChemSpiPy/compare/ .. _`PEP8`: https://www.python.org/dev/peps/pep-0008 .. _`PEP257`: https://www.python.org/dev/peps/pep-0257 .. _`good commit messages`: https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html .. _`Using pull requests`: https://help.github.com/articles/using-pull-requests ChemSpiPy-2.0.0/LICENSE000066400000000000000000000021001334530421700143520ustar00rootroot00000000000000The MIT License Copyright (c) 2018 Matt Swain 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. ChemSpiPy-2.0.0/MANIFEST.in000066400000000000000000000002231334530421700151070ustar00rootroot00000000000000include *.rst include CHANGELOG.md include environment.yml include LICENSE recursive-include tests *.py recursive-include docs * prune docs/_build ChemSpiPy-2.0.0/README.rst000066400000000000000000000042001334530421700150370ustar00rootroot00000000000000ChemSpiPy ========= .. image:: https://img.shields.io/pypi/v/ChemSpiPy.svg?style=flat :target: https://pypi.python.org/pypi/ChemSpiPy .. image:: https://img.shields.io/pypi/l/ChemSpiPy.svg?style=flat :target: https://github.com/mcs07/ChemSpiPy/blob/master/LICENSE .. image:: https://img.shields.io/travis/mcs07/ChemSpiPy/master.svg?style=flat :target: https://travis-ci.org/mcs07/ChemSpiPy .. image:: https://img.shields.io/coveralls/mcs07/ChemSpiPy/master.svg?style=flat :target: https://coveralls.io/r/mcs07/ChemSpiPy?branch=master ChemSpiPy provides a way to interact with ChemSpider in Python. It allows chemical searches, chemical file downloads, depiction and retrieval of chemical properties:: >>> from chemspipy import ChemSpider >>> cs = ChemSpider('') >>> c1 = cs.get_compound(236) # Specify compound by ChemSpider ID >>> c2 = cs.search('benzene') # Search using name, SMILES, InChI, InChIKey, etc. Installation ------------ Install ChemSpiPy using conda:: conda install -c conda-forge chemspipy or using pip:: pip install chemspipy Alternatively, try one of the other `installation options`_. Documentation ------------- Full documentation is available at https://chemspipy.readthedocs.io/en/stable/. The `general documentation for the ChemSpider API`_ is also a useful resource. Contribute ---------- - Feature ideas and bug reports are welcome on the `Issue Tracker`_. - Fork the `source code`_ on GitHub, make changes and file a pull request. License ------- ChemSpiPy is licensed under the `MIT license`_. This project was originally forked from `ChemSpiPy by Cameron Neylon`_, which has been released into the public domain. .. _`installation options`: https://chemspipy.readthedocs.io/en/stable/guide/install.html .. _`source code`: https://github.com/mcs07/ChemSpiPy .. _`Issue Tracker`: https://github.com/mcs07/ChemSpiPy/issues .. _`MIT license`: https://github.com/mcs07/ChemSpiPy/blob/master/LICENSE .. _`ChemSpiPy by Cameron Neylon`: https://github.com/cameronneylon/ChemSpiPy .. _`general documentation for the ChemSpider API`: https://developer.rsc.org/compounds-v1/apis ChemSpiPy-2.0.0/chemspipy/000077500000000000000000000000001334530421700153555ustar00rootroot00000000000000ChemSpiPy-2.0.0/chemspipy/__init__.py000066400000000000000000000010661334530421700174710ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ ChemSpiPy ~~~~~~~~~ Python wrapper for the ChemSpider API. """ from __future__ import print_function from __future__ import unicode_literals from __future__ import division __author__ = 'Matt Swain' __email__ = 'm.swain@me.com' __version__ = '2.0.0' __license__ = 'MIT' from .api import ChemSpider, MOL2D, MOL3D, BOTH, ASCENDING, DESCENDING, RECORD_ID, CSID, MASS_DEFECT, MOLECULAR_WEIGHT from .api import REFERENCE_COUNT, DATASOURCE_COUNT, PUBMED_COUNT, RSC_COUNT, FIELDS from .objects import Compound from .search import Results ChemSpiPy-2.0.0/chemspipy/api.py000066400000000000000000001123261334530421700165050ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ chemspipy.api ~~~~~~~~~~~~~ Core API for interacting with ChemSpider web services. """ from __future__ import print_function from __future__ import unicode_literals from __future__ import division import base64 import logging import sys import warnings import zlib import requests from . import __version__, errors from .objects import Compound from .search import Results log = logging.getLogger(__name__) #: Default API URL. API_URL = 'https://api.rsc.org' #: Default API version. API_VERSION = 'v1' #: 2D coordinate dimensions MOL2D = '2d' #: 3D coordinate dimensions MOL3D = '3d' #: Both coordinate dimensions BOTH = 'both' #: Ascending sort direction ASCENDING = 'ascending' #: Descending sort direction DESCENDING = 'descending' #: Record ID sort order RECORD_ID = 'record_id' #: CSID sort order (same as RECORD_ID, kept for backwards compatibility) CSID = 'csid' #: Mass defect sort order MASS_DEFECT = 'mass_defect' #: Molecular weight sort order MOLECULAR_WEIGHT = 'molecular_weight' #: Reference count sort order REFERENCE_COUNT = 'reference_count' #: Datasource count sort order DATASOURCE_COUNT = 'datasource_count' #: Pubmed count sort order PUBMED_COUNT = 'pubmed_count' #: RSC count sort order RSC_COUNT = 'rsc_count' #: Map sort directions to strings required by REST API. DIRECTIONS = { ASCENDING: 'ascending', DESCENDING: 'descending' } #: Map sort orders to strings required by REST API. ORDERS = { RECORD_ID: 'recordId', CSID: 'recordId', MASS_DEFECT: 'massDefect', MOLECULAR_WEIGHT: 'molecularWeight', REFERENCE_COUNT: 'referenceCount', DATASOURCE_COUNT: 'dataSourceCount', PUBMED_COUNT: 'pubMedCount', RSC_COUNT: 'rscCount' } #: All available compound details fields. FIELDS = [ 'SMILES', 'Formula', 'AverageMass', 'MolecularWeight', 'MonoisotopicMass', 'NominalMass', 'CommonName', 'ReferenceCount', 'DataSourceCount', 'PubMedCount', 'RSCCount', 'Mol2D', 'Mol3D' ] class ChemSpider(object): """Provides access to the ChemSpider API. Usage:: >>> from chemspipy import ChemSpider >>> cs = ChemSpider('') """ def __init__(self, api_key, user_agent=None, api_url=API_URL, api_version=API_VERSION): """ :param string api_key: Your ChemSpider API key. :param string user_agent: (Optional) Identify your application to ChemSpider servers. :param string api_url: (Optional) API server. Default https://api.rsc.org. :param string api_version: (Optional) API version. Default v1. """ log.debug('Initializing ChemSpider') self.api_url = api_url self.http = requests.session() self.http.headers['User-Agent'] = user_agent if user_agent else 'ChemSpiPy/{} Python/{} '.format( __version__, sys.version.split()[0] ) self.api_key = api_key self.api_version = api_version def __repr__(self): return 'ChemSpider()' def request(self, method, api, namespace, endpoint, params=None, json=None): """Make a request to the ChemSpider API. :param string method: HTTP method. :param string api: Top-level API, e.g. compounds. :param string namespace: API namespace, e.g. filter, lookups, records, or tools. :param string endpoint: Web service endpoint URL. :param dict params: Query parameters to add to the URL. :param dict json: JSON data to send in the request body. :return: Web Service response JSON. :rtype: dict """ # Construct request URL url = '{}/{}/{}/{}/{}'.format(self.api_url, api, self.api_version, namespace, endpoint) # Set apikey header headers = {'apikey': self.api_key} log.debug('{} : {} : {} : {}'.format(url, headers, params, json)) # Make request r = self.http.request(method, url, params=params, json=json, headers=headers) # Raise exception for HTTP errors if not r.ok: err = { 400: errors.ChemSpiPyBadRequestError, 401: errors.ChemSpiPyAuthError, 404: errors.ChemSpiPyNotFoundError, 405: errors.ChemSpiPyMethodError, 413: errors.ChemSpiPyPayloadError, 429: errors.ChemSpiPyRateError, 500: errors.ChemSpiPyServerError, 503: errors.ChemSpiPyUnavailableError }.get(r.status_code, errors.ChemSpiPyHTTPError) raise err(message=r.reason, http_code=r.status_code) log.debug('Request duration: {}'.format(r.elapsed)) return r.json() def get(self, api, namespace, endpoint, params=None): """Convenience method for making GET requests. :param string api: Top-level API, e.g. compounds. :param string namespace: API namespace, e.g. filter, lookups, records, or tools. :param string endpoint: Web service endpoint URL. :param dict params: Query parameters to add to the URL. :return: Web Service response JSON. :rtype: dict """ return self.request('GET', api=api, namespace=namespace, endpoint=endpoint, params=params) def post(self, api, namespace, endpoint, json=None): """Convenience method for making POST requests. :param string api: Top-level API, e.g. compounds. :param string namespace: API namespace, e.g. filter, lookups, records, or tools. :param string endpoint: Web service endpoint URL. :param dict json: JSON data to send in the request body. :return: Web Service response content. :rtype: dict or string """ return self.request('POST', api=api, namespace=namespace, endpoint=endpoint, json=json) def get_compound(self, csid): """Return a Compound object for a given ChemSpider ID. :param string|int csid: ChemSpider ID. :return: The Compound with the specified ChemSpider ID. :rtype: :class:`~chemspipy.objects.Compound` """ return Compound(self, csid) def get_compounds(self, csids): """Return a list of Compound objects, given a list ChemSpider IDs. :param list[string|int] csids: List of ChemSpider IDs. :return: List of Compounds with the specified ChemSpider IDs. :rtype: list[:class:`~chemspipy.objects.Compound`] """ return [Compound(self, csid) for csid in csids] def search(self, query, order=None, direction=ASCENDING, raise_errors=False): """Search ChemSpider for the specified query and return the results. The accepted values for ``order`` are: :data:`~chemspipy.api.RECORD_ID`, :data:`~chemspipy.api.MASS_DEFECT`, :data:`~chemspipy.api.MOLECULAR_WEIGHT`, :data:`~chemspipy.api.REFERENCE_COUNT`, :data:`~chemspipy.api.DATASOURCE_COUNT`, :data:`~chemspipy.api.PUBMED_COUNT` or :data:`~chemspipy.api.RSC_COUNT`. :param string|int query: Search query. :param string order: (Optional) Field to sort the result by. :param string direction: (Optional) :data:`~chemspipy.api.ASCENDING` or :data:`~chemspipy.api.DESCENDING`. :param bool raise_errors: (Optional) If True, raise exceptions. If False, store on Results ``exception`` property. :return: Search Results list. :rtype: :class:`~chemspipy.search.Results` """ return Results(self, self.filter_name, (query, order, direction), raise_errors=raise_errors) def get_datasources(self): """Get the list of datasources in ChemSpider. Many other endpoints let you restrict which sources are used to lookup the requested query. Restricting the sources makes queries faster. :return: List of datasources. :rtype: list[string] """ response = self.get(api='compounds', namespace='lookups', endpoint='datasources') return response['dataSources'] def get_details(self, record_id, fields=FIELDS): """Get details for a compound record. The available fields are listed in :data:`~chemspipy.api.FIELDS`. :param int record_id: Record ID. :param list[string] fields: (Optional) List of fields to include in the result. :return: Record details. :rtype: dict """ params = {'fields': ','.join(fields)} endpoint = '{}/details'.format(record_id) response = self.get(api='compounds', namespace='records', endpoint=endpoint, params=params) return response def get_details_batch(self, record_ids, fields=FIELDS): """Get details for a list of compound records. The available fields are listed in :data:`~chemspipy.api.FIELDS`. :param list[int] record_ids: List of record IDs (up to 100). :param list[string] fields: (Optional) List of fields to include in the results. :return: List of record details. :rtype: list[dict] """ json = {'recordIds': record_ids, 'fields': fields} response = self.post(api='compounds', namespace='records', endpoint='batch', json=json) return response['records'] def get_external_references(self, record_id, datasources=None): """Get external references for a compound record. Optionally filter the results by data source. Use :meth:`~chemspipy.api.ChemSpider.get_datasources` to get the available datasources. :param int record_id: Record ID. :param list[string] datasources: (Optional) List of datasources to restrict the results to. :return: External references. :rtype: list[dict] """ params = {} if datasources is not None: params['dataSources'] = ','.join(datasources) endpoint = '{}/externalreferences'.format(record_id) response = self.get(api='compounds', namespace='records', endpoint=endpoint, params=params) return response['externalReferences'] def get_image(self, record_id): """Get image for a compound record. :param int record_id: Record ID. :return: Image. :rtype: bytes """ endpoint = '{}/image'.format(record_id) response = self.get(api='compounds', namespace='records', endpoint=endpoint) return base64.b64decode(response['image']) def get_mol(self, record_id): """Get MOLfile for a compound record. :param int record_id: Record ID. :return: MOLfile. :rtype: string """ endpoint = '{}/mol'.format(record_id) response = self.get(api='compounds', namespace='records', endpoint=endpoint) return response['sdf'] def filter_element(self, include_elements, exclude_elements=None, include_all=False, complexity=None, isotopic=None, order=None, direction=None): """Search compounds by element. Set include_all to true to only consider records that contain all of the elements in ``include_elements``, otherwise all records that contain any of the elements will be returned. A compound with a complexity of 'multiple' has more than one disconnected system in it or a metal atom or ion. The accepted values for ``order`` are: :data:`~chemspipy.api.RECORD_ID`, :data:`~chemspipy.api.MASS_DEFECT`, :data:`~chemspipy.api.MOLECULAR_WEIGHT`, :data:`~chemspipy.api.REFERENCE_COUNT`, :data:`~chemspipy.api.DATASOURCE_COUNT`, :data:`~chemspipy.api.PUBMED_COUNT` or :data:`~chemspipy.api.RSC_COUNT`. :param list[string] include_elements: List of up to 15 elements to search for compounds containing. :param list[string] exclude_elements: List of up to 100 elements to exclude compounds containing. :param bool include_all: (Optional) Whether to only include compounds that have all include_elements. :param string complexity: (Optional) 'any', 'single', or 'multiple' :param string isotopic: (Optional) 'any', 'labeled', or 'unlabeled'. :param string order: (Optional) Field to sort the result by. :param string direction: (Optional) :data:`~chemspipy.api.ASCENDING` or :data:`~chemspipy.api.DESCENDING`. :return: Query ID that may be passed to ``filter_status`` and ``filter_results``. :rtype: string """ json = { 'includeElements': include_elements, 'excludeElements': exclude_elements, 'options': {'includeAll': include_all, 'complexity': complexity, 'isotopic': isotopic}, 'orderBy': ORDERS.get(order), 'orderDirection': DIRECTIONS.get(direction) } response = self.post(api='compounds', namespace='filter', endpoint='element', json=json) return response['queryId'] def filter_formula(self, formula, datasources=None, order=None, direction=None): """Search compounds by formula. Optionally filter the results by data source. Use :meth:`~chemspipy.api.ChemSpider.get_datasources` to get the available datasources. The accepted values for ``order`` are: :data:`~chemspipy.api.RECORD_ID`, :data:`~chemspipy.api.MASS_DEFECT`, :data:`~chemspipy.api.MOLECULAR_WEIGHT`, :data:`~chemspipy.api.REFERENCE_COUNT`, :data:`~chemspipy.api.DATASOURCE_COUNT`, :data:`~chemspipy.api.PUBMED_COUNT` or :data:`~chemspipy.api.RSC_COUNT`. :param string formula: Molecular formula. :param list[string] datasources: (Optional) List of datasources to restrict the results to. :param string order: (Optional) Field to sort the result by. :param string direction: (Optional) :data:`~chemspipy.api.ASCENDING` or :data:`~chemspipy.api.DESCENDING`. :return: Query ID that may be passed to ``filter_status`` and ``filter_results``. :rtype: string """ json = { 'formula': formula, 'dataSources': datasources, 'orderBy': ORDERS.get(order), 'orderDirection': DIRECTIONS.get(direction) } response = self.post(api='compounds', namespace='filter', endpoint='formula', json=json) return response['queryId'] def filter_formula_batch(self, formulas, datasources=None, order=None, direction=None): """Search compounds with a list of formulas. Optionally filter the results by data source. Use :meth:`~chemspipy.api.ChemSpider.get_datasources` to get the available datasources. The accepted values for ``order`` are: :data:`~chemspipy.api.RECORD_ID`, :data:`~chemspipy.api.MASS_DEFECT`, :data:`~chemspipy.api.MOLECULAR_WEIGHT`, :data:`~chemspipy.api.REFERENCE_COUNT`, :data:`~chemspipy.api.DATASOURCE_COUNT`, :data:`~chemspipy.api.PUBMED_COUNT` or :data:`~chemspipy.api.RSC_COUNT`. :param list[string] formulas: Molecular formula. :param list[string] datasources: (Optional) List of datasources to restrict the results to. :param string order: (Optional) Field to sort the result by. :param string direction: (Optional) :data:`~chemspipy.api.ASCENDING` or :data:`~chemspipy.api.DESCENDING`. :return: Query ID that may be passed to ``filter_formula_batch_status`` and ``filter_formula_batch_results``. :rtype: string """ json = { 'formulas': formulas, 'dataSources': datasources, 'orderBy': ORDERS.get(order), 'orderDirection': DIRECTIONS.get(direction) } response = self.post(api='compounds', namespace='filter', endpoint='formula/batch', json=json) return response['queryId'] def filter_formula_batch_status(self, query_id): """Get formula batch filter status using a query ID that was returned by a previous filter request. :param string query_id: Query ID from a previous formula batch filter request. :return: Status dict with 'status', 'count', and 'message' fields. :rtype: dict """ endpoint = 'formula/batch/{}/status'.format(query_id) response = self.get(api='compounds', namespace='filter', endpoint=endpoint) return response def filter_formula_batch_results(self, query_id): """Get formula batch filter results using a query ID that was returned by a previous filter request. Each result is a dict containing a ``formula`` key and a ``results`` key. :param string query_id: Query ID from a previous formula batch filter request. :return: List of results. :rtype: list[dict] """ endpoint = 'formula/batch/{}/results'.format(query_id) response = self.get(api='compounds', namespace='filter', endpoint=endpoint) return response['batchResults'] def filter_inchi(self, inchi): """Search compounds by InChI. :param string inchi: InChI. :return: Query ID that may be passed to ``filter_status`` and ``filter_results``. :rtype: string """ json = {'inchi': inchi} response = self.post(api='compounds', namespace='filter', endpoint='inchi', json=json) return response['queryId'] def filter_inchikey(self, inchikey): """Search compounds by InChIKey. :param string inchikey: InChIKey. :return: Query ID that may be passed to ``filter_status`` and ``filter_results``. :rtype: string """ json = {'inchikey': inchikey} response = self.post(api='compounds', namespace='filter', endpoint='inchikey', json=json) return response['queryId'] def filter_intrinsicproperty(self, formula=None, molecular_weight=None, nominal_mass=None, average_mass=None, monoisotopic_mass=None, molecular_weight_range=None, nominal_mass_range=None, average_mass_range=None, monoisotopic_mass_range=None, complexity=None, isotopic=None, order=None, direction=None): """Search compounds by intrinsic property, such as formula and mass. At least one of formula, molecular_weight, nominal_mass, average_mass, monoisotopic_mass must be specified. A compound with a complexity of 'multiple' has more than one disconnected system in it or a metal atom or ion. The accepted values for ``order`` are: :data:`~chemspipy.api.RECORD_ID`, :data:`~chemspipy.api.MASS_DEFECT`, :data:`~chemspipy.api.MOLECULAR_WEIGHT`, :data:`~chemspipy.api.REFERENCE_COUNT`, :data:`~chemspipy.api.DATASOURCE_COUNT`, :data:`~chemspipy.api.PUBMED_COUNT` or :data:`~chemspipy.api.RSC_COUNT`. :param string formula: Molecular formula. :param float molecular_weight: Molecular weight. :param float nominal_mass: Nominal mass. :param float average_mass: Average mass. :param float monoisotopic_mass: Monoisotopic mass. :param float molecular_weight_range: Molecular weight range. :param float nominal_mass_range: Nominal mass range. :param float average_mass_range: Average mass range. :param float monoisotopic_mass_range: Monoisotopic mass range. :param string complexity: (Optional) 'any', 'single', or 'multiple' :param string isotopic: (Optional) 'any', 'labeled', or 'unlabeled'. :param string order: (Optional) Field to sort the result by. :param string direction: (Optional) :data:`~chemspipy.api.ASCENDING` or :data:`~chemspipy.api.DESCENDING`. :return: Query ID that may be passed to ``filter_status`` and ``filter_results``. :rtype: string """ json = { 'formula': formula, 'options': {'complexity': complexity, 'isotopic': isotopic}, 'orderBy': ORDERS.get(order), 'orderDirection': DIRECTIONS.get(direction) } if molecular_weight is not None and molecular_weight_range is not None: json['molecularWeight'] = {'mass': molecular_weight, 'range': molecular_weight_range} if nominal_mass is not None and nominal_mass_range is not None: json['nominalMass'] = {'mass': nominal_mass, 'range': nominal_mass_range} if average_mass is not None and average_mass_range is not None: json['averageMass'] = {'mass': average_mass, 'range': average_mass_range} if monoisotopic_mass is not None and monoisotopic_mass_range is not None: json['monoisotopicMass'] = {'mass': monoisotopic_mass, 'range': monoisotopic_mass_range} response = self.post(api='compounds', namespace='filter', endpoint='intrinsicproperty', json=json) return response['queryId'] def filter_mass(self, mass, mass_range, datasources=None, order=None, direction=None): """Search compounds by mass. Filter to compounds within ``mass_range`` of the given ``mass``. Optionally filter the results by data source. Use :meth:`~chemspipy.api.ChemSpider.get_datasources` to get the available datasources. The accepted values for ``order`` are: :data:`~chemspipy.api.RECORD_ID`, :data:`~chemspipy.api.MASS_DEFECT`, :data:`~chemspipy.api.MOLECULAR_WEIGHT`, :data:`~chemspipy.api.REFERENCE_COUNT`, :data:`~chemspipy.api.DATASOURCE_COUNT`, :data:`~chemspipy.api.PUBMED_COUNT` or :data:`~chemspipy.api.RSC_COUNT`. :param float mass: Mass between 1 and 11000 Atomic Mass Units. :param float mass_range: Mass range between 0.0001 and 100 Atomic Mass Units. :param list[string] datasources: (Optional) List of datasources to restrict the results to. :param string order: (Optional) Field to sort the result by. :param string direction: (Optional) :data:`~chemspipy.api.ASCENDING` or :data:`~chemspipy.api.DESCENDING`. :return: Query ID that may be passed to ``filter_status`` and ``filter_results``. :rtype: string """ json = { 'mass': mass, 'range': mass_range, 'dataSources': datasources, 'orderBy': ORDERS.get(order), 'orderDirection': DIRECTIONS.get(direction) } response = self.post(api='compounds', namespace='filter', endpoint='mass', json=json) return response['queryId'] def filter_mass_batch(self, masses, datasources=None, order=None, direction=None): """Search compounds with a list of masses and mass ranges. The ``masses`` parameter should be a list of tuples, each with two elements: A mass, and a mass range:: qid = cs.filter_mass_batch(masses=[(12, 0.001), (24, 0.001)]) Optionally filter the results by data source. Use :meth:`~chemspipy.api.ChemSpider.get_datasources` to get the available datasources. The accepted values for ``order`` are: :data:`~chemspipy.api.RECORD_ID`, :data:`~chemspipy.api.MASS_DEFECT`, :data:`~chemspipy.api.MOLECULAR_WEIGHT`, :data:`~chemspipy.api.REFERENCE_COUNT`, :data:`~chemspipy.api.DATASOURCE_COUNT`, :data:`~chemspipy.api.PUBMED_COUNT` or :data:`~chemspipy.api.RSC_COUNT`. :param list[tuple[float, float]] masses: List of (mass, range) tuples. :param list[string] datasources: (Optional) List of datasources to restrict the results to. :param string order: (Optional) Field to sort the result by. :param string direction: (Optional) :data:`~chemspipy.api.ASCENDING` or :data:`~chemspipy.api.DESCENDING`. :return: Query ID that may be passed to ``filter_formula_batch_status`` and ``filter_formula_batch_results``. :rtype: string """ masses = [{'mass': m, 'range': r} for m, r in masses] json = { 'masses': masses, 'dataSources': datasources, 'orderBy': ORDERS.get(order), 'orderDirection': DIRECTIONS.get(direction) } response = self.post(api='compounds', namespace='filter', endpoint='mass/batch', json=json) return response['queryId'] def filter_mass_batch_status(self, query_id): """Get formula batch filter status using a query ID that was returned by a previous filter request. :param string query_id: Query ID from a previous formula batch filter request. :return: Status dict with 'status', 'count', and 'message' fields. :rtype: dict """ endpoint = 'mass/batch/{}/status'.format(query_id) response = self.get(api='compounds', namespace='filter', endpoint=endpoint) return response def filter_mass_batch_results(self, query_id): """Get formula batch filter results using a query ID that was returned by a previous filter request. Each result is a dict containing a ``formula`` key and a ``results`` key. :param string query_id: Query ID from a previous formula batch filter request. :return: List of results. :rtype: list[dict] """ endpoint = 'mass/batch/{}/results'.format(query_id) response = self.get(api='compounds', namespace='filter', endpoint=endpoint) return response['batchResults'] def filter_name(self, name, order=None, direction=None): """Search compounds by name. The accepted values for ``order`` are: :data:`~chemspipy.api.RECORD_ID`, :data:`~chemspipy.api.MASS_DEFECT`, :data:`~chemspipy.api.MOLECULAR_WEIGHT`, :data:`~chemspipy.api.REFERENCE_COUNT`, :data:`~chemspipy.api.DATASOURCE_COUNT`, :data:`~chemspipy.api.PUBMED_COUNT` or :data:`~chemspipy.api.RSC_COUNT`. :param string name: Compound name. :param string order: (Optional) Field to sort the result by. :param string direction: (Optional) :data:`~chemspipy.api.ASCENDING` or :data:`~chemspipy.api.DESCENDING`. :return: Query ID that may be passed to ``filter_status`` and ``filter_results``. :rtype: string """ json = {'name': name, 'orderBy': ORDERS.get(order), 'orderDirection': DIRECTIONS.get(direction)} response = self.post(api='compounds', namespace='filter', endpoint='name', json=json) return response['queryId'] def filter_smiles(self, smiles): """Search compounds by SMILES. :param string smiles: Compound SMILES. :return: Query ID that may be passed to ``filter_status`` and ``filter_results``. :rtype: string """ json = {'smiles': smiles} response = self.post(api='compounds', namespace='filter', endpoint='smiles', json=json) return response['queryId'] def filter_status(self, query_id): """Get filter status using a query ID that was returned by a previous filter request. :param string query_id: Query ID from a previous filter request. :return: Status dict with 'status', 'count', and 'message' fields. :rtype: dict """ endpoint = '{}/status'.format(query_id) response = self.get(api='compounds', namespace='filter', endpoint=endpoint) return response def filter_results(self, query_id, start=None, count=None): """Get filter results using a query ID that was returned by a previous filter request. :param string query_id: Query ID from a previous filter request. :param int start: Zero-based results offset. :param int count: Number of results to return. :return: List of results. :rtype: list[int] """ endpoint = '{}/results'.format(query_id) params = {'start': start, 'count': count} response = self.get(api='compounds', namespace='filter', endpoint=endpoint, params=params) return response['results'] def filter_results_sdf(self, query_id): """Get filter results as SDF file using a query ID that was returned by a previous filter request. :param string query_id: Query ID from a previous filter request. :return: SDF file containing the results. :rtype: bytes """ endpoint = '{}/results/sdf'.format(query_id) response = self.get(api='compounds', namespace='filter', endpoint=endpoint) return zlib.decompress(base64.b64decode(response['results']), 16 + zlib.MAX_WBITS) def convert(self, input, input_format, output_format): """Convert a chemical from one format to another. Format: ``SMILES``, ``InChI``, ``InChIKey`` or ``Mol``. Allowed conversions: from InChI to InChIKey, from InChI to Mol file, from InChI to SMILES, from InChIKey to InChI, from InChIKey to Mol file, from Mol file to InChI, from Mol file to InChIKey, from SMILES to InChI. :param string input: Input chemical. :param string input_format: Input format. :param string output_format: Output format. :return: Input chemical in output format. :rtype: string """ json = {'input': input, 'inputFormat': input_format, 'outputFormat': output_format} response = self.post(api='compounds', namespace='tools', endpoint='convert', json=json) return response['output'] def validate_inchikey(self, inchikey): """Return whether ``inchikey`` is valid. :param string inchikey: The InChIKey to validate. :return: Whether the InChIKey is valid. :rtype: bool """ json = {'inchikey': inchikey} try: response = self.post(api='compounds', namespace='tools', endpoint='validate/inchikey', json=json) return response['valid'] except errors.ChemSpiPyHTTPError: return False def get_databases(self): """Get the list of datasources in ChemSpider. .. deprecated:: 2.0.0 Use :py:meth:`~chemspipy.api.ChemSpider.get_datasources` instead. """ warnings.warn('Use get_datasources instead of get_databases.', DeprecationWarning) return self.get_datasources() def get_extended_compound_info(self, csid): """Get extended record details for a CSID. .. deprecated:: 2.0.0 Use :py:meth:`~chemspipy.api.ChemSpider.get_details` instead. :param string|int csid: ChemSpider ID. """ warnings.warn('Use get_details instead of get_extended_compound_info.', DeprecationWarning) return self.get_details(record_id=csid) def get_extended_compound_info_list(self, csids): """Get extended record details for a list of CSIDs. .. deprecated:: 2.0.0 Use :py:meth:`~chemspipy.api.ChemSpider.get_details_batch` instead. :param list[string|int] csids: ChemSpider IDs. """ warnings.warn('Use get_details_batch instead of get_extended_compound_info.', DeprecationWarning) return self.get_details_batch(record_ids=csids) def get_extended_mol_compound_info_list(self, csids, mol_type=MOL2D, include_reference_counts=False, include_external_references=False): """Get extended record details (including MOL) for a list of CSIDs. A maximum of 250 CSIDs can be fetched per request. .. deprecated:: 2.0.0 Use :py:meth:`~chemspipy.api.ChemSpider.get_details_batch` instead. :param list[string|int] csids: ChemSpider IDs. :param string mol_type: :data:`~chemspipy.api.MOL2D`, :data:`~chemspipy.api.MOL3D` or :data:`~chemspipy.api.BOTH`. :param bool include_reference_counts: Whether to include reference counts. :param bool include_external_references: Whether to include external references. """ warnings.warn('Use get_details_batch instead of get_extended_mol_compound_info_list.', DeprecationWarning) return self.get_details_batch(record_ids=csids) def get_record_mol(self, csid, calc3d=False): """Get ChemSpider record in MOL format. .. deprecated:: 2.0.0 Use :py:meth:`~chemspipy.api.ChemSpider.get_mol` instead. :param string|int csid: ChemSpider ID. :param bool calc3d: Whether 3D coordinates should be calculated before returning record data. """ warnings.warn('Use get_mol instead of get_record_mol.', DeprecationWarning) if calc3d: warnings.warn('calc3d parameter for get_record_mol is no longer supported.', DeprecationWarning) return self.get_mol(record_id=csid) def async_simple_search(self, query): """Search ChemSpider with arbitrary query, returning results in order of the best match found. This method returns a transaction ID which can be used with other methods to get search status and results. .. deprecated:: 2.0.0 Use :py:meth:`~chemspipy.api.ChemSpider.filter_name` instead. :param string query: Search query - a name, SMILES, InChI, InChIKey, CSID, etc. :return: Transaction ID. :rtype: string """ warnings.warn('Use filter_name instead of async_simple_search.', DeprecationWarning) return self.filter_name(name=query) def async_simple_search_ordered(self, query, order=CSID, direction=ASCENDING): """Search ChemSpider with arbitrary query, returning results with a custom order. This method returns a transaction ID which can be used with other methods to get search status and results. .. deprecated:: 2.0.0 Use :meth:`~chemspipy.api.ChemSpider.filter_name` instead. :param string query: Search query - a name, SMILES, InChI, InChIKey, CSID, etc. :param string order: (Optional) Field to sort the result by. :param string direction: (Optional) :data:`~chemspipy.api.ASCENDING` or :data:`~chemspipy.api.DESCENDING`. :return: Transaction ID. :rtype: string """ warnings.warn('Use filter_name instead of async_simple_search.', DeprecationWarning) return self.filter_name(name=query, order=order, direction=direction) def get_async_search_status(self, rid): """Check the status of an asynchronous search operation. .. deprecated:: 2.0.0 Use :meth:`~chemspipy.api.ChemSpider.filter_status` instead. :param string rid: A transaction ID, returned by an asynchronous search method. :return: Unknown, Created, Scheduled, Processing, Suspended, PartialResultReady, ResultReady, Failed, TooManyRecords :rtype: string """ warnings.warn('Use filter_status instead of get_async_search_status.', DeprecationWarning) return self.filter_status(query_id=rid)['status'] def get_async_search_status_and_count(self, rid): """Check the status of an asynchronous search operation. If ready, a count and message are also returned. .. deprecated:: 2.0.0 Use :meth:`~chemspipy.api.ChemSpider.filter_status` instead. :param string rid: A transaction ID, returned by an asynchronous search method. :rtype: dict """ warnings.warn('Use filter_status instead of get_async_search_status_and_count.', DeprecationWarning) return self.filter_status(query_id=rid) def get_async_search_result(self, rid): """Get the results from a asynchronous search operation. .. deprecated:: 2.0.0 Use :meth:`~chemspipy.api.ChemSpider.filter_results` instead. :param string rid: A transaction ID, returned by an asynchronous search method. :return: A list of Compounds. :rtype: list[:class:`~chemspipy.objects.Compound`] """ warnings.warn('Use filter_results instead of get_async_search_result.', DeprecationWarning) results = self.filter_results(query_id=rid) return [Compound(self, record_id) for record_id in results] def get_async_search_result_part(self, rid, start=0, count=-1): """Get a slice of the results from a asynchronous search operation. .. deprecated:: 2.0.0 Use :meth:`~chemspipy.api.ChemSpider.filter_results` instead. :param string rid: A transaction ID, returned by an asynchronous search method. :param int start: The number of results to skip. :param int count: The number of results to return. -1 returns all through to end. :return: A list of Compounds. :rtype: list[:class:`~chemspipy.objects.Compound`] """ warnings.warn('Use filter_results instead of get_async_search_result_part.', DeprecationWarning) if count == -1: count = None results = self.filter_results(query_id=rid, start=start, count=count) return [Compound(self, record_id) for record_id in results] def get_compound_info(self, csid): """Get SMILES, StdInChI and StdInChIKey for a given CSID. .. deprecated:: 2.0.0 Use :meth:`~chemspipy.api.ChemSpider.get_details` instead. :param string|int csid: ChemSpider ID. :rtype: dict """ warnings.warn('Use get_details instead of get_compound_info.', DeprecationWarning) return self.get_details(record_id=csid) def get_compound_thumbnail(self, csid): """Get PNG image as binary data. .. deprecated:: 2.0.0 Use :meth:`~chemspipy.api.ChemSpider.get_image` instead. :param string|int csid: ChemSpider ID. :rtype: bytes """ warnings.warn('Use get_image instead of get_compound_thumbnail.', DeprecationWarning) return self.get_image(record_id=csid) def simple_search(self, query): """Search ChemSpider with arbitrary query. .. deprecated:: 2.0.0 Use :meth:`~chemspipy.api.ChemSpider.search` instead. :param string query: Search query - a chemical name. :return: Search Results list. :rtype: :class:`~chemspipy.search.Results` """ warnings.warn('Use search instead of simple_search.', DeprecationWarning) return self.search(query=query) ChemSpiPy-2.0.0/chemspipy/errors.py000066400000000000000000000054351334530421700172520ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ chemspipy.errors ~~~~~~~~~~~~~~~~ Exceptions raised by ChemSpiPy. """ from __future__ import print_function from __future__ import unicode_literals from __future__ import division class ChemSpiPyError(Exception): """Root ChemSpiPy Exception.""" pass class ChemSpiPyHTTPError(ChemSpiPyError): """Base exception to handle HTTP errors.""" #: Default message if none supplied. Override in subclasses. MESSAGE = 'ChemSpiPy Error' HTTP_CODE = None def __init__(self, message=None, http_code=None, *args, **kwargs): """ :param string|bytes message: Error message. :param http_code: HTTP code. """ # Decode message to unicode if necessary if isinstance(message, bytes): try: message = message.decode('utf-8') except UnicodeDecodeError: message = message.decode('iso-8859-1') self.message = message if message is not None else self.MESSAGE self.http_code = http_code if http_code is not None else self.HTTP_CODE super(ChemSpiPyHTTPError, self).__init__(*args, **kwargs) def __repr__(self): args = 'message={!r}'.format(self.message) if self.http_code is not None: args += ', http_code={!r}'.format(self.http_code) return '{}({})'.format(self.__class__.__name__, args) def __str__(self): return self.message class ChemSpiPyBadRequestError(ChemSpiPyHTTPError): """Raised for a bad request.""" MESSAGE = 'Bad request.' HTTP_CODE = 400 class ChemSpiPyAuthError(ChemSpiPyHTTPError): """Raised when API key authorization fails.""" MESSAGE = 'Unauthorized.' HTTP_CODE = 401 class ChemSpiPyNotFoundError(ChemSpiPyHTTPError): """Raised when the requested resource was not found.""" MESSAGE = 'Not found.' HTTP_CODE = 404 class ChemSpiPyMethodError(ChemSpiPyHTTPError): """Raised when an invalid HTTP method is used.""" MESSAGE = 'Method Not Allowed.' HTTP_CODE = 405 class ChemSpiPyPayloadError(ChemSpiPyHTTPError): """Raised when a request payload is too large.""" MESSAGE = 'Payload Too Large.' HTTP_CODE = 413 class ChemSpiPyRateError(ChemSpiPyHTTPError): """Raised when too many requests are sent in a given amount of time.""" MESSAGE = 'Too Many Requests.' HTTP_CODE = 429 class ChemSpiPyServerError(ChemSpiPyHTTPError): """Raised when an internal server error occurs.""" MESSAGE = 'Internal Server Error.' HTTP_CODE = 500 class ChemSpiPyUnavailableError(ChemSpiPyHTTPError): """Raised when the service is temporarily unavailable.""" MESSAGE = 'Service Unavailable.' HTTP_CODE = 503 class ChemSpiPyTimeoutError(ChemSpiPyError): """Raised when an asynchronous request times out.""" pass ChemSpiPy-2.0.0/chemspipy/objects.py000066400000000000000000000121421334530421700173600ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ chemspipy.objects ~~~~~~~~~~~~~~~~~ Objects returned by ChemSpiPy API methods. """ from __future__ import print_function from __future__ import unicode_literals from __future__ import division import warnings from .utils import memoized_property class Compound(object): """ A class for retrieving and caching details about a specific ChemSpider record. The purpose of this class is to provide access to various parts of the ChemSpider API that return information about a compound given its ChemSpider ID. Information is loaded lazily when requested, and cached for future access. """ def __init__(self, cs, record_id): """ :param ChemSpider cs: ``ChemSpider`` session. :param int|string record_id: Compound record ID. """ self._cs = cs self._record_id = int(record_id) # TODO: Allow optional initialize with a record-type response from the API (kwarg or class method from_dict?). def __eq__(self, other): return isinstance(other, Compound) and self.csid == other.csid def __repr__(self): return 'Compound(%r)' % self.csid def _repr_png_(self): """For IPython notebook, display 2D image.""" return self.image @property def record_id(self): """Compound record ID. :rtype: int """ return self._record_id @property def csid(self): """ChemSpider ID. .. deprecated:: 2.0.0 Use :py:attr:`~chemspipy.objects.Compound.record_id` instead. :rtype: int """ warnings.warn('Use record_id instead of csid.', DeprecationWarning) return self._record_id @property def image_url(self): """Return the URL of a PNG image of the 2D chemical structure. :rtype: string """ return 'http://www.chemspider.com/ImagesHandler.ashx?id=%s' % self.record_id @memoized_property def _details(self): """Request compound info and cache the result.""" return self._cs.get_details(self.record_id) @property def molecular_formula(self): """Return the molecular formula for this Compound. :rtype: string """ return self._details['formula'] @property def smiles(self): """Return the SMILES for this Compound. :rtype: string """ return self._details['smiles'] # TODO: Convert tool to get inchi? @property def stdinchi(self): """Return the Standard InChI for this Compound. .. deprecated:: 2.0.0 Use :py:attr:`~chemspipy.objects.Compound.inchi` instead. :rtype: string """ warnings.warn('Use inchi instead of stdinchi.', DeprecationWarning) return self.inchi @property def stdinchikey(self): """Return the Standard InChIKey for this Compound. .. deprecated:: 2.0.0 Use :py:attr:`~chemspipy.objects.Compound.inchikey` instead. :rtype: string """ warnings.warn('Use inchikey instead of stdinchikey.', DeprecationWarning) return self.inchikey @property def inchi(self): """Return the InChI for this Compound. :rtype: string """ return self._cs.convert(self.mol_2d, 'Mol', 'InChI') @property def inchikey(self): """Return the InChIKey for this Compound. :rtype: string """ return self._cs.convert(self.mol_2d, 'Mol', 'InChIKey') @property def average_mass(self): """Return the average mass of this Compound. :rtype: float """ return self._details['averageMass'] @property def molecular_weight(self): """Return the molecular weight of this Compound. :rtype: float """ return self._details['molecularWeight'] @property def monoisotopic_mass(self): """Return the monoisotopic mass of this Compound. :rtype: float """ return self._details['monoisotopicMass'] @property def nominal_mass(self): """Return the nominal mass of this Compound. :rtype: float """ return self._details['nominalMass'] @property def common_name(self): """Return the common name for this Compound. :rtype: string """ return self._details['commonName'] @memoized_property def mol_2d(self): """Return the MOL file for this Compound with 2D coordinates. :rtype: string """ return self._details['mol2D'] @memoized_property def mol_3d(self): """Return the MOL file for this Compound with 3D coordinates. :rtype: string """ return self._details['mol3D'] @memoized_property def image(self): """Return a 2D depiction of this Compound. :rtype: bytes """ return self._cs.get_image(self.record_id) @memoized_property def external_references(self): """Return external references for this Compound. :rtype: list[dict] """ return self._cs.get_external_references(self.record_id) ChemSpiPy-2.0.0/chemspipy/search.py000066400000000000000000000142311334530421700171750ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ chemspipy.search ~~~~~~~~~~~~~~~~ A wrapper for asynchronous search requests. """ from __future__ import print_function from __future__ import unicode_literals from __future__ import division import datetime import logging import threading import time from six.moves import range from . import errors, objects, utils log = logging.getLogger(__name__) # TODO: Use Sequence abc metaclass? class Results(object): """Container class to perform a search on a background thread and hold the results when ready.""" def __init__(self, cs, searchfunc, searchargs, raise_errors=False, max_requests=40): """Generally shouldn't be instantiated directly. See :meth:`~chemspipy.api.ChemSpider.search` instead. :param ChemSpider cs: ``ChemSpider`` session. :param function searchfunc: Search function that returns a transaction ID. :param tuple searchargs: Arguments for the search function. :param bool raise_errors: If True, raise exceptions. If False, store on ``exception`` property. :param int max_requests: Maximum number of times to check if search results are ready. """ log.debug('Results init') self._cs = cs self._raise_errors = raise_errors self._max_requests = max_requests self._status = 'Created' self._exception = None self._qid = None self._message = None self._start = None self._end = None self._results = [] self._searchthread = threading.Thread(name='SearchThread', target=self._search, args=(cs, searchfunc, searchargs)) self._searchthread.start() def _search(self, cs, searchfunc, searchargs): """Perform the search and retrieve the results.""" log.debug('Searching in background thread') self._start = datetime.datetime.utcnow() try: self._qid = searchfunc(*searchargs) log.debug('Setting qid: %s' % self._qid) for _ in range(self._max_requests): log.debug('Checking status: %s' % self._qid) status = cs.filter_status(self._qid) self._status = status['status'] self._message = status.get('message', '') log.debug(status) time.sleep(0.2) if status['status'] == 'Complete': break elif status['status'] in {'Failed', 'Unknown', 'Suspended', 'Not Found'}: raise errors.ChemSpiPyServerError('Search Failed: %s' % status.get('message', '')) else: raise errors.ChemSpiPyTimeoutError('Search took too long') log.debug('Search success!') self._end = datetime.datetime.utcnow() if status['count'] > 0: self._results = [objects.Compound(cs, csid) for csid in cs.filter_results(self._qid)] log.debug('Results: %s', self._results) elif not self._message: self._message = 'No results found' except Exception as e: # Catch and store exception so we can raise it in the main thread self._exception = e self._end = datetime.datetime.utcnow() if self._status == 'Created': self._status = 'Failed' def ready(self): """Return True if the search finished. :rtype: bool """ return not self._searchthread.is_alive() def success(self): """Return True if the search finished with no errors. :rtype: bool """ return self.ready() and not self._exception def wait(self): """Block until the search has completed and optionally raise any resulting exception.""" log.debug('Waiting for search to finish') self._searchthread.join() if self._exception and self._raise_errors: raise self._exception @property def status(self): """Current status string returned by ChemSpider. :return: 'Unknown', 'Created', 'Scheduled', 'Processing', 'Suspended', 'PartialResultReady', 'ResultReady' :rtype: string """ return self._status @property def exception(self): """Any Exception raised during the search. Blocks until the search is finished.""" self.wait() # TODO: If raise_errors=True this will raise the exception when trying to access it? return self._exception @property def qid(self): """Search query ID. :rtype: string """ return self._qid @property def message(self): """A contextual message about the search. Blocks until the search is finished. :rtype: string """ self.wait() return self._message @property def count(self): """The number of search results. Blocks until the search is finished. :rtype: int """ return len(self) @property def duration(self): """The time taken to perform the search. Blocks until the search is finished. :rtype: :py:class:`datetime.timedelta` """ self.wait() return self._end - self._start @utils.memoized_property def sdf(self): """Get an SDF containing all the search results. :return: SDF containing the search results. :rtype: bytes """ self.wait() return self._cs.filter_results_sdf(self._qid) def __getitem__(self, index): """Get a single result or a slice of results. Blocks until the search is finished. This means a Results instance can be treated like a normal Python list. For example:: cs.search('glucose')[2] cs.search('glucose')[0:2] An IndexError will be raised if the index is greater than the total number of results. """ self.wait() return self._results.__getitem__(index) def __len__(self): self.wait() return self._results.__len__() def __iter__(self): self.wait() return iter(self._results) def __repr__(self): if self.success(): return 'Results(%s)' % self._results else: return 'Results(%s)' % self.status ChemSpiPy-2.0.0/chemspipy/utils.py000066400000000000000000000020701334530421700170660ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ chemspipy.utils ~~~~~~~~~~~~~~~ Miscellaneous utility functions. """ from __future__ import print_function from __future__ import unicode_literals from __future__ import division import datetime import functools def memoized_property(fget): """Decorator to create memoized properties.""" attr_name = '_{}'.format(fget.__name__) @functools.wraps(fget) def fget_memoized(self): if not hasattr(self, attr_name): setattr(self, attr_name, fget(self)) return getattr(self, attr_name) return property(fget_memoized) def timestamp(ts): """Create a datetime object from a timestamp string.""" fmt = '%Y-%m-%dT%H:%M:%S.%f' if '.' in ts else '%Y-%m-%dT%H:%M:%S' return datetime.datetime.strptime(ts, fmt) def duration(ts): """Create a timedelta object from a duration string.""" fmt = '%H:%M:%S.%f' if '.' in ts else '%H:%M:%S' dt = datetime.datetime.strptime(ts, fmt) return datetime.timedelta(hours=dt.hour, minutes=dt.minute, seconds=dt.second, microseconds=dt.microsecond) ChemSpiPy-2.0.0/docs/000077500000000000000000000000001334530421700143045ustar00rootroot00000000000000ChemSpiPy-2.0.0/docs/Makefile000066400000000000000000000011361334530421700157450ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = ChemSpiPy SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)ChemSpiPy-2.0.0/docs/api.rst000066400000000000000000000014241334530421700156100ustar00rootroot00000000000000.. _api: API Documentation ================= .. sectionauthor:: Matt Swain .. module:: chemspipy This part of the documentation is automatically generated from the ChemSpiPy source code and comments. .. automodule:: chemspipy.api .. autoclass:: ChemSpider() :members: .. autodata:: ASCENDING .. autodata:: DESCENDING .. autodata:: RECORD_ID .. autodata:: MASS_DEFECT .. autodata:: MOLECULAR_WEIGHT .. autodata:: REFERENCE_COUNT .. autodata:: DATASOURCE_COUNT .. autodata:: PUBMED_COUNT .. autodata:: RSC_COUNT .. autodata:: ORDERS .. autodata:: DIRECTIONS .. autodata:: FIELDS .. automodule:: chemspipy.objects :members: .. automodule:: chemspipy.search :members: .. automodule:: chemspipy.errors :members: ChemSpiPy-2.0.0/docs/conf.py000066400000000000000000000147251334530421700156140ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import inspect import os import sys sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- project = 'ChemSpiPy' copyright = '2018, Matt Swain' author = 'Matt Swain' # The short X.Y version version = '' # The full version, including alpha/beta/rc tags release = '2.0.0' # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.linkcode', 'm2r', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] # source_suffix = '.rst' # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output ------------------------------------------------- # on_rtd is whether we are on readthedocs.org on_rtd = os.environ.get('READTHEDOCS', None) == 'True' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # # html_theme = 'alabaster' if not on_rtd: # only import and set the theme if we're building docs locally import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'ChemSpiPydoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'ChemSpiPy.tex', 'ChemSpiPy Documentation', 'Matt Swain', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'chemspipy', 'ChemSpiPy Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'ChemSpiPy', 'ChemSpiPy Documentation', author, 'ChemSpiPy', 'One line description of project.', 'Miscellaneous'), ] # -- Extension configuration ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} # Sort autodoc members by the order they appear in the source code autodoc_member_order = 'bysource' # Concatenate the class and __init__ docstrings together autoclass_content = 'both' m2r_anonymous_references = True # Function courtesy of NumPy to return URLs containing line numbers def linkcode_resolve(domain, info): """Determine the URL corresponding to Python object.""" if domain != 'py': return None modname = info['module'] fullname = info['fullname'] submod = sys.modules.get(modname) if submod is None: return None obj = submod for part in fullname.split('.'): try: obj = getattr(obj, part) except: return None try: fn = inspect.getsourcefile(obj) except: fn = None if not fn: return None try: source, lineno = inspect.findsource(obj) except: lineno = None if lineno: linespec = '#L{}'.format(lineno + 1) else: linespec = "" fn = os.path.relpath(fn, start=os.path.abspath('..')) return 'http://github.com/mcs07/ChemSpiPy/blob/master/{}{}'.format(fn, linespec) ChemSpiPy-2.0.0/docs/guide/000077500000000000000000000000001334530421700154015ustar00rootroot00000000000000ChemSpiPy-2.0.0/docs/guide/advanced.rst000066400000000000000000000024461334530421700177060ustar00rootroot00000000000000.. _advanced: Advanced ======== Keep Your API Key Secret ------------------------ Be careful not to include your API key when sharing code. A simple way to ensure this doesn't happen by accident is to store your API key as an environment variable that can be specified in your `.bash_profile` or `.zshrc` file:: export CHEMSPIDER_API_KEY= This can then be retrieved in your scripts using ``os.environ``:: >>> api_key = os.environ['CHEMSPIDER_API_KEY'] >>> cs = ChemSpider(api_key) Specify a User Agent -------------------- As well as using your API key, it is possible to identify your program to the ChemSpider servers using a User Agent string. You can specify a custom User Agent through ChemSpiPy through the optional ``user_agent`` parameter to the ChemSpider class:: >>> from chemspipy import ChemSpider >>> cs = ChemSpider('', user_agent='My program 1.3, ChemSpiPy 2.0.0, Python 3.6') Logging ------- ChemSpiPy can generate logging statements if required. Just set the desired logging level:: import logging logging.basicConfig(level=logging.DEBUG) The logger is named 'chemspipy'. There is more information on logging in the `Python logging documentation`_. .. _`Python logging documentation`: https://docs.python.org/3/howto/logging.html ChemSpiPy-2.0.0/docs/guide/compound.rst000066400000000000000000000101601334530421700177550ustar00rootroot00000000000000.. _compound: Compound ======== Many ChemSpiPy search methods return :class:`~chemspipy.objects.Compound` objects, which provide more functionality that a simple list of ChemSpider IDs. The primary benefit is allowing easy access to further compound properties after performing a search. Creating a Compound ------------------- The easiest way to create a :class:`~chemspipy.objects.Compound` for a given ChemSpider ID is to use the :meth:`~chemspipy.api.ChemSpider.get_compound` method:: >>> compound = cs.get_compound(2157) Alternatively, a :class:`~chemspipy.objects.Compound` can be instantiated directly:: >>> compound = Compound(cs, 2157) Either way, no requests are made to the ChemSpider servers until specific :class:`~chemspipy.objects.Compound` properties are requested:: >>> print(compound.molecular_formula) C_{9}H_{8}O_{4} >>> print(compound.molecular_weight) 180.1574 >>> print(compound.smiles) CC(=O)Oc1ccccc1C(=O)O >>> print(compound.common_name) Aspirin Properties are cached locally after the first time they are retrieved, speeding up subsequent access and reducing the number of unnecessary requests to the ChemSpider servers. External References ------------------- Get a list of all external references for a given compound using the :attr:`~chemspipy.objects.Compound.external_references` property:: >>> refs = compound.external_references >>> print(len(refs)) 28181 >>> print(refs[0]) {'source': 'ChemBank', 'sourceUrl': 'http://chembank.broadinstitute.org/', 'externalId': 'DivK1c_000555', 'externalUrl': 'http://chembank.broad.harvard.edu/chemistry/viewMolecule.htm?cbid=1171'} Each reference is a dict with details for an external source. The list of references can be very large and slow to retrieve for popular compounds, so it is possible to filter it by datasource. To do this, use the :meth:`~chemspipy.api.ChemSpider.get_external_references` method directly:: >>> refs = cs.get_external_references(2157, datasources=['PubChem']) >>> print(refs) [{'source': 'PubChem', 'sourceUrl': 'http://pubchem.ncbi.nlm.nih.gov/', 'externalId': 2244, 'externalUrl': 'http://pubchem.ncbi.nlm.nih.gov/summary/summary.cgi?cid=2244'}] See the :ref:`Data Sources ` documentation for how to get a list of all available data sources. Searching for Compounds ----------------------- See the :ref:`searching documentation ` for full details. Implementation Details ---------------------- Each :class:`~chemspipy.objects.Compound` object is a simple wrapper around a ChemSpider ID. Behind the scenes, the property methods use the :meth:`~chemspipy.api.ChemSpider.get_details`, :meth:`~chemspipy.api.ChemSpider.convert`, :meth:`~chemspipy.api.ChemSpider.get_image`, and :meth:`~chemspipy.api.ChemSpider.get_external_references` API methods to retrieve the relevant information. It is possible to use these API methods directly if required:: >>> info = cs.get_details(2157) >>> print(info.keys()) dict_keys(['id', 'smiles', 'formula', 'averageMass', 'molecularWeight', 'monoisotopicMass', 'nominalMass', 'commonName', 'referenceCount', 'dataSourceCount', 'pubMedCount', 'rscCount', 'mol2D', 'mol3D']) >>> print(info['smiles']) CC(=O)Oc1ccccc1C(=O)O Results are returned as a python dictionary that is derived directly from the ChemSpider API JSON response. Compound Properties ------------------- .. class:: chemspipy.objects.Compound :noindex: .. autoattribute:: record_id :noindex: .. autoattribute:: image_url :noindex: .. autoattribute:: molecular_formula :noindex: .. autoattribute:: inchi :noindex: .. autoattribute:: inchikey :noindex: .. autoattribute:: average_mass :noindex: .. autoattribute:: molecular_weight :noindex: .. autoattribute:: monoisotopic_mass :noindex: .. autoattribute:: nominal_mass :noindex: .. autoattribute:: common_name :noindex: .. autoattribute:: mol_2d :noindex: .. autoattribute:: mol_3d :noindex: .. autoattribute:: image :noindex: .. autoattribute:: external_references :noindex: ChemSpiPy-2.0.0/docs/guide/gettingstarted.rst000066400000000000000000000036631334530421700211730ustar00rootroot00000000000000.. _gettingstarted: Getting Started =============== This page gives a introduction on how to get started with ChemSpiPy. Before We Start --------------- - Make sure you have :ref:`installed ChemSpiPy `. - :ref:`Obtain an API key ` from the ChemSpider web site. First Steps ----------- Start by importing ChemSpider:: >>> from chemspipy import ChemSpider Then connect to ChemSpider by creating a ``ChemSpider`` instance using your API key:: >>> cs = ChemSpider('') All your interaction with the ChemSpider database should now happen through this ChemSpider object, ``cs``. Retrieve a Compound ------------------- Retrieving information about a specific Compound in the ChemSpider database is simple. Let's get the Compound with `ChemSpider ID 2157`_:: >>> c = cs.get_compound(2157) Now we have a :class:`~chemspipy.objects.Compound` object called ``c``. We can get various identifiers and calculated properties from this object:: >>> print(c.molecular_formula) C_{9}H_{8}O_{4} >>> print(c.molecular_weight) 180.1574 >>> print(c.smiles) CC(=O)Oc1ccccc1C(=O)O >>> print(c.common_name) Aspirin Search for a Name ----------------- What if you don't know the ChemSpider ID of the Compound you want? Instead use the ``search`` method:: >>> for result in cs.search('Glucose'): ... print(result) Compound(5589) Compound(58238) Compound(71358) Compound(96749) Compound(2006622) Compound(5341883) Compound(5360239) Compound(9129332) Compound(9281077) Compound(9312824) Compound(9484839) Compound(9655623) The ``search`` method accepts any identifer that ChemSpider can interpret, including names, registry numbers, SMILES and InChI. That's a quick taster of the basic ChemSpiPy functionality. Read on for more some more advanced usage examples. .. _`ChemSpider ID 2157`: http://www.chemspider.com/Chemical-Structure.2157.html ChemSpiPy-2.0.0/docs/guide/install.rst000066400000000000000000000051471334530421700176100ustar00rootroot00000000000000.. _install: Installation ============ ChemSpiPy supports Python versions 2.7 and 3.5+. There are two required dependencies: `six`_ and `requests`_. Option 1: Use conda (recommended) --------------------------------- The easiest and recommended way to install is using conda. `Anaconda Python`_ is a self-contained Python environment that is particularly useful for scientific applications. If you don't already have it, start by installing `Miniconda`_, which includes a complete Python distribution and the conda package manager. Choose the Python 3 version, unless you have a particular reason why you must use Python 2. To install ChemSpiPy, at the command line, run:: conda config --add channels conda-forge conda install chemspipy This will add the `conda-forge`_ channel to your conda config, then install ChemSpiPy and all its dependencies into your conda environment. Option 2: Use pip ----------------- An alternative method is to install using pip:: pip install chemspipy This will download the latest version of ChemSpiPy, and place it in your `site-packages` folder so it is automatically available to all your python scripts. It should also ensure that the dependencies `six`_ and `requests`_ are installed. If you don't already have pip installed, you can `install it using get-pip.py`_:: curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python get-pip.py Option 3: Download the Latest Release ------------------------------------- Alternatively, `download the latest release`_ manually and install yourself:: tar -xzvf ChemSpiPy-2.0.0.tar.gz cd ChemSpiPy-2.0.0 python setup.py install The setup.py command will install ChemSpiPy in your `site-packages` folder so it is automatically available to all your python scripts. Option 4: Clone the Repository ------------------------------ The latest development version of ChemSpiPy is always `available on GitHub`_. This version is not guaranteed to be stable, but may include new features that have not yet been released. Simply clone the repository and install as usual:: git clone https://github.com/mcs07/ChemSpiPy.git cd ChemSpiPy python setup.py install .. _`six`: http://pythonhosted.org/six/ .. _`requests`: http://docs.python-requests.org/ .. _`Anaconda Python`: https://www.anaconda.com/distribution/ .. _`Miniconda`: https://conda.io/miniconda.html .. _`conda-forge`: https://conda-forge.org/ .. _`install it using get-pip.py`: https://pip.pypa.io/en/stable/installing/ .. _`download the latest release`: https://github.com/mcs07/ChemSpiPy/releases/ .. _`available on GitHub`: https://github.com/mcs07/ChemSpiPy ChemSpiPy-2.0.0/docs/guide/intro.rst000066400000000000000000000024151334530421700172700ustar00rootroot00000000000000.. _intro: Introduction ============ ChemSpiPy is a Python wrapper that allows simple access to the web APIs offered by ChemSpider. The aim is to provide an interface for users to access and query the ChemSpider database using Python, facilitating programs that can automatically carry out the tasks that you might otherwise perform manually via the `ChemSpider website`_. The RSC website has `full documentation for the ChemSpider APIs`_. It can be useful to browse through this documentation before getting started with ChemSpiPy to get an idea of what sort of features are available. .. _apikey: Obtaining an API Key -------------------- The Royal Society of Chemistry web services are currently available as an Open Developer Preview. During the preview you can make 1000 calls per month. For an increased allowance, contact `api@rsc.org`_. All operations require an API key. To obtain one, `Register for a RSC Developers account`_ and then `Add a new key`_. .. _`ChemSpider website`: http://www.chemspider.com .. _`full documentation for the ChemSpider APIs`: https://developer.rsc.org/compounds-v1/apis .. _`api@rsc.org`: api@rsc.org .. _`Register for a RSC Developers account`: https://developer.rsc.org/user/register .. _`Add a new key`: https://developer.rsc.org/user/me/apps ChemSpiPy-2.0.0/docs/guide/misc.rst000066400000000000000000000015341334530421700170710ustar00rootroot00000000000000.. _misc: Miscellaneous ============= .. _datasources: Data Sources ------------ Get a list of data sources in ChemSpider using the :meth:`~chemspipy.api.ChemSpider.get_datasources` method: >>> cs.get_datasources() ['Abacipharm', 'Abblis Chemicals', 'Abcam', 'ABI Chemicals', 'Abmole Bioscience', 'ACB Blocks', 'Accela ChemBio', ... ] Format Conversion ----------------- Convert between different molecular representations using the :meth:`~chemspipy.api.ChemSpider.convert` method:: >>> cs.convert('c1ccccc1', 'SMILES', 'InChI') 'InChI=1S/C6H6/c1-2-4-6-5-3-1/h1-6H' Allowed conversions: - From ``InChI`` to ``InChIKey`` - From ``InChI`` to ``Mol`` - From ``InChI`` to ``SMILES`` - From ``InChIKey`` to ``InChI`` - From ``InChIKey`` to ``Mol`` - From ``Mol`` to ``InChI`` - From ``Mol`` to ``InChIKey`` - From ``SMILES`` to ``InChI`` ChemSpiPy-2.0.0/docs/guide/searching.rst000066400000000000000000000072761334530421700201120ustar00rootroot00000000000000.. _searching: Searching ========= ChemSpiPy provides a number of different ways to search ChemSpider. Compound Search --------------- The main ChemSpiPy search method functions in a similar way to the main search box on the ChemSpider website. Just provide any type of query, and ChemSpider will interpret it and provide the most relevant results:: >>> cs.search('O=C(OCC)C') Results([Compound(8525)]) >>> cs.search('glucose') Results([Compound(5589), Compound(58238), Compound(71358), Compound(96749), Compound(2006622), Compound(5341883), Compound(5360239), Compound(9129332), Compound(9281077), Compound(9312824), Compound(9484839), Compound(9655623)]) >>> cs.search('2157') Results([Compound(2157)]) The supported query types include systematic names, synonyms, trade names, registry numbers, molecular formula, SMILES, InChI and InChIKey. The :class:`~chemspipy.search.Results` object that is returned can be treated just like any regular python list. For example, you can iterate over the results:: >>> for result in cs.search('Glucose'): ... print(result.record_id) 5589 58238 71358 96749 2006622 5341883 5360239 9129332 9281077 9312824 9484839 9655623 The :class:`~chemspipy.search.Results` object also provides the time taken to perform the search, and a message that explains how the query type was resolved:: >>> r = cs.search('Glucose') >>> print(r.duration) 0:00:00.513406 >>> print(r.message) Found by approved synonym Asynchronous Searching ---------------------- Certain types of search can sometimes take slightly longer, which can be inconvenient if the search method blocks the Python interpreter until the search results are returned. Fortunately, the ChemSpiPy search method works asynchronously. Once a search is executed, ChemSpiPy immediately returns the :class:`~chemspipy.search.Results` object, which is actually empty at first:: >>> results = cs.search('O=C(OCC)C') >>> print(results.ready()) False In a background thread, ChemSpiPy is making the search request and waiting for the response. But in the meantime, it is possible to continue performing other tasks in the main Python interpreter process. Call :meth:`~chemspipy.search.Results.ready()` at any point to check if the results have been returned and are available. Any attempt to access the results will just block until the results are ready, like a simple synchronous search. To manually block the main thread until the results are ready, use the :meth:`~chemspipy.search.Results.wait()` method:: >>> results.wait() >>> results.ready() True For more detailed information about the status of a search, use the :attr:`~chemspipy.search.Results.status` property:: >>> results.status 'Created' >>> results.wait() >>> results.status 'Complete' The possible statuses are ``Created``, ``Failed``, ``Unknown``, ``Suspended``, ``Complete``. Search by Formula ----------------- Searching by molecular formula is supported by the main :meth:`~chemspipy.api.ChemSpider.search()` method, but there is the possibility that a formula could be interpreted as a name or SMILES or another query type. To specifically search by formula, use:: >>> cs.search_by_formula('C44H30N4Zn') [Compound(436642), Compound(3232330), Compound(24746832), Compound(26995124)] Search by Mass -------------- It is also possible to search ChemSpider by mass by specifying a certain range:: >>> cs.search_by_mass(680, 0.001) [Compound(8298180), Compound(12931939), Compound(12931969), Compound(21182158)] The first parameter specifies the desired molecular mass, while the second parameter specifies the allowed ± range of values. ChemSpiPy-2.0.0/docs/index.rst000066400000000000000000000036331334530421700161520ustar00rootroot00000000000000.. ChemSpiPy documentation master file ChemSpiPy ========= .. sectionauthor:: Matt Swain **ChemSpiPy** provides a way to interact with ChemSpider in Python. It allows chemical searches, chemical file downloads, depiction and retrieval of chemical properties. Here's a quick peek:: >>> from chemspipy import ChemSpider >>> cs = ChemSpider('') >>> c1 = cs.get_compound(236) # Specify compound by ChemSpider ID >>> c2 = cs.search('benzene') # Search using name, SMILES, InChI, InChIKey, etc. Features -------- - Search compounds by synonym, SMILES, InChI, InChIKey, formula and mass. - Get identifiers and calculated properties for any compound record in ChemSpider. - Download compound records as a MOL file with 2D or 3D coordinates. - Get a 2D compound depiction as a PNG image. - Complete interface to every endpoint of the ChemSpider Web APIs. - Supports Python versions 2.7 and 3.5+. User Guide ---------- A step-by-step guide to getting started with ChemSpiPy. .. toctree:: :maxdepth: 2 guide/intro guide/install guide/gettingstarted guide/compound guide/searching guide/misc guide/advanced API Documentation ----------------- Comprehensive API documentation with information on every function, class and method. .. toctree:: :maxdepth: 2 api Additional Notes ---------------- .. toctree:: :maxdepth: 2 notes/license notes/contributing notes/migrating notes/changelog Useful links ------------ - `ChemSpiPy on GitHub`_ - `ChemSpiPy on PyPI`_ - `Issue tracker`_ - `Release history`_ - `ChemSpiPy Travis CI`_ .. _`ChemSpiPy on GitHub`: https://github.com/mcs07/ChemSpiPy .. _`ChemSpiPy on PyPI`: https://pypi.python.org/pypi/ChemSpiPy .. _`Issue tracker`: https://github.com/mcs07/ChemSpiPy/issues .. _`Release history`: https://github.com/mcs07/ChemSpiPy/releases .. _`ChemSpiPy Travis CI`: https://travis-ci.org/mcs07/ChemSpiPy ChemSpiPy-2.0.0/docs/notes/000077500000000000000000000000001334530421700154345ustar00rootroot00000000000000ChemSpiPy-2.0.0/docs/notes/changelog.rst000066400000000000000000000000621334530421700201130ustar00rootroot00000000000000.. _changelog: .. mdinclude:: ../../CHANGELOG.md ChemSpiPy-2.0.0/docs/notes/contributing.rst000066400000000000000000000000671334530421700207000ustar00rootroot00000000000000.. _contributing: .. include:: ../../CONTRIBUTING.rst ChemSpiPy-2.0.0/docs/notes/license.rst000066400000000000000000000005361334530421700176140ustar00rootroot00000000000000.. _license: License ======= Authors ------- ChemSpiPy is developed and maintained by Matt Swain and community contributors. See a full list of contributors on the `GitHub Contributors page`_. ChemSpiPy License ----------------- .. include:: ../../LICENSE .. _`GitHub Contributors page`: https://github.com/mcs07/ChemSpiPy/graphs/contributors ChemSpiPy-2.0.0/docs/notes/migrating.rst000066400000000000000000000050241334530421700201500ustar00rootroot00000000000000.. _migrating: Migration Guide =============== Upgrading to version 2.x ------------------------ The RSC released an entirely new REST API in 2018, necessitating a number of changes to ChemSpiPy. Where possible, backwards compatibility has been maintained, but many methods are deprecated and some have been removed entirely. ChemSpider Object ~~~~~~~~~~~~~~~~~ - Instantiate the :class:`~chemspipy.api.ChemSpider` with a required ``api_key`` parameter instead of the optional ``security_token`` parameter. - Deprecated methods: - ``get_databases`` → :meth:`~chemspipy.api.ChemSpider.get_datasources` - ``get_extended_compound_info`` → :meth:`~chemspipy.api.ChemSpider.get_details` - ``get_extended_compound_info_list`` → :meth:`~chemspipy.api.ChemSpider.get_details_batch` - ``get_extended_mol_compound_info_list`` → :meth:`~chemspipy.api.ChemSpider.get_details_batch` - ``get_record_mol`` → :meth:`~chemspipy.api.ChemSpider.get_mol` - ``async_simple_search`` → :meth:`~chemspipy.api.ChemSpider.filter_name` - ``async_simple_search_ordered`` → :meth:`~chemspipy.api.ChemSpider.filter_name` - ``get_async_search_status`` → :meth:`~chemspipy.api.ChemSpider.filter_status` - ``get_async_search_status_and_count`` → :meth:`~chemspipy.api.ChemSpider.filter_status` - ``get_async_search_result`` → :meth:`~chemspipy.api.ChemSpider.filter_results` - ``get_async_search_result_part`` → :meth:`~chemspipy.api.ChemSpider.filter_results` - ``get_compound_info`` → :meth:`~chemspipy.api.ChemSpider.get_details` - ``get_compound_thumbnail`` → :meth:`~chemspipy.api.ChemSpider.get_image` - ``simple_search`` → :meth:`~chemspipy.api.ChemSpider.search` - Removed methods: - ``get_original_mol`` - ``get_all_spectra_info`` - ``get_spectrum_info`` - ``get_compound_spectra_info`` - ``get_spectra_info_list`` Compound Object ~~~~~~~~~~~~~~~ - Non-standard InChI and InChIKey are no longer available. All are now 'standard'. Deprecated properties: - ``stdinchi`` → :attr:`~chemspipy.objects.Compound.inchi` - ``stdinchikey`` → :attr:`~chemspipy.objects.Compound.inchikey` - Removed properties: - ``xlogp`` - ``alogp`` - ``mol_3d`` - ``mol_raw`` Spectrum Object ~~~~~~~~~~~~~~~ - ``Spectrum`` object has been removed entirely. :mod:`~chemspipy.api` Module ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Removed ``DIMENSIONS`` mapping. - Replaced :attr:`~chemspipy.api.FIELDS` mapping with a list of available properties fields. - Removed ``xml_to_dict`` function. ChemSpiPy-2.0.0/environment.yml000066400000000000000000000003611334530421700164430ustar00rootroot00000000000000name: chemspipy channels: - conda-forge - defaults dependencies: - bumpversion=0.5.3 - coverage=4.5.1 - coveralls=1.3.0 - m2r=0.2.0 - requests=2.19.1 - pytest=3.6.2 - six=1.11.0 - sphinx=1.7.5 - sphinx_rtd_theme=0.4.1 ChemSpiPy-2.0.0/examples/000077500000000000000000000000001334530421700151725ustar00rootroot00000000000000ChemSpiPy-2.0.0/examples/Getting Started.ipynb000066400000000000000000000225571334530421700212400ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true, "run_control": { "frozen": false, "read_only": false } }, "source": [ "# ChemSpiPy: Getting Started\n", "\n", "Before we start:\n", "\n", "- Make sure you have [installed ChemSpiPy](http://chemspipy.readthedocs.io/en/latest/guide/install.html#install).\n", "- [Obtain a security token](http://chemspipy.readthedocs.io/en/latest/guide/intro.html#securitytoken) from the ChemSpider web site.\n", "\n", "## First Steps\n", "\n", "Start by importing ChemSpider:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true, "deletable": true, "editable": true, "run_control": { "frozen": false, "read_only": false } }, "outputs": [], "source": [ "from chemspipy import ChemSpider" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true, "run_control": { "frozen": false, "read_only": false } }, "source": [ "Then connect to ChemSpider by creating a ChemSpider instance using your security token:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "deletable": true, "editable": true, "run_control": { "frozen": false, "read_only": false } }, "outputs": [], "source": [ "# Tip: Store your security token as an environment variable to reduce the chance of accidentally sharing it\n", "import os\n", "mytoken = os.environ['CHEMSPIDER_SECURITY_TOKEN']\n", "\n", "cs = ChemSpider(security_token=mytoken)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true, "run_control": { "frozen": false, "read_only": false } }, "source": [ "All your interaction with the ChemSpider database should now happen through this ChemSpider object, `cs`.\n", "\n", "## Retrieve a Compound\n", "\n", "Retrieving information about a specific Compound in the ChemSpider database is simple.\n", "\n", "Let’s get the Compound with [ChemSpider ID 2157](http://www.chemspider.com/Chemical-Structure.2157.html):" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "deletable": true, "editable": true, "run_control": { "frozen": false, "read_only": false } }, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAYAAAA8AXHiAAAAAXNSR0IArs4c6QAAAARnQU1BAACx\njwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAACo1JREFU\neF7tnTGIVD0Qx/0EwU4RBEsbwcLCwkLEQrFQO68QxEq8QkFQwQPtBEEsLS1ULLUTC7G0tBJLsRIU\ntLRQEBRc95919svt5Xbf22QyeTMTWE6z702Smd/m5U0yyX+jcdriyTVQWgMAy5NroLQGtpQW6PJc\nA+Ep6GpwDXBowMHi0KrL9B7LGeDRgPdYPHo1L9XBMo8AjwIcLB69mpfqYJlHgEcBDhaPXs1LdbDM\nI8CjAAeLR6/mpTpY5hHgUYCDxaNX81IdLPMI8CjAweLRq3mpDpZ5BHgU4GDx6NW8VAfLPAI8CnCw\nePRqXqqDZR4BHgU4WDx6NS/VwTKPAI8CHCwevZqX6mCZR4BHAQ4Wj17NS3WwzCPAowAHi0ev5qU6\nWOYR4FGAg8WjV/NSHSzzCPAowMHi0at5qQ6WeQR4FOBg8ejVvFQTYP38+XO0uro6unnz5uj27dtF\nPzdu3AiyPa3XgAmwrl69in1WWT9ra2vOVqQB9WB9/fp1tH379tG2bdtG165dK9pbofe7dOlSAHbP\nnj2jb9++OVz/NKAerAsXLgTDX79+nc3oly9fZi+DrfJMglWD9e7du2lvgnEWV6JeET3j+/fvuYoZ\nlFzVYB08eDCA9eTJk6lRAFsJyPDYiyG6f/9+KOvMmTODAoCrsmrBevr0aTA04KIEwJB37969bH3i\n0QpZr1+/DrIA6969e0Pe8+fPs+UPXYBKsGBkDKZh5Ddv3kwNT3notXJTClwARTCX6BVz6yh5v0qw\nbt26teGxRHnnzp0rpu/Dhw+Hch48eDCVeerUqZCHR6PlpA6seCCNfyOl8koYPX45IFdDnEfllyhr\naDLUgYXBM3oM+JgopfJKGSrlzqDxF6eLo1T9ueSoAgvjKXJW0hgnlVdSmSlXA/J27twZHLMlxnMl\n61tLliqwyL2AgTWlVF5p5eItc9bVYN39oAYsDKBhXAyoKZF7IXY5lIYK8uK3UHI1IG///v1m3Q8q\nwIoNS4+eVB4HVCSTXA2AiR7Dlt0PKsCiwTIG0pQ43AuLwCT3Q+xqsOp+GDxYmFbBIBkfer3/+PHj\nhrxFUJT4nlwNGLhTXZCHusV5JcpqXcbgwSJXQtxLUF6JqZu+BqSVDvhLyaL7YdBgvXr1KgyOMUdH\n4xrM3c26HPrCkXM9uR9QBxrvIa/kdFJO/WrdO1iwABK5EuJJ31ReLWVSOeRqOHbs2LRoysOYy0Ia\nLFj0yDl+/PjUToAN45nY5SBhRFrpgF6KUux+sDCPOFiwTpw4ER55Kysr69jBY6eFlQWYO5xdqky9\naTz+kgC/RpmDBau2nyrXGLFPK1fWEO4fLFhQbi3Peq4h4x8BXjgspEGDBQOllh+3ZrjUXGJrdSxd\nn8GDxb16IVfh8eoHOG6tpMGDBUNhVSgG8pjGaS3VCD9rrc2ojwqwWu0VaoWfOViMGmhxHEPjv3hN\nPKMKmhKtoseCRuM3LwrJktR0KopHsj61y1YDFhTXiq8oFX5W27DS5akCC8rE/Jx0+BUCOaxHRasD\nSzr8iivUTLoH6lu+OrCgAMndX2gtWIuuj75w5FyvEiyp8KvWnbU5oPS9VyVYUMK89U+Yr8NYbNkP\neqXUm2eNULO+Bt5wPba13Lfv/w/TNpdqwdpsIWA8wM/ZPjIO3IDMQUyIHz06domPTY4P4KJ/I79w\nUgtW7H6IQ7KQj3VS6HFyPvGar0Es4bl7dwISIPrwYYIR/hJs+L5gUg0W9JQKtiiovyCKgiVK7mRT\nuo7THurffl5T+fg/9WAFC1UPFoWHYXPbZ8+eZfVSqR7u8ePHYePcOPysoH3KiaJxVUrivO+WrIF6\nsKCXQ4cOsW7FjbHa2bNnlzRBpdscrLKKxvhn9+7dAazz588X34775MmTQfa+seFaWGu/qfZosO6P\nwjKA1Qi1rzGOy9ZGPHgnuPDXB+/9VVsr1H4wYfTubugPUeoOzp38ZstrNYwey3fgbvnx48ekyu4g\nzYOrdqh9i2H0sX+t9sEGat8KJULtWwujT40v0YPB38b9oqESrNTufnn9X7e7500jdZNQ7qpFu0dz\nHyilDiwoTHJnl9QOOOVw6S5p3u7RNXZzVgcWDaIl90eQdj8s2j2au7cC/qrASu3u1/03Xu5K6XrM\n2z26VsSQKrCke4oYTameM7V8R2JJjxqwUrsWl+uD+kuSGOst2j26ZlicCrBaehuLEUzt7Ncf0e53\npKKDUgdWdZe4/JUqwGrNfxSbg8Y73L1Fyr1Qa0orhV93sDZbdrFZfqWpA6nAia6/Zcwjfvr0iX0q\nJbUximTEEA9YFSc7W52jWwcesz5Sm4/UntKa/aGVB6vi2mrp4NROvVYFfaQ2n5OOGCoPVsUFZRRO\nf+TIEfa5r04QpS5i1kdqvwqa0uI+nGqeTvqBRYvu47i02YX4HZfAwreybFwf7jtw4EBYublr1y7x\nvRrmQtdRH3ijW0YfODxh69atYQUrVrO2EjEkBhYduZYT24d779y50/ZZNR3Aov3pc3WB+6kXn417\nXLrHXfLGfmBBSbNpVnEdu368zS0T13flypXQQ50+fXr08uXLUJumB/Ad9QHXwDL6oHsePnwY9IKI\noR07dkwPiVqSi+zbyoPFvLaa3AuDOauGWR8xAbTf6cWLF7PByBVQHizUiPn1mhyi8dEmtb3cvRTP\nrA+qS+p86l71LHgxD1ioIKODNJ7CSZ3/HB/aVFBXeaIY9RFXrJWzqLuDlafW4nfTazYW9bVypFzx\nRi4hkA6IwlBB8hSMwYIFnaemLGpG5ixh9yq3tHAW9aDBaunY3irE9ChE+izqQYMVuxrQU1GqEf3c\nw8Yil0pPdw0erNT+7r4d9oRlSf/e4MGCAlNzY9Y38IdeJINoVYAFJdJsfnwsLvxceDuqFUAg8sxb\nUKjUIkg1YMUhTxTeZPmQJOJNatm2GrBi90MckEnTHIDMapIIolUFVjylEYP0588fq0xN24235keP\nHo1+/fpVRReqwILGKFLl8+fPEwVWmkqpYq2MQn7//v1PHath7RY+R8dzmC9evFgnlb6bLWqz/M2q\npA4sNPTt27eT9laa/M2wd9VbARJtawlQaP1XDJeDtcgkFdaaL6pCS9/fHesDIAGuL1++THsvyqO6\nOliLrNZxgd0iMVq+p97qAx0eMG4YAFtZWVl3fIuDtcjiHZYELxKh6fuuYyR6RNL19JceoV11onKM\nFRrvYHUalKcG6fE4zMHaqKHJUR6V9jXv+kuWuo56In8U5lqg4lrz3KrWuH9tba3o4D2OKErVX++j\n0N0N6+z9/fv34Lsq4W6AjDjN/h/f6QYLLXQH6ZQBwLU61kdpB6lNsGo8Z4yWQY9De49Cowav3Wzv\nsWprXGl5PsZSatgWmmX7rbAFCxitg/63QqOGlW62gyVtAaXlO1hKDSvdLAdL2gJKy3ewlBpWulkO\nlrQFlJbvYCk1rHSzHCxpCygt38FSaljpZjlY0hZQWr6DpdSw0s1ysKQtoLR8B0upYaWb5WBJW0Bp\n+Q6WUsNKN8vBkraA0vIdLKWGlW6WgyVtAaXlO1hKDSvdLAdL2gJKy3ewlBpWulkOlrQFlJbvYCk1\nrHSzHCxpCygt38FSaljpZv0FqtR95byLG1IAAAAASUVORK5CYII=\n", "text/plain": [ "Compound(2157)" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "comp = cs.get_compound(2157)\n", "comp" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true, "run_control": { "frozen": false, "read_only": false } }, "source": [ "Now we have a [Compound](http://chemspipy.readthedocs.org/en/latest/api.html#chemspipy.Compound) object called `comp`. We can get various identifiers and calculated properties from this object:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "deletable": true, "editable": true, "run_control": { "frozen": false, "read_only": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "C_{9}H_{8}O_{4}\n", "180.1574\n", "CC(=O)Oc1ccccc1C(=O)O\n", "Aspirin\n" ] } ], "source": [ "print(comp.molecular_formula)\n", "print(comp.molecular_weight)\n", "print(comp.smiles)\n", "print(comp.common_name)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true, "run_control": { "frozen": false, "read_only": false } }, "source": [ "## Search for a name\n", "\n", "What if you don’t know the ChemSpider ID of the Compound you want? Instead use the `search` method:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "deletable": true, "editable": true, "run_control": { "frozen": false, "read_only": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Compound(5589)\n", "Compound(58238)\n", "Compound(71358)\n", "Compound(96749)\n", "Compound(2006622)\n", "Compound(5341883)\n", "Compound(5360239)\n", "Compound(9129332)\n", "Compound(9281077)\n", "Compound(9312824)\n", "Compound(9484839)\n", "Compound(9655623)\n" ] } ], "source": [ "for result in cs.search('glucose'):\n", " print(result)" ] }, { "cell_type": "markdown", "metadata": { "deletable": true, "editable": true, "run_control": { "frozen": false, "read_only": false } }, "source": [ "The search method accepts any identifer that ChemSpider can interpret, including names, registry numbers, SMILES and InChI." ] } ], "metadata": { "hide_input": false, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1" } }, "nbformat": 4, "nbformat_minor": 0 } ChemSpiPy-2.0.0/setup.py000066400000000000000000000025351334530421700150730ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os from setuptools import setup if os.path.exists('README.rst'): long_description = open('README.rst').read() else: long_description = '''A simple Python wrapper around the ChemSpider Web Services.''' setup( name='ChemSpiPy', version='2.0.0', author='Matt Swain', author_email='m.swain@me.com', license='MIT', url='https://github.com/mcs07/ChemSpiPy', packages=['chemspipy'], description='A simple Python wrapper around the ChemSpider Web Services.', long_description=long_description, keywords='chemistry cheminformatics chemspider rsc rest api', zip_safe=False, install_requires=['requests', 'six'], tests_require=['pytest'], classifiers=[ 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Healthcare Industry', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Topic :: Scientific/Engineering', 'Topic :: Scientific/Engineering :: Bio-Informatics', 'Topic :: Scientific/Engineering :: Chemistry', 'Topic :: Software Development :: Libraries :: Python Modules', ], ) ChemSpiPy-2.0.0/tests/000077500000000000000000000000001334530421700145165ustar00rootroot00000000000000ChemSpiPy-2.0.0/tests/test_api.py000066400000000000000000000353031334530421700167040ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ test_api ~~~~~~~~ Test the core API functionality. """ from __future__ import print_function from __future__ import unicode_literals from __future__ import division import logging import os import re import time import pytest import six from chemspipy import ChemSpider, errors logging.basicConfig(level=logging.WARN) logging.getLogger('chemspipy').setLevel(logging.DEBUG) # API key is retrieved from environment variables CHEMSPIDER_API_KEY = os.environ['CHEMSPIDER_API_KEY'] # Chemspider instances with and without an API key cs = ChemSpider(CHEMSPIDER_API_KEY) def test_no_api_key(): """Test ChemSpider cannot be initialized with no API key.""" with pytest.raises(TypeError): ChemSpider() def test_api_key(): """Test API key is set correctly when initializing ChemSpider.""" assert cs.api_key == CHEMSPIDER_API_KEY def test_chemspider_repr(): """Test ChemSpider object repr.""" assert repr(cs) == 'ChemSpider()' # Lookups def test_get_datasources(): """Test get_datasources returns the list of ChemSpider data sources.""" datasources = cs.get_datasources() assert all(source in datasources for source in ['Wikipedia', 'ZINC', 'PubChem']) # Records def test_get_details(): """Test get_details returns details for a record ID.""" info = cs.get_details(6543) assert all(field in info for field in [ 'id', 'smiles', 'formula', 'averageMass', 'molecularWeight', 'monoisotopicMass', 'nominalMass', 'commonName', 'referenceCount', 'dataSourceCount', 'pubMedCount', 'rscCount', 'mol2D' ]) assert all(isinstance(info[field], float) for field in [ 'averageMass', 'molecularWeight', 'monoisotopicMass' ]) assert isinstance(info['id'], int) assert all(isinstance(info[field], six.text_type) for field in [ 'smiles', 'formula', 'commonName', 'mol2D' ]) def test_get_details_batch(): """Test get_extended_compound_info_list returns info for a list of record IDs.""" info = cs.get_details_batch([6543, 1235, 6084]) assert len(info) == 3 assert all(field in info[0] for field in [ 'id', 'smiles', 'formula', 'averageMass', 'molecularWeight', 'monoisotopicMass', 'nominalMass', 'commonName', 'referenceCount', 'dataSourceCount', 'pubMedCount', 'rscCount', 'mol2D' ]) assert all(isinstance(info[0][field], float) for field in [ 'averageMass', 'molecularWeight', 'monoisotopicMass' ]) assert isinstance(info[0]['id'], int) assert all(isinstance(info[0][field], six.text_type) for field in [ 'smiles', 'formula', 'commonName', 'mol2D' ]) def test_get_external_references(): """Test get_external_references returns references for a record ID.""" refs = cs.get_external_references(125) assert len(refs) > 5 for ref in refs: assert 'source' in ref assert 'sourceUrl' in ref assert 'externalId' in ref assert 'externalUrl' in ref def test_get_image(): """Test get_image returns image data for a record ID.""" img = cs.get_image(123) assert img[:8] == b'\x89PNG\x0d\x0a\x1a\x0a' # PNG magic number def test_get_mol(): """Test get_mol returns a MOLfile for a record ID.""" mol = cs.get_mol(6084) assert 'V2000' in mol assert 'M END' in mol # Filter def test_filter_formula_batch(): """Test filter_formula_batch returns a list of CSIDs.""" qid = cs.filter_formula_batch(formulas=['C2H2', 'C3H6']) while True: status = cs.filter_formula_batch_status(qid) if status['status'] in {'Suspended', 'Failed', 'Not Found', 'Complete'}: break time.sleep(1) results = cs.filter_formula_batch_results(qid) assert len(results) == 2 for result in results: assert 'formula' in result assert 'results' in result assert len(result['results']) > 1 def test_filter_intrinsicproperty_formula(): """Test filter_intrinsicproperty returns a list of CSIDs.""" qid = cs.filter_intrinsicproperty(formula='C6H6') while True: status = cs.filter_status(qid) if status['status'] in {'Suspended', 'Failed', 'Not Found', 'Complete'}: break time.sleep(1) results = cs.filter_results(qid) assert len(results) > 10 def test_filter_intrinsicproperty_mass(): """Test filter_intrinsicproperty returns a list of CSIDs.""" qid = cs.filter_intrinsicproperty(monoisotopic_mass=500, monoisotopic_mass_range=0.001) while True: status = cs.filter_status(qid) if status['status'] in {'Suspended', 'Failed', 'Not Found', 'Complete'}: break time.sleep(1) results = cs.filter_results(qid) assert len(results) > 10 def test_filter_mass(): """Test filter_mass returns a list of CSIDs.""" qid = cs.filter_mass(500, 0.001) while True: status = cs.filter_status(qid) if status['status'] in {'Suspended', 'Failed', 'Not Found', 'Complete'}: break time.sleep(1) results = cs.filter_results(qid) assert len(results) > 10 def test_filter_mass_batch(): """Test filter_mass_batch returns a list of CSIDs.""" qid = cs.filter_mass_batch(masses=[(12, 0.001), (24, 0.001)]) while True: status = cs.filter_mass_batch_status(qid) if status['status'] in {'Suspended', 'Failed', 'Not Found', 'Complete'}: break time.sleep(1) results = cs.filter_mass_batch_results(qid) print(results) assert len(results) == 2 for result in results: assert 'mass' in result assert 'range' in result assert 'results' in result assert len(result['results']) > 0 def test_filter_smiles(): """Test filter_smiles returns a list of CSIDs.""" qid = cs.filter_smiles('c1ccccc1') while True: status = cs.filter_status(qid) if status['status'] in {'Suspended', 'Failed', 'Not Found', 'Complete'}: break time.sleep(1) results = cs.filter_results(qid) assert len(results) == 1 assert results[0] == 236 # Benzene ChemSpider ID def test_filter_sdf(): """Test filter_results_sdf returns an SDF file.""" qid = cs.filter_formula('C10H20') while True: status = cs.filter_status(qid) if status['status'] in {'Suspended', 'Failed', 'Not Found', 'Complete'}: break time.sleep(1) sdf = cs.filter_results_sdf(qid) assert b'V2000' in sdf assert b'$$$$' in sdf # Tools def test_convert(): """Test convert.""" assert cs.convert('c1ccccc1', 'SMILES', 'InChI') == 'InChI=1S/C6H6/c1-2-4-6-5-3-1/h1-6H' assert cs.convert('InChI=1S/C6H6/c1-2-4-6-5-3-1/h1-6H', 'InChI', 'InChIKey') == 'UHOVQNZJYSORNB-UHFFFAOYSA-N' assert cs.convert('UHOVQNZJYSORNB-UHFFFAOYSA-N', 'InChIKey', 'InChI') == 'InChI=1S/C6H6/c1-2-4-6-5-3-1/h1-6H' def test_validate_inchikey(): """Test validate_inchikey.""" assert cs.validate_inchikey('UHOVQNZJYSORNB-UHFFFAOYSA-N') is True assert cs.validate_inchikey('UHOVQNZJYSORNB-UHFFFAOYSQ-N') is False assert cs.validate_inchikey('UHOVQNZJYSORNB-UHFFFAOYSA') is False # MassSpecAPI def test_get_databases(): """Test get_databases returns the list of ChemSpider data sources.""" with pytest.deprecated_call(): dbs = cs.get_databases() assert all(source in dbs for source in ['Wikipedia', 'ZINC', 'PubChem']) def test_get_extended_compound_info(): """Test get_extended_compound_info returns info for a CSID.""" with pytest.deprecated_call(): info = cs.get_extended_compound_info(6543) assert all(field in info for field in [ 'id', 'smiles', 'formula', 'averageMass', 'molecularWeight', 'monoisotopicMass', 'nominalMass', 'commonName', 'referenceCount', 'dataSourceCount', 'pubMedCount', 'rscCount', 'mol2D' ]) assert all(isinstance(info[field], float) for field in [ 'averageMass', 'molecularWeight', 'monoisotopicMass' ]) assert isinstance(info['id'], int) assert all(isinstance(info[field], six.text_type) for field in [ 'smiles', 'formula', 'commonName', 'mol2D' ]) def test_get_extended_compound_info_list(): """Test get_extended_compound_info_list returns info for a list of CSIDs.""" with pytest.deprecated_call(): info = cs.get_extended_compound_info_list([6543, 1235, 6084]) assert len(info) == 3 assert all(field in info[0] for field in [ 'id', 'smiles', 'formula', 'averageMass', 'molecularWeight', 'monoisotopicMass', 'nominalMass', 'commonName', 'referenceCount', 'dataSourceCount', 'pubMedCount', 'rscCount', 'mol2D' ]) assert all(isinstance(info[0][field], float) for field in [ 'averageMass', 'molecularWeight', 'monoisotopicMass' ]) assert isinstance(info[0]['id'], int) assert all(isinstance(info[0][field], six.text_type) for field in [ 'smiles', 'formula', 'commonName', 'mol2D' ]) def test_get_extended_mol_compound_info_list(): """Test get_extended_mol_compound_info_list returns info for a list of CSIDs.""" with pytest.deprecated_call(): info = cs.get_extended_mol_compound_info_list([1236], include_external_references=True, include_reference_counts=True) assert len(info) == 1 assert all(field in info[0] for field in [ 'id', 'smiles', 'formula', 'averageMass', 'molecularWeight', 'monoisotopicMass', 'nominalMass', 'commonName', 'referenceCount', 'dataSourceCount', 'pubMedCount', 'rscCount', 'mol2D' ]) assert all(isinstance(info[0][field], float) for field in [ 'averageMass', 'molecularWeight', 'monoisotopicMass' ]) assert all(isinstance(info[0][field], int) for field in [ 'id', 'referenceCount', 'dataSourceCount', 'pubMedCount', 'rscCount' ]) assert all(isinstance(info[0][field], six.text_type) for field in [ 'smiles', 'formula', 'commonName', 'mol2D' ]) def test_get_record_mol(): """Test get_record_mol returns a MOL file.""" with pytest.deprecated_call(): mol = cs.get_record_mol(6084) assert 'V2000' in mol assert 'M END' in mol # Search def test_async_simple_search(): """Test async_simple_search returns a transaction ID.""" with pytest.deprecated_call(): rid = cs.async_simple_search('benzene') assert re.compile(r'[a-f0-9\-]{20,50}').search(rid) def test_async_simple_search_ordered(): """Test async_simple_search returns a transaction ID.""" with pytest.deprecated_call(): rid = cs.async_simple_search_ordered('glucose') assert re.compile(r'[a-f0-9\-]{20,50}').search(rid) def test_get_async_search_status(): """Test get_async_search_status returns the status for a transaction ID.""" with pytest.deprecated_call(): rid = cs.async_simple_search('benzene') status = cs.get_async_search_status(rid) assert status in { 'Complete', 'Suspended', 'Failed', 'Not Found', 'Unknown', 'Created', 'Scheduled', 'Processing', 'Suspended', 'PartialResultReady', 'ResultReady' } def test_get_async_search_status_and_count(): """Test get_async_search_status_and_count returns the status for a transaction ID.""" with pytest.deprecated_call(): rid = cs.async_simple_search('benzene') while True: status = cs.get_async_search_status_and_count(rid) if status['status'] in {'Created', 'Scheduled', 'Processing'}: continue assert status['count'] == 1 assert status['message'] == 'Found by approved synonym' break def test_get_async_search_result(): """Test get_async_search_result returns a list of CSIDs.""" with pytest.deprecated_call(): rid = cs.async_simple_search('benzene') while True: status = cs.get_async_search_status(rid) if status in {'Created', 'Scheduled', 'Processing'}: continue assert [c.csid for c in cs.get_async_search_result(rid)] == [236] break def test_get_async_search_result_part(): """Test get_async_search_result_part returns a list of CSIDs.""" with pytest.deprecated_call(): rid = cs.async_simple_search('glucose') while True: status = cs.get_async_search_status(rid) if status in {'Created', 'Scheduled', 'Processing'}: continue assert len(cs.get_async_search_result_part(rid)) > 6 assert len(cs.get_async_search_result_part(rid, start=2)) > 2 assert len(cs.get_async_search_result_part(rid, start=2, count=2)) == 2 assert len(cs.get_async_search_result_part(rid, start=2, count=99)) > 2 break def test_get_compound_info(): """Test get_compound_info returns info for a CSID.""" with pytest.deprecated_call(): info = cs.get_compound_info(123) assert all(field in info for field in ['id', 'smiles']) assert isinstance(info['id'], int) assert isinstance(info['smiles'], six.text_type) def test_get_compound_thumbnail(): """Test get_compound_thumbnail returns image data for a CSID.""" with pytest.deprecated_call(): img = cs.get_compound_thumbnail(123) assert img[:8] == b'\x89PNG\x0d\x0a\x1a\x0a' # PNG magic number def test_simple_search(): """Test simple_search returns a list of CSIDs.""" assert all(csid in [c.csid for c in cs.simple_search('glucose')] for csid in [5589, 58238, 71358, 96749, 9312824, 9484839]) # Errors def test_invalid_api_key(): """Test ChemSpiPyAuthError is raised if a token with invalid format is used.""" with pytest.raises(errors.ChemSpiPyAuthError): mf = ChemSpider('abcde1-1346fa-934a').get_compound(2157).molecular_formula def test_invalid_api_key2(): """Test ChemSpiPyAuthError is raised if a fake token with correct format is used.""" with pytest.raises(errors.ChemSpiPyAuthError): mf = ChemSpider('6qBA6lrJycPAYTTcajkkaN02brz5S6Ee').get_compound(2157).molecular_formula def test_invalid_query_id(): """Test ChemSpiPyBadRequestError is raised when an invalid query ID is used.""" with pytest.raises(errors.ChemSpiPyBadRequestError): cs.filter_status('xxxxxx') def test_expired_query_id(): """Test ChemSpiPyBadRequestError is raised when a valid but expired query ID is used.""" with pytest.raises(errors.ChemSpiPyBadRequestError): cs.filter_status('1a93ee87-acbe-4caa-bc3b-23c3ff39be0f') def test_fictional_query_id(): """Test ChemSpiPyBadRequestError is raised when a valid but made up query ID is used.""" with pytest.raises(errors.ChemSpiPyBadRequestError): cs.filter_status('1a93ee87-acbe-4caa-bc3b-23c3ff39be0a') ChemSpiPy-2.0.0/tests/test_compound.py000066400000000000000000000122751334530421700177620ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ test_compound ~~~~~~~~~~~~~ Test the Compound object. """ from __future__ import print_function from __future__ import unicode_literals from __future__ import division import logging import os import pytest import requests from chemspipy import ChemSpider, Compound logging.basicConfig(level=logging.WARN, format='%(levelname)s:%(name)s:(%(threadName)-10s):%(message)s') logging.getLogger('chemspipy').setLevel(logging.DEBUG) # API key is retrieved from environment variables CHEMSPIDER_API_KEY = os.environ['CHEMSPIDER_API_KEY'] cs = ChemSpider(CHEMSPIDER_API_KEY) def test_get_compound(): """Test getting a compound by ChemSpider ID.""" compound = cs.get_compound(2157) assert isinstance(compound, Compound) assert compound.record_id == 2157 with pytest.deprecated_call(): assert compound.csid == 2157 compound = cs.get_compound('2157') assert isinstance(compound, Compound) assert compound.record_id == 2157 with pytest.deprecated_call(): assert compound.csid == 2157 def test_get_compounds(): """Test getting multiple compounds by ChemSpider ID.""" compounds = cs.get_compounds([2157, 13837760]) assert [c.csid for c in compounds], [2157 == 13837760] for c in compounds: assert 'http://' in c.image_url assert c.average_mass > 0 def test_compound_init(): """Test instantiating a Compound directly.""" compound = Compound(cs, 2157) assert compound.csid == 2157 def test_compound_equality(): """Test equality test by ChemSpider ID.""" c1 = cs.get_compound(13837760) c2 = cs.get_compound(2157) c3 = cs.get_compound(2157) assert c1 != c2 assert c2 == c3 def test_compound_repr(): """Test Compound object repr.""" assert repr(cs.get_compound(1234)) == 'Compound(1234)' def test_image_url(): """Test image_url returns a valid URL.""" url = cs.get_compound(2157).image_url response = requests.get(url) assert 'http://www.chemspider.com/ImagesHandler.ashx?id=' in url assert response.status_code == 200 def test_molecular_formula(): """Test Compound property molecular_formula.""" compound = cs.get_compound(2157) assert compound.molecular_formula == 'C_{9}H_{8}O_{4}' # Ensure value is the same on subsequent access from cache assert compound.molecular_formula == 'C_{9}H_{8}O_{4}' def test_smiles(): """Test Compound property smiles.""" compound = cs.get_compound(2157) assert compound.smiles == 'CC(=O)Oc1ccccc1C(=O)O' # Ensure value is the same on subsequent access from cache assert compound.smiles == 'CC(=O)Oc1ccccc1C(=O)O' def test_inchi(): """Test Compound property inchi.""" compound = cs.get_compound(2157) assert compound.inchi == 'InChI=1S/C9H8O4/c1-6(10)13-8-5-3-2-4-7(8)9(11)12/h2-5H,1H3,(H,11,12)' # Ensure value is the same on subsequent access from cache assert compound.inchi == 'InChI=1S/C9H8O4/c1-6(10)13-8-5-3-2-4-7(8)9(11)12/h2-5H,1H3,(H,11,12)' def test_stdinchi(): """Test Compound property stdinchi.""" compound = cs.get_compound(2157) with pytest.deprecated_call(): assert compound.stdinchi == 'InChI=1S/C9H8O4/c1-6(10)13-8-5-3-2-4-7(8)9(11)12/h2-5H,1H3,(H,11,12)' # Ensure value is the same on subsequent access from cache assert compound.stdinchi == 'InChI=1S/C9H8O4/c1-6(10)13-8-5-3-2-4-7(8)9(11)12/h2-5H,1H3,(H,11,12)' def test_inchikey(): """Test Compound property inchikey.""" compound = cs.get_compound(2157) assert compound.inchikey == 'BSYNRYMUTXBXSQ-UHFFFAOYSA-N' # Ensure value is the same on subsequent access from cache assert compound.inchikey == 'BSYNRYMUTXBXSQ-UHFFFAOYSA-N' def test_stdinchikey(): """Test Compound property stdinchikey.""" compound = cs.get_compound(2157) with pytest.deprecated_call(): assert compound.stdinchikey == 'BSYNRYMUTXBXSQ-UHFFFAOYSA-N' # Ensure value is the same on subsequent access from cache assert compound.stdinchikey == 'BSYNRYMUTXBXSQ-UHFFFAOYSA-N' def test_masses(): """Test Compound property average_mass, molecular_weight, monoisotopic_mass, nominal_mass.""" compound = cs.get_compound(2157) assert 180 < compound.average_mass < 180.2 assert 180 < compound.molecular_weight < 180.2 assert 180 < compound.monoisotopic_mass < 180.2 assert compound.nominal_mass == 180 def test_name(): """Test Compound property common_name.""" compound = cs.get_compound(2157) assert compound.common_name == 'Aspirin' def test_molfiles(): """Test Compound property mol2d, mol3d, mol_raw.""" compound = cs.get_compound(2157) assert 'V2000' in compound.mol_2d assert 'V2000' in compound.mol_3d def test_image(): """Test Compound property image.""" compound = cs.get_compound(2157) assert compound.image[:8] == b'\x89PNG\x0d\x0a\x1a\x0a' # PNG magic number def test_external_references(): """Test Compound property external_references.""" compound = cs.get_compound(97809) assert len(compound.external_references) > 50 for xref in compound.external_references: assert 'externalId' in xref assert 'externalUrl' in xref assert 'source' in xref assert 'sourceUrl' in xref ChemSpiPy-2.0.0/tests/test_search.py000066400000000000000000000073141334530421700174010ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ test_search ~~~~~~~~~~~ Test the search wrapper. """ from __future__ import print_function from __future__ import unicode_literals from __future__ import division import logging import os import pytest from chemspipy import ChemSpider, errors, ASCENDING, DESCENDING, CSID, REFERENCE_COUNT, MOLECULAR_WEIGHT logging.basicConfig(level=logging.WARN, format='%(levelname)s:%(name)s:(%(threadName)-10s):%(message)s') logging.getLogger('chemspipy').setLevel(logging.DEBUG) # API key is retrieved from environment variables CHEMSPIDER_API_KEY = os.environ['CHEMSPIDER_API_KEY'] cs = ChemSpider(CHEMSPIDER_API_KEY) def test_search_smiles(): """Test SMILES input to search.""" results = cs.search('O=C(OCC)C') assert results.ready() is False results.wait() assert results.ready() is True assert results.success() is True assert results.message == 'Found by conversion query string to chemical structure (full match)' assert results[0].csid == 8525 assert results.duration.total_seconds() > 0 def test_search_csid(): """Test ChemSpider ID input to search.""" results = cs.search(8525) assert results.message == 'Found by CSID' assert len(results) == 1 assert repr(results) == 'Results([Compound(8525)])' assert results[0].csid == 8525 def test_search_name(): """Test name input to search.""" results = cs.search('propanol') assert results.message == 'Found by approved synonym' assert results[0].csid == 1004 def test_search_iter(): """Test iteration of search results.""" for result in cs.search('glucose'): assert isinstance(result.csid, int) def test_search_ordered_csid(): """Test search results ordered by CSID.""" results = cs.search('glucose', order=CSID) assert list(results) == sorted(results, key=lambda x: x.csid) def test_search_ordered_csid_descending(): """Test search results ordered by CSID and direction descending.""" results = cs.search('glucose', order=CSID, direction=DESCENDING) assert list(results) == sorted(results, key=lambda x: x.csid, reverse=True) def test_search_ordered_ref_descending(): """Test search results ordered by CSID and direction descending.""" results = cs.search('glucose', order=REFERENCE_COUNT, direction=DESCENDING) assert [result.csid for result in results] def test_search_ordered_weight_ascending(): """Test search results ordered by CSID and direction descending.""" results = cs.search('P', order=MOLECULAR_WEIGHT, direction=ASCENDING) assert list(results) == sorted(results, key=lambda x: x.molecular_weight) def test_search_no_results(): """Test name input to search.""" results = cs.search('aergherguyaelrgiaubrfawyef') assert results.message == 'No results found' assert results.ready() is True assert results.success() is True assert len(results) == 0 def test_too_high_index(): """Test IndexError is raised for a too high index.""" with pytest.raises(IndexError): result = cs.search('glucose')[7843] def test_search_failed(): """Test ChemSpiPyServerError is raised for an invalid SMILES.""" results = cs.search('O=C(OCC)C*') results.wait() assert isinstance(results.exception, errors.ChemSpiPyBadRequestError) assert results.status == 'Failed' assert repr(results) == 'Results(Failed)' assert results.ready() is True assert results.success() is False assert results.count == 0 assert results.duration.total_seconds() > 0 def test_search_exception(): """Test ChemSpiPyServerError is raised for an invalid SMILES.""" with pytest.raises(errors.ChemSpiPyBadRequestError): results = cs.search('O=C(OCC)C*', raise_errors=True) results.wait() ChemSpiPy-2.0.0/tests/test_utils.py000066400000000000000000000033361334530421700172740ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ test_utils ~~~~~~~~~~ Test miscellaneous utility functions. """ from __future__ import print_function from __future__ import unicode_literals from __future__ import division import datetime import logging from chemspipy.utils import timestamp, duration logging.basicConfig(level=logging.WARN, format='%(levelname)s:%(name)s:(%(threadName)-10s):%(message)s') logging.getLogger('chemspipy').setLevel(logging.DEBUG) def test_timestamp_microseconds(): """Test timestamp parser function on timestamps strings with microseconds.""" assert timestamp('2007-08-08T20:18:36.593') == datetime.datetime(2007, 8, 8, 20, 18, 36, 593000) assert timestamp('2035-12-31T23:59:59.999') == datetime.datetime(2035, 12, 31, 23, 59, 59, 999000) def test_timestamp_seconds(): """Test timestamp parser function on timestamps strings with no microseconds.""" assert timestamp('2007-08-08T20:18:36') == datetime.datetime(2007, 8, 8, 20, 18, 36) assert timestamp('2010-09-01T04:33:59') == datetime.datetime(2010, 9, 1, 4, 33, 59) def test_duration_microseconds(): """Test duration parser function on duration strings with microseconds.""" assert duration('0:00:00.001') == datetime.timedelta(0, 0, 1000) assert duration('0:00:00.12') == datetime.timedelta(0, 0, 120000) assert duration('0:00:00.120') == datetime.timedelta(0, 0, 120000) assert duration('0:00:00.052') == datetime.timedelta(0, 0, 52000) assert duration('0:00:03.523') == datetime.timedelta(0, 3, 523000) def test_duration_seconds(): """Test duration parser function on duration strings with no microseconds.""" assert duration('0:00:00') == datetime.timedelta(0) assert duration('0:00:03') == datetime.timedelta(0, 3)