Skip to main content

Structured BibTeX citations for developers of scientific software packages.

Project description

citeable 📚

Structured BibTeX citations for developers of scientific software packages.

License Coverage Status PyPI version PyPI - Python Version Ruff CodeQL Codacy Badge

Overview

citeable is a lightweight, zero-dependency, pure-Python library for defining structured bibliographic citations. The goal is to make it easy for package users to cite package developers. It is being used by cogent3 and cogent3 plugin developers to declare citations that cogent3 can assemble into a BibTeX-compatible .bib file to ensure users cite their work. But it can be used for other projects too!

Installation

pip install citeable
Requirements

Pure Python, no dependencies. Requires Python >= 3.11.

Developer quick start

from citeable import Article

cite = Article(
    author=["Huttley, Gavin", "Caley, Katherine", "McArthur, Robert"],
    title="diverse-seq: an application for alignment-free selecting and clustering biological sequences",
    journal="Journal of Open Source Software",
    year=2025,
    volume=10,
    number=110,
    pages="7765",
    doi="10.21105/joss.07765",
    url="https://doi.org/10.21105/joss.07765",
)
# cite.key == 'Huttley.2025'
@article{Huttley.2025,
  author    = {Huttley, Gavin and Caley, Katherine and McArthur, Robert},
  title     = {diverse-seq: an application for alignment-free selecting and clustering biological sequences},
  journal   = {Journal of Open Source Software},
  year      = {2025},
  volume    = {10},
  number    = {110},
  pages     = {7765},
  doi       = {10.21105/joss.07765},
  url       = {https://doi.org/10.21105/joss.07765},
}

Defining a citation

Constructing directly in Python (recommended)

Citations are constructed directly in Python source. Required fields are positional-or-keyword constructor arguments; optional fields are keyword-only with None defaults. key is always optional at construction -- it will be auto-generated if omitted (see Key generation below).

from citeable import Article, Software

cite = Article(
    author=["Huttley, Gavin", "Caley, Katherine", "McArthur, Robert"],
    title="diverse-seq: an application for alignment-free selecting and clustering biological sequences",
    journal="Journal of Open Source Software",
    year=2025,
    volume=10,
    number=110,
    pages="7765",
    doi="10.21105/joss.07765",
)
# cite.key == "Huttley.2025"

tool_cite = Software(
    author=["Smith, Jane"],
    title="my-cogent3-plugin",
    year=2024,
    version="1.0.0",
    url="https://github.com/jsmith/my-cogent3-plugin",
)
# tool_cite.key == "Smith.2024"

Validation is performed at construction time. A missing required field raises ValueError with a message identifying the field and entry type:

ValueError: Article requires 'volume'; received None
Parsing from a BibTeX string

from_bibtex_string accepts a raw BibTeX string containing a single record and returns the corresponding citeable object. This is the intended path for developers who already have a .bib entry in a reference manager -- paste the raw BibTeX string directly into Python source.

from citeable import from_bibtex_string

cite = from_bibtex_string("""
@article{Huttley.2025,
  doi       = {10.21105/joss.07765},
  url       = {https://doi.org/10.21105/joss.07765},
  year      = {2025},
  volume    = {10},
  number    = {110},
  pages     = {7765},
  author    = {Huttley, Gavin and Caley, Katherine and McArthur, Robert},
  title     = {diverse-seq: an application for alignment-free selecting and clustering biological sequences},
  journal   = {Journal of Open Source Software},
}
""")

The cite key from the BibTeX string is preserved as the key value. Author names in "First Last" format are normalised to "Last, First" on parse.

Round-trip scaffolding: use from_bibtex_string + repr() to convert a BibTeX record into a clean Python constructor call, then paste that into your source:

print(repr(cite))
Article(
    author=['Huttley, Gavin', 'Caley, Katherine', 'McArthur, Robert'],
    title='diverse-seq: an application for alignment-free selecting and clustering biological sequences',
    year=2025,
    journal='Journal of Open Source Software',
    volume=10,
    pages='7765',
    number=110,
    doi='10.21105/joss.07765',
    url='https://doi.org/10.21105/joss.07765',
)

Supported entry types

Class BibTeX @type Required fields (beyond common)
Article @article journal, volume, pages or article_number
Book @book publisher
InProceedings @inproceedings booktitle
TechReport @techreport institution
Thesis @phdthesis / @mastersthesis school, thesis_type
Software @software (none)
Misc @misc (none)

All types share common fields: author (required), title (required), year (required), doi, url, note, key, app.

Field reference

Fields common to all entry types

Field Required Notes
key No Auto-generated if not supplied
author Yes List of strings in "Surname, Given" format
title Yes
year Yes Integer
doi No Recommended where available
url No
note No
app No Name of the cogent3 app. Not written to BibTeX output; excluded from equality and hashing.

Article

Field Required
journal Yes
volume Yes
pages Yes (or article_number)
article_number No
number No -- issue number

Book

Field Required
publisher Yes
edition No
editor No -- list of strings

InProceedings

Field Required
booktitle Yes
pages No
publisher No
editor No

TechReport

Field Required
institution Yes
number No -- report number

Thesis

Field Required Notes
school Yes
thesis_type Yes "phd" or "masters" -- determines BibTeX entry type

Software

Field Required
publisher No -- organisation or individual releasing the software
version No -- strongly recommended
license No

Misc

No additional required fields beyond the common set.

Key generation

Keys are auto-generated from the first author's surname and the year, e.g. "Huttley.2025":

  1. Extract surname from the first author (before the first comma, or the last token)
  2. Strip non-ASCII characters and spaces; title-case the result
  3. Return "{surname}.{year}"

On collision, assign_unique_keys appends a lowercase letter suffix: "Smith.2024.a", "Smith.2024.b", etc.

A developer may supply an explicit key at construction time, in which case auto-generation is skipped. But note that the key attribute of a citation can be modified and cogent3 will do this if their are key conflicts.

Working with collections

Assigning unique keys

Because citations come from multiple independent plugin developers, key collisions are expected. The function assign_unique_keys resolves collisions in-place across a deduplicated list:

from citeable import assign_unique_keys

unique = assign_unique_keys(citations)
  • Deduplication by value is performed first: if two objects compare as equal, only the first is retained
  • Keys already unique in the deduplicated collection are left unchanged
  • Collisions between distinct citations sharing a base key get a letter suffix: "Smith.2024" becomes "Smith.2024.a", "Smith.2024.b", etc.
  • The function mutates surviving objects in-place and returns the deduplicated list

cogent3 calls assign_unique_keys when assembling a bibliography from a composed app, so plugin developers do not need to call it themselves.

Writing a .bib file

write_bibtex takes a list of citations and a file path, deduplicates the list, assigns unique keys, then writes the result as a valid .bib file:

from citeable import write_bibtex

write_bibtex(citations, "bibliography.bib")

For cases where only the string is needed:

unique = assign_unique_keys(citations)
bib_string = "\n\n".join(str(c) for c in unique)

Using citeable with cogent3

The define_app decorator in cogent3 has an optional cite argument:

@define_app(cite=Article(...))
class MyPlugin:
    ...

cogent3 collects citations across a composed app and expose a method (e.g. app.bibliography()) that returns a combined .bib string.

Distribution guidance

Plugin developers must define their citation as a Python object in their package source. This guarantees it is present after pip install without any special package_data configuration or MANIFEST.in entries.

The recommended pattern is a dedicated citations.py in the plugin package:

my_plugin/
    __init__.py
    citations.py   # citation objects defined here
    app.py         # @define_app(cite=MY_CITE) used here

from_bibtex_string is provided as a convenience constructor only. Either way, the result is a Python object embedded in source, not a runtime file read.

Developer setup (uv)

This project uses uv for dependency management.

Initial setup

uv sync

This creates a .venv and installs the package in editable mode with all dev dependencies.

Running tests

uv run pytest

Running nox (multi-version test matrix)

uv run nox

Nox is configured to use uv as its virtualenv backend, so it will use uv to create per-session environments.

Formatting

uv run nox -s fmt

This runs ruff check --fix-only followed by ruff format.

Other common commands

uv run mypy src/citeable --strict   # type checking
uv run ruff check .                 # linting
uv run cog -r README.md             # regenerate cog blocks

Contributing

Bug reports and pull requests are welcome at https://github.com/cogent3/citeable.

Licence

BSD-3-Clause. See LICENSE.

Project details


Download files

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

Source Distribution

citeable-2026.3.11b1.tar.gz (52.5 kB view details)

Uploaded Source

Built Distribution

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

citeable-2026.3.11b1-py3-none-any.whl (14.4 kB view details)

Uploaded Python 3

File details

Details for the file citeable-2026.3.11b1.tar.gz.

File metadata

  • Download URL: citeable-2026.3.11b1.tar.gz
  • Upload date:
  • Size: 52.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for citeable-2026.3.11b1.tar.gz
Algorithm Hash digest
SHA256 185915fb852b58b64ab82a4d90cbc91ef4033650e42e286467d1758c10edfac0
MD5 e34be55419021cab4cd1da7b148ae9ef
BLAKE2b-256 042e145346ec16350c8c1ced7cfeefb190e056928e7a39d65d86d22488be3bb6

See more details on using hashes here.

Provenance

The following attestation bundles were made for citeable-2026.3.11b1.tar.gz:

Publisher: release.yml on cogent3/citeable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file citeable-2026.3.11b1-py3-none-any.whl.

File metadata

  • Download URL: citeable-2026.3.11b1-py3-none-any.whl
  • Upload date:
  • Size: 14.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for citeable-2026.3.11b1-py3-none-any.whl
Algorithm Hash digest
SHA256 04a939569c6dd6cd0e934440562d8fc026e59e8ae05887dbfc93c0690672efa0
MD5 63a62094ac593ba2404719b0f925eb77
BLAKE2b-256 f076f208c2b7167b39ea15c116683930200a450219db8a382ecd6584779b2129

See more details on using hashes here.

Provenance

The following attestation bundles were made for citeable-2026.3.11b1-py3-none-any.whl:

Publisher: release.yml on cogent3/citeable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page