citeable 📚
Structured BibTeX citations for developers of scientific software packages.
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":
- Extract surname from the first author (before the first comma, or the last token)
- Strip non-ASCII characters and spaces; title-case the result
- 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.
Metadata
Release files for citeable 2026.9.24
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| citeable-2026.9.24.tar.gz | 52.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| citeable-2026.9.24-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 67.3 kB
Release files / citeable-2026.9.24.tar.gz
| Download URL | citeable-2026.9.24.tar.gz |
|---|---|
| Size | 52.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3dc000c88c3fc0cf9a37860bbc17f94ea56eca38bae153bd03d5988390f9f0a8
|
|
BLAKE2b-256 checksum How to use checksums |
993e2c4685f7f8b4bab551ae510cdbe09bf5681e581fa67ba17b3205f90864c3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / citeable-2026.9.24-py3-none-any.whl
| Download URL | citeable-2026.9.24-py3-none-any.whl |
|---|---|
| Size | 14.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d81a0af02b3478bfae5924ccddf12f5c10219f6ccf2399e04929463cc3340435
|
|
BLAKE2b-256 checksum How to use checksums |
483a13ba02af735b1de9b4232b30cd484ae4943310ff8a5bc933e1a33cdb75a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log