Skip to main content

Blue Core Client

Test PyPI

A command line tool and Python library for the Blue Core API.

Blue Core describes library resources using BIBFRAME. Reads return JSON-LD as plain Python dictionaries; turtle, RDF/XML and N-Triples are available too.

Install

pip install bluecore-client

Or, with uv, to get the bluecore command on its own without installing into a project:

uv tool install bluecore-client

Try it

By default everything points at https://dev.bcld.info, and reading needs no credentials, so you can start looking around straight away:

bluecore search moon
Moon handbooks
  https://dev.bcld.info/works/d18a81bf-42ac-4ee5-b71b-b9de61833c62
Moon sign book
  https://dev.bcld.info/works/395f1ae0-7e93-46c0-bf76-5b5b3d98841a
Moon Atlantic Canada
  https://dev.bcld.info/works/3801e757-10a5-40e6-9fa9-af9c64ad5187

On a wide enough terminal the same results are laid out in columns. Either way the URI is never truncated, because copying it is the point.

Then follow your nose. Any command taking a UUID will also take a full Blue Core URI, so you can paste what you just found:

bluecore work view 4403fbce-ba01-5a4e-a8fc-03fc71caf56d
bluecore work view https://dev.bcld.info/works/d18a81bf-42ac-4ee5-b71b-b9de61833c62

Command line

# Look around
bluecore search moon
bluecore search moon --type works --all
bluecore search '"le mal joli"'          # quoted: match the words in order

# Read
bluecore work view <uuid-or-uri>
bluecore work view <uuid-or-uri> --expand
bluecore -o turtle work view <uuid-or-uri>
bluecore instance view <uuid-or-uri>
bluecore hub view <uuid-or-uri>
bluecore resource list
bluecore profile list

# History
bluecore changes list works
bluecore changes feed instances

# Write
bluecore work create record.jsonld
bluecore work update <uuid-or-uri> record.jsonld
bluecore work delete <uuid-or-uri>

# Load in bulk
bluecore load url https://example.org/records.jsonld
bluecore load file records.ttl        # converted to JSON-LD before upload
bluecore load file records.zip
bluecore load profiles

# Convert MARC, without storing anything
bluecore convert bibframe record.mrc
bluecore convert marcxml record.mrc

# Where am I pointed, and as whom?
bluecore whoami

Listing and paging

--all walks every page, printing results as each page arrives rather than collecting everything first, so output starts immediately:

bluecore search moon --all          # all 1095, streaming
bluecore profile list --all
bluecore changes list works --all

With --all the client asks for the largest page the endpoint allows -- 100 for search, per the API's own schema -- to keep the number of round trips down. The count at the end reports what actually printed, not what the API predicted.

When something goes wrong

--verbose narrates each request, which is the quickest way to tell a slow API from a wrong URL:

$ bluecore --verbose search moon
api      https://dev.bcld.info/api
keycloak https://dev.bcld.info/keycloak
auth     anonymous
→ GET https://dev.bcld.info/api/search/?q=moon&type=all&limit=20&offset=0
200 in 0.34s

--timeout caps how long to wait, rather than sitting there:

$ bluecore --timeout 10 search moon
✗ Could not reach https://dev.bcld.info/api (ReadTimeout: The read operation timed out)

Output serializations

-o / --output is global, so it works with any command:

-o What you get
(default) Human-readable: one record per line, or pretty JSON-LD for a single resource
json The API's own response, with total and per-record metadata
jsonld The records as one merged JSON-LD graph
turtle Turtle
rdfxml RDF/XML
ntriples N-Triples
sinopia application/vnd.sinopia+json
bluecore -o json search moon | jq -r '.results[].uri'
bluecore -o turtle work view <uuid-or-uri>
bluecore -o turtle search moon --all > moon.ttl
bluecore -o ntriples search moon --type instances

For a single resource the API does the serializing, so you get the deployment's own output. For a result set the API only offers JSON-LD, so the client merges the results into one graph and serializes that, which can't stream, unlike the default output. Change feeds are Activity Streams rather than BIBFRAME, so -o jsonld leaves them as they are and the other RDF serializations are refused.

json and jsonld differ on purpose: json is the API's response as it came, with total and each record's uri, uuid and timestamps, which is what you want for scripting. jsonld is the RDF those records describe, merged into one graph, which is the same graph as turtle, just in a different syntax.

Round tripping

Because jsonld is a graph rather than an envelope, anything you save can be loaded back:

bluecore -o turtle search moon --all > moon.ttl
bluecore load file moon.ttl

load file takes JSON-LD, turtle, RDF/XML or N-Triples. The loading workflow only reads JSON-LD, so anything else is converted before it is sent.

The same applies to a single resource:

bluecore -o jsonld work view <uuid> > work.jsonld
bluecore work update <uuid> work.jsonld

Data goes to stdout and status messages to stderr, so -o turtle ... > out.ttl gives you a clean file.

bluecore token prints an access token and nothing else, so it can be captured directly:

curl -H "Authorization: Bearer $(bluecore token)" \
  https://dev.bcld.info/api/works/<uuid>

Run bluecore --help, or bluecore <command> --help, for the rest.

Credentials

Reading is anonymous, so search, view, and list need nothing configured. Anything that writes, such as create, update, delete, load, export, convert, and token, do need credentials, and will prompt for them if it can't find any. Point it at another deployment the same way:

bluecore --bluecore-url https://bcld.info/ --username me search moon

To avoid being prompted, set these in your environment, or in a .env file in the directory you're working in:

Variable What it's for
BLUECORE_URL Root of the deployment; the API and Keycloak are derived from it
API_KEYCLOAK_USER Username
API_KEYCLOAK_PASSWORD Password
API_URL The API directly, when it isn't at BLUECORE_URL/api
KEYCLOAK_EXTERNAL_URL Keycloak directly, when it isn't at BLUECORE_URL/keycloak
BLUECORE_TOKEN An access token, instead of logging in

Setting API_KEYCLOAK_USER and API_KEYCLOAK_PASSWORD is enough to stop the prompt appearing; BLUECORE_TOKEN skips the Keycloak login as well. A real environment variable takes precedence over a .env entry, and either can be overridden per command with --username and --password.

These are the same names Blue Core API uses, so an existing .env works as-is. For a local development server, which serves the API at the bare root rather than under /api:

bluecore --api-url http://localhost:3000 \
         --keycloak-url http://localhost:8081/keycloak/ \
         search ""

Using it as a library

Everything the CLI does is available in Python, and it's meant to be useful in a Jupyter notebook.

from bluecore_client import BluecoreClient

client = BluecoreClient()
work = client.works.get("4403fbce-ba01-5a4e-a8fc-03fc71caf56d")

work["@id"]  # 'https://dev.bcld.info/works/4403fbce-...'
work["@type"]  # ['Work', 'Text', 'Monograph']

work is a dictionary. Index it, json.dumps it, put it in a dataframe.

As on the command line, a URI works anywhere a UUID does:

client.works.get("https://dev.bcld.info/works/4403fbce-...")

Connecting

client = BluecoreClient()  # the default deployment

client = BluecoreClient(  # somewhere else
    bluecore_url="https://bcld.info/",
    username="me",
    password="...",
)

client = BluecoreClient(  # a local dev server
    api_url="http://localhost:3000",
    keycloak_url="http://localhost:8081/keycloak/",
)

client = BluecoreClient(token="...")  # a token you already have

The client logs in and refreshes the token before it expires. You never have to handle one. Pass anonymous=True to skip authentication entirely, which is enough for reads. It's also a context manager, if you'd like the connection pool closed promptly:

with BluecoreClient() as client:
    ...

Reading

client.works.get(uuid)  # a dict of JSON-LD
client.works.get(uuid, expand=True)  # with referenced resources included
client.works.get(uuid, format="ttl")  # Turtle, as a string
client.instances.get(uuid)
client.instances.cbd(uuid)  # concise bounded description
client.hubs.get(uuid)
client.resources.get(resource_id)
client.profiles.get(uuid)

JSON formats come back parsed; everything else comes back as text. Available formats: jsonld, json, ttl, rdf, nt, cbd.jsonld, cbd.xml, vnd.sinopia.json.

For a result set, which the API only returns as JSON-LD, as_rdf merges the records into one graph and serializes it:

page = client.search("moon", limit=50).first()

print(client.as_rdf(page))  # turtle
print(client.as_rdf(page, "ntriples"))
client.as_rdf(client.works.get(uuid))  # a single graph works too

