Metadata-Version: 2.4
Name: national-gallery-api
Version: 0.2.0
Summary: Python client for the National Gallery (London) Elasticsearch API
Keywords: national gallery,ng,api,elasticsearch,art,museum,london
Author: William Thorne
Author-email: William Thorne <wthorne1@sheffield.ac.uk>
License-Expression: Apache-2.0
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Dist: hishel>=1.2.1
Requires-Dist: httpx>=0.28.1
Requires-Dist: httpx-retries>=0.5.0
Requires-Dist: pydantic>=2.13.4
Requires-Dist: hishel[async]>=1.2.1 ; extra == 'async'
Requires-Python: >=3.12
Project-URL: Homepage, https://github.com/wrmthorne/national-gallery-api
Project-URL: Repository, https://github.com/wrmthorne/national-gallery-api
Project-URL: Issues, https://github.com/wrmthorne/national-gallery-api/issues
Provides-Extra: async
Description-Content-Type: text/markdown

# National Gallery API Wrapper

A small Python wrapper around the National Gallery (London) [Elasticsearch search endpoint](https://data.ng.ac.uk/es/public/_search). The library aims to provide:

1. A more pythonic interface with the National Gallery API
2. Plain-text rendering of records e.g. for use in LLM prompts (entity disambiguation, authority linking, etc.)

For a practical example of how this library can be used, some [example notebooks](examples/) are included. 

> National Gallery data is offered for reuse under specific [licences](https://www.nationalgallery.org.uk/documentation/ngacuk/licences).

## Setup

This package is available for [install via PyPi](https://pypi.org/project/national-gallery-api/):

```bash
# sync only
pip install national-gallery-api

# sync and async
pip install "national-gallery-api[async]"
```

## Quick start

The following entities are available: `people`, `organisations`, `works`, `events`, `exhibitions`, `places`, `locations`, `concepts`, `publications`, `archives`, `media`, and `packages`.

### Search records

```python
from national_gallery_api import NationalGallery

with NationalGallery() as ng:
    results = ng.people.search("rembrandt", actual="Individual", size=5)

    for person in results:
        print(person.title, person.pid, person.dates)
```

### Look up a single record by PID

```python
with NationalGallery() as ng:
    vincent = ng.people.get("0QCE-0001-0000-0000")
    print(vincent.title)          # Vincent van Gogh
    print(vincent.external_ids)   # ULAN, Wikidata, RKD, VIAF, ...
```

### Iterate over all results

`iter_all` lazily walks an entire result set, handling paging internally:

```python
with NationalGallery() as ng:
    for person in ng.people.iter_all(actual="Individual", page_size=100):
        ...
```

## Navigating Objects

Data objects are constructed at runtime to reflect the schemaless nature of the [CIIM model](https://docs.ciim.k-int.com/technical/how-ciim-works/) served by the API. Child models are dynamically named to be semantically informative: 

```python
with NationalGallery() as ng:
    vincent = ng.people.get("0QCE-0001-0000-0000")

    print(vincent.birth.date[0].value)   # 1853
    print(vincent.description[0].value)  # Van Gogh is today one of the most popular ...
```

The model's `__repr__` is designed to make exploring complex, deeply nested records clearer:

```
Person(
  date=[Date(from_='1853', to='1890', value='1853 - 1890')],
  summary=Summary(title='Vincent van Gogh'),
  identifier=[
    Identifier(type='PID', value='0QCE-0001-0000-0000'),
    
      # ... 375 more lines of content
      
      created=1633515854787,
      formatted='<div><p>Van Gogh is today one of the most popular of the <a href="/paintings/glossary/post-impressionism">Post-Impressio'…(+1539 chars),
      source='TMS',
      type='web text',
      value='Van Gogh is today one of the most popular of the Post-Impressionist painters, although he was not widely appreciated dur'…(+1175 chars),
      status='Active'
    )
  ]
)
```

## Caching

Caching is disabled by default. To minimise server load when making frequent repeat requests (e.g. during batch jobs), cache should be enabled:

```python
with NationalGallery(cache=True, ttl=3600, database_path="hishel_cache.db") as ng:
    ...
```

## Async

`AsyncNationalGallery` mirrors the sync client.

```python
import asyncio
from national_gallery_api import AsyncNationalGallery

async def main():
    async with AsyncNationalGallery() as ng:
        results = await ng.works.search("portrait", size=5)
        for work in results:
            ...

        async for work in ng.works.iter_all("portrait", page_size=50):
            ...

asyncio.run(main())
```

## Rendering for LLM context

```python
from national_gallery_api import NationalGallery, to_context, render_candidates

with NationalGallery() as ng:
    vincent = ng.people.get("0QCE-0001-0000-0000")
    print(to_context(vincent)) # for single entities

    candidates = ng.people.search("rembrandt", actual="Individual", size=5)
    print(render_candidates(candidates)) # for single entities or record sets
```

Example `to_context` output:

```
Person: Vincent van Gogh
  PID: 0QCE-0001-0000-0000
  Names: Vincent van Gogh; Gogh, Vincent van
  Dates: 1853 - 1890
  Roles: artist
  External IDs: http://viaf.org/viaf/9854560; http://vocab.getty.edu/ulan/500115588; https://rkd.nl/artists/32439; https://www.wikidata.org/entity/Q5582
```

## Free-text search across all types

Calling `search` on the client object with a string performs a free-text search across all fields:

```python
with NationalGallery() as ng:
    results = ng.search("van gogh", size=10)
    for entity in results:
        ...
    
    # mixed collections of entity types can still be passed
    print(render_candidates(results))
```

## Raw Elasticsearch queries

Calling `search` on the client object with an Elasticsearch body allows for custom queries, returning an unparsed response dict. This should be used for any queries or fields not handled by the typed API:

```python
with NationalGallery() as ng:
    payload = ng.search({"query": {"match_all": {}}, "size": 0})
    print(payload["hits"]["total"])
```

Query bodies can also be built with the `build_search` helper:

```python
from national_gallery_api import build_search, EntityType

body = build_search("van gogh", base=EntityType.AGENT, actual="Individual", size=10)
```