Metadata-Version: 2.4
Name: molharbor
Version: 0.1.6
Summary:  unofficial python wrapper for MolPort API
Project-URL: homepage, https://github.com/asiomchen/molharbor
Project-URL: repository, https://github.com/asiomchen/molharbor
Author-email: Anton Siomchen <41703271+asiomchen@users.noreply.github.com>
License: Apache-2.0
License-File: LICENSE
Keywords: chemistry,molport,vhts
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Requires-Dist: cloudscraper>=1.1.17
Requires-Dist: pydantic>=2.0
Description-Content-Type: text/markdown


# MolHarbour
[![image](https://shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)](#)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![codecov](https://codecov.io/gh/asiomchen/molharbor/graph/badge.svg?token=BPMQ6F3IV9)](https://codecov.io/gh/asiomchen/molharbor)

MolHarbour is a unofficial Python wrapper for the Molport REST API. It allows you to search for chemical compounds and retrieve information about them.
Additionally, MolHarbour unifies the Molport API variables names and verifies the response data using wonderful [Pydantic](https://github.com/pydantic/pydantic) models.

This library is not affiliated with Molport in any way. Molport is a registered trademark of Molport SIA.

The project was initiated as a part of my master's thesis and and is based on refactored scripts which I wrote to retrive commercial availablity of compounds in chembl database.

## Installation

### From PyPI

```bash
pip install molharbor
```

### From source

```bash
pip install git+https://github.com/asiomchen/molharbor

```

## Quickstart
### Authentication

To start using MolHarbour, you need to create an instance of the `Molport` class and log in to the Molport API using your username and password or API key.

```python
from molharbor import Molport

molport = Molport()
molport.login(username="john.spade", password="fasdga34a3")
```

or API key

```python
molport.login(api_key="16072de6-d318-4324-a82c-08c7dfe64d5d")
```

### Compound search

You can search for compounds using the `search` method. All the search types are supported( via `SearchType` enum).
Additionally, you can specify the maximum number of results to return using the `max_results` parameter, and the minimum similarity threshold using the `similarity` parameter (it is used only for similarity search).


#### Exact search

```python
from molharbor import SearchType

molport.find("O=C(O)c1ccccc1", search_type=SearchType.EXACT)

[
    [
        MolportCompound(
            smiles="OC(=O)c1ccccc1",
            molport_id="Molport-000-871-563",
            link="https://www.molport.com/shop/compound/Molport-000-871-563",
        ),
        MolportCompound(
            smiles="[2H]c1c([2H])c([2H])c(c([2H])c1[2H])C(O)=O",
            molport_id="Molport-003-927-939",
            link="https://www.molport.com/shop/compound/Molport-003-927-939",
        ),
        MolportCompound(
            smiles="O[13C](=O)c1ccccc1",
            molport_id="Molport-003-929-055",
            link="https://www.molport.com/shop/compound/Molport-003-929-055",
        ),
        MolportCompound(
            smiles="OC(=O)[13c]1[13cH][13cH][13cH][13cH][13cH]1",
            molport_id="Molport-046-688-787",
            link="https://www.molport.com/shop/compound/Molport-046-688-787",
        ),
    ]
]
```

#### Similarity search

```python
molport.find("O=C(O)c1ccccc1", search_type=SearchType.SIMILARITY, max_results=5)

[
    [
        MolportCompound(
            smiles="OC(=O)c1ccccc1",
            molport_id="Molport-000-871-563",
            link="https://www.molport.com/shop/compound/Molport-000-871-563",
        ),
        MolportCompound(
            smiles="[2H]c1c([2H])c([2H])c(c([2H])c1[2H])C(O)=O",
            molport_id="Molport-003-927-939",
            link="https://www.molport.com/shop/compound/Molport-003-927-939",
        ),
        MolportCompound(
            smiles="O[13C](=O)c1ccccc1",
            molport_id="Molport-003-929-055",
            link="https://www.molport.com/shop/compound/Molport-003-929-055",
        ),
        MolportCompound(
            smiles="[Zn++].[O-]C(=O)c1ccccc1.[O-]C(=O)c1ccccc1",
            molport_id="Molport-003-986-949",
            link="https://www.molport.com/shop/compound/Molport-003-986-949",
        ),
        MolportCompound(
            smiles="OC(=O)[13c]1[13cH][13cH][13cH][13cH][13cH]1",
            molport_id="Molport-046-688-787",
            link="https://www.molport.com/shop/compound/Molport-046-688-787",
        ),
    ]
]
```

#### Superstructure search

```python
molport.find("O=C(O)c1ccccc1", search_type=SearchType.SUPERSTRUCTURE, max_results=5)

[
    [
        MolportCompound(
            smiles="CC=O",
            molport_id="Molport-001-783-184",
            link="https://www.molport.com/shop/compound/Molport-001-783-184",
        ),
        MolportCompound(
            smiles="OCc1ccccc1",
            molport_id="Molport-001-783-216",
            link="https://www.molport.com/shop/compound/Molport-001-783-216",
        ),
        MolportCompound(
            smiles="C=O",
            molport_id="Molport-001-785-627",
            link="https://www.molport.com/shop/compound/Molport-001-785-627",
        ),
        MolportCompound(
            smiles="CCO",
            molport_id="Molport-001-785-844",
            link="https://www.molport.com/shop/compound/Molport-001-785-844",
        ),
        MolportCompound(
            smiles="O",
            molport_id="Molport-003-926-090",
            link="https://www.molport.com/shop/compound/Molport-003-926-090",
        ),
    ]
]
```

#### Substructure search

```python
molport.find("O=C(O)c1ccccc1", search_type=SearchType.SUBSTRUCTURE, max_results=5)

[
    [
        MolportCompound(
            smiles="OC(=O)c1ccc2[nH]c(S)nc2c1",
            molport_id="Molport-000-004-519",
            link="https://www.molport.com/shop/compound/Molport-000-004-519",
        ),
        MolportCompound(
            smiles="OC(=O)c1cc(C#N)c(Cl)cc1Cl",
            molport_id="Molport-051-434-827",
            link="https://www.molport.com/shop/compound/Molport-051-434-827",
        ),
        MolportCompound(
            smiles="OC(=O)c1ccc(cc1)-c1ccc(cc1)-c1ccc(cc1)N(c1ccc(cc1)-c1ccc(cc1)-c1ccc(cc1)C(O)=O)c1ccc(cc1)-c1ccc(cc1)-c1ccc(cc1)C(O)=O",
            molport_id="Molport-051-434-831",
            link="https://www.molport.com/shop/compound/Molport-051-434-831",
        ),
        MolportCompound(
            smiles="COC(=O)[C@]1(C[C@H](OC(C)=O)[C@@H](NC(C)=O)[C@@H](O1)[C@H](OC(C)=O)[C@@H](COC(C)=O)OC(C)=O)O[C@H]1[C@@H](OC(=O)c2ccccc2)[C@@H](COC(=O)c2ccccc2)O[C@@H](Oc2ccc(OC)cc2)[C@@H]1OC(=O)c1ccccc1",
            molport_id="Molport-051-434-926",
            link="https://www.molport.com/shop/compound/Molport-051-434-926",
        ),
        MolportCompound(
            smiles="[Na+].[Na+].OC(=O)CN(CC([O-])=O)Cc1cc2c(Oc3cc(O)c(CN(CC(O)=O)CC([O-])=O)cc3C22OC(=O)c3ccccc23)cc1O",
            molport_id="Molport-051-435-130",
            link="https://www.molport.com/shop/compound/Molport-051-435-130",
        ),
    ]
]
```

#### Raw response manipulation

MolHarbour design to simplify commonn tasks so `.find()` method returns a list of `MolportCompound` objects (which itself is a dataclass object with `smiles`, `molport_id` and `link` field). 

However, you can access the raw response using the `return_response` parameter. Returned `Response` object inherits from Pydantic `BaseModel` and contains all the fields from the Molport API response with type validation provided by Pydantic.
All the fields have the same name as in Molport API docs, only lowercase and the spaces are replaced with underscores( e.g. `Shipment Type` -> `shipment_type`)

```python
from molharbor import SearchType

molport.find(
    "O=C(O)c1ccccc1",
    search_type=SearchType.SUBSTRUCTURE,
    max_results=1,
    return_response=False,
)

[
    [
        MolportCompound(
            smiles="OC(=O)c1cc(C#N)c(Cl)cc1Cl",
            molport_id="Molport-051-434-827",
            link="https://www.molport.com/shop/compound/Molport-051-434-827",
        )
    ]
]
```
vs

```python
molport.find("O=C(O)c1ccccc1", 
              search_type=SearchType.SUBSTRUCTURE, 
              max_results=1, 
              return_response=True)

[Response(result=Result(status=1, message='Substructure search completed!'), data=Data(molecules=[Molecule(id=45........
```

### Suppliers search

Having a Molport ID, you can search for suppliers using the `get_suppliers` method. Similar to the `find()` method, you can receive either a raw Pydantic response or a processed list of `SupplierOffer` models. Each offer represents one available packing from a supplier catalogue.

```python
offers = molport.get_suppliers("Molport-001-794-639")
```

Or you could use id values of `MolportCompound` objects returned by `find()` method

```python
result = molport.find("C[C@H](CS)C(=O)N1CCC[C@H]1C(O)=O", search_type=SearchType.EXACT, max_results=1)[0]
result

MolportCompound(smiles='C[C@H](CS)C(=O)N1CCC[C@H]1C(O)=O', molport_id='Molport-001-794-639', link=...

offers = molport.get_suppliers(result.molport_id)
```
#### Processed supplier offers

The returned models contain the supplier, catalogue, and packing fields from the MolPort response with Pydantic validation.

```python
first_offer = offers[0]
first_offer.supplier_name
first_offer.price
first_offer.model_dump()
```

Applications that use pandas can construct a dataframe without making it a MolHarbor dependency:

```python
import pandas as pd

df = pd.DataFrame(offer.model_dump() for offer in offers)
```

#### Raw response

```python
molport.get_suppliers("Molport-000-871-563", return_response=True)

ResponseSupplier(result=Result(status=1, message='Molecule found!'), data=DataSupplier(molecule=Molecule2(id=871563, molport_id='Molport-000-871-563', smiles='OC(=O)c1ccccc1', .....
```
## Contributing

Contributions are welcome!

We use uv for dependency management. To install the dependencies for development, run:

```bash
uv sync
```


Please make sure to update tests as appropriate.
We use ruff for code formatting and linting. Please make sure to run it before opening a pull request.

Additionally, pre-commit hooks are set up to run ruff before each commit. To install them, run:

```bash
pre-commit install
```
Thank you for your contributions!