Or work with the graph directly:

from bluecore_client import rdf

graph = rdf.to_graph(rdf.graphs_of(page.items), context=client.context())
len(graph)  # an rdflib Graph

Searching and paging

Search returns a lazily-paged result set. Iterate it and pages are fetched as you reach them, so this is safe on a large result set:

for result in client.search("moon"):
    print(result["uri"])

Work a page at a time when you'd rather control the fetching:

pages = client.search("moon", limit=50)
first = pages.first()
first.total  # how many matches there are altogether
len(first)  # how many are on this page

for page in pages.pages():
    ...

The same applies to client.profiles.list() and client.resources.list().

Note: the API has no collection endpoint for Works, Instances, or Hubs -- there is no GET /works/ -- so there is no client.works.list(). To enumerate them, use client.search("") or read the change document feeds.

Change feeds

The Activity Streams feeds are an append-only history, which makes them the dependable way to harvest a deployment:

client.changes.feed("works")  # the collection document
client.changes.page(1, "works")  # one page
for activity in client.changes.activities():  # every activity, oldest first
    ...

Writing

Create and update take a JSON-LD graph as a dictionary. No string-encoding needed:

client.works.create(
    {
        "@context": {"@vocab": "http://id.loc.gov/ontologies/bibframe/"},
        "@type": ["Work", "Text"],
        "title": [{"@type": "Title", "mainTitle": "Le mal joli"}],
    }
)

client.works.update(uuid, graph)
client.works.delete(uuid)

Loading in bulk

Loading is handed off to Airflow, so these return a workflow id rather than waiting for the load to finish:

client.batches.from_url("https://example.org/records.jsonld")
client.batches.upload("records.ttl")  # converted to JSON-LD first
client.batches.upload("records.zip")
client.batches.upload("odd.txt", convert=False)  # send the bytes as they are

Errors

Everything raises a subclass of BluecoreError, carrying the API's own message:

from bluecore_client import NotFound

try:
    client.works.get("nope")
except NotFound as error:
    print(error)  # Work nope not found (GET .../works/nope)

AuthError, PermissionDenied, ValidationError, ConnectionFailed, and APIError are the others. APIError keeps the status_code and the underlying response; ConnectionFailed means the API couldn't be reached at all, so a timeout or refused connection never surfaces as a bare traceback.

Development

uv sync
uv run ruff format
uv run ruff check
uv run ty check
uv run pytest

CI runs those four in that order, so a formatting or typing problem fails before the suite does.

Integration tests are excluded from the default run. To run them, start Blue Core locally (see the Blue Core API README), then:

export BLUECORE_TEST_API_URL=http://localhost:3000
export KEYCLOAK_EXTERNAL_URL=http://localhost:8081/keycloak/
export API_KEYCLOAK_USER=developer
export API_KEYCLOAK_PASSWORD=123456
uv run pytest -m integration

Status

Sync only for now. An AsyncBluecoreClient is planned, as a real httpx.AsyncClient twin rather than a wrapper -- a sync facade that called asyncio.run() internally would fail inside a notebook's running event loop.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bluecore_client-0.1.0.tar.gz (72.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bluecore_client-0.1.0-py3-none-any.whl (54.5 kB view details)

Uploaded Python 3

File details

Details for the file bluecore_client-0.1.0.tar.gz.

File metadata

  • Download URL: bluecore_client-0.1.0.tar.gz
  • Upload date:
  • Size: 72.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for bluecore_client-0.1.0.tar.gz
Algorithm Hash digest
SHA256 7a72a71631eeb1b9d706e5fed886f1b088793c8b2d8c5ea17ada168c289a30a9
MD5 2a5facb650d542e52f3bdcc0886bf218
BLAKE2b-256 28083bbce83495f67369a77e1aa0bf66c8aad2b919b8c069c208cbfc1ec21188

See more details on using hashes here.

File details

Details for the file bluecore_client-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: bluecore_client-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 54.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for bluecore_client-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2d222e01b48c4f17fbb19b7dd7895eb5c4ec752080cadbec7ba823095025d65b
MD5 7b0c534ab78c6d1f5b5e33f718776ce0
BLAKE2b-256 482a2e9c960b24c2668e11f3b681a821d853e2b8cd084b0d86e7abed2de08027

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page