Metadata-Version: 2.4
Name: splent_feature_citation
Version: 0.3.1
License-Expression: LGPL-3.0-or-later
Requires-Python: >=3.13
Description-Content-Type: text/markdown
License-File: COPYING
License-File: COPYING.LESSER
Requires-Dist: splent_framework>=1.30.0
Requires-Dist: rdflib==7.1.4
Requires-Dist: qrcode[pil]==7.4.2
Dynamic: license-file

# splent_feature_citation

The `citation` feature of a [SPLENT](https://splent.io) product line: how a
hub's datasets are cited and described, their QR codes, their identity, and
the hub's own "Cite us".

A SPLENT feature is a Python package that a product composes with others:
its models, routes, services, templates and hooks are registered when the
product starts. What this feature provides and requires is listed in the
`[tool.splent.contract]` section of `pyproject.toml`, which
`splent feature:contract --write` keeps in sync with the source.

## Use it in a product

```bash
splent feature:add splent_feature_citation
```

It requires dataset 0.8.0 or later, and serves uvlhub 2.12's addresses of
the BibTeX and the QR with dataset 0.13.0 or later, which hands them over
(see "Taking over from dataset"). It requires auth too, whose shared
throttle counts its costly answers (CT-3): the line's model gets
`citation => auth` from [docs/openhubs_spl.uvl.patch](docs/openhubs_spl.uvl.patch).

## One record, every format (CT-1)

A published dataset is described once, as a record (`record.py`), and every
format writes that record (`formats.py`):

| Key | What | Media type |
|-----|------|------------|
| `bibtex` | BibTeX | application/x-bibtex |
| `ris` | RIS | application/x-research-info-systems |
| `apa` | APA 7 | text/plain |
| `text` | How to cite it in a sentence | text/plain |
| `csl` | CSL-JSON | application/vnd.citationstyles.csl+json |
| `datacite` | DataCite JSON | application/vnd.datacite.datacite+json |
| `jsonld` | schema.org JSON-LD | application/ld+json |
| `turtle` | RDF, Turtle | text/turtle |
| `rdfxml` | RDF, RDF/XML | application/rdf+xml |

- The dates are real: issued is when the version was published
  (`published_at`), created and modified when its draft was made and last
  changed.
- A version names its concept DOI (IsVersionOf), the version before it
  (IsNewVersionOf) and after it (IsPreviousVersionOf), in DataCite, and
  `dcterms:isVersionOf`, `replaces` and `isReplacedBy` in the RDF and the
  JSON-LD.
- The article a dataset supplements is named only when a DOI or an http
  address can be read in its field (dataset's `article_identifier`), and
  dataset 0.13.0 keeps that field a bare DOI or an http(s) address.
- The description is its words, dataset's `description_text` (its
  Markdown or its inherited HTML, rendered and cleaned, D21), and as markup
  what the dataset page draws, `description_html`; with an older dataset,
  the stored HTML and its text.
- BibTeX escapes what LaTeX would run (`& % $ # _ { } ~ ^ \`), writes a name
  as "Family, Given" when its parts are known, braces the title so a style
  keeps its case, and the DOI bare.
- The RDF is a graph rdflib writes, so the Turtle and the RDF/XML are the
  same statements. What a file is comes from dataset's registry of formats,
  which uvl fills for the UVL model.
- The signposting of the landing (`metadata.link_header`) has one cite-as,
  the version's DOI, a describedby per description, an item per file, and
  RFC 5829's latest-version, predecessor-version and successor-version.

`/datasets/<id>/cite/<format>` answers a published dataset in a format,
`?download=1` as a file. A draft, a withdrawn dataset and one nobody may
read are not there (404). A feature adds a format with `register_format`;
a description may be asked for by other media types too (`aliases`: Turtle
by the `application/x-turtle` uvlhub 2.12 answered), and one that costs is
`costly` (the RDF ones, below).

## The QR code (CT-3, CT-4)

`/datasets/<id>/cite/qr.<png, jpg or svg>` is the dataset's QR code. It
carries what the dataset is cited as: its DOI's doi.org address, which
resolves wherever the hub moves, or, for a dataset with an identifier of
the hub's own (local_publication's), the landing its provider names. The
Cite dialog names it by the identifier it carries, as dataset's
`identifier_label()` does: "QR code of the DOI", or of the ID.
It is drawn once per address, format and logo, and cached in
splent_feature_redis's cache when the product has it, else in the process.
Its ETag is that key, so a browser that has it gets a 304 without the image
being looked up, and it may keep it `CITATION_QR_CACHE_SECONDS`. The logo
in its middle is `CITATION_QR_LOGO`, else dataset's `DATASET_QR_LOGO`.

### The limit on what costs (CT-3)

A QR code drawn and a dataset's Turtle or RDF/XML (rdflib's work) are the
answers that cost the most, wherever they are asked: citation's addresses,
2.12's, the landing's content negotiation. Each counts against the address
that asked (`throttle.py`), `CITATION_COSTLY_PER_MINUTE` of them a minute,
60 by default and 0 for no limit; past it the answer is a 429,
`{"error", "code": "rate_limited"}` with `Retry-After`. What a cache holds
is never counted: a QR the cache has, a browser's 304. The count is auth's
shared throttle, in the product's database, so every worker sees one budget
per address (behind a proxy, the product's `ProxyFix` gives the client's
address); without its table it counts in the process. nginx's `limit_req`
can still limit them in front.

## Identity (CT-4)

`identity.py` says what a dataset is: its one landing, `/doi/<doi>/` with
the slash, the form the QR codes and the citations carry, or, with no DOI,
the landing its provider names (`/records/<public_id>/` for
local_publication); what it is cited as, its DOI's address, else that
landing; and where it stands among the public versions of its lineage. `latest_for_concept(doi)` is the newest public version of the
lineage of a concept DOI, what `/doi/<concept DOI>` should answer 302 to.
`/sitemap/datasets.xml` lists the landing of every public version.

## Cite us (CT-2)

The hub's own references are data (`models.py`, the table
`citation_reference`), and every citing format writes them, with the DOI
bare and the ISSN checked. A product keeps them in a TOML file and names it
in `CITATION_REFERENCES_FILE`, a path from its package; the seeder and
`splent feature:citation references-load [FILE]` load it, updating each by
its key, all of a file or none:

```toml
[[reference]]
key = "uvlhub"
label = "UVLHub"
title = "UVLHub: A feature model data repository using UVL and Open Science principles"
authors = ["Romero-Organvidez, David", "Galindo, José A."]
container = "Journal of Systems and Software"
pages = "112150"
year = 2024
issn = "0164-1212"
doi = "10.1016/j.jss.2024.112150"
```

`/cite-us` lists them, `/cite-us/<key>/<format>` gives one in a format, and
the home page's "Cite us!" card (`home.aside`, `CITATION_HOME_CARD`) names
them, with a dialog that fetches the format a reader picks.

## Settings

Typed in `[tool.splent.config_schema]`, so `product:validate` checks a
product's value. The first four are the product's (`derivation`), the last
each deployment's (`deploy`).

| Variable | Type | Default | |
|---|---|---|---|
| `CITATION_QR_LOGO` | str | empty | An image in the product's static folder for the middle of the QR codes. Empty takes dataset's `DATASET_QR_LOGO` |
| `CITATION_QR_CACHE_SECONDS` | int, `0` to `31536000` | `86400` | How long a browser may keep a QR code |
| `CITATION_REFERENCES_FILE` | str | empty | The TOML file of the hub's own references, a path from the product's package (see [Cite us](#cite-us-ct-2)) |
| `CITATION_HOME_CARD` | bool | `true` | Whether the home page has the "Cite us!" card |
| `CITATION_COSTLY_PER_MINUTE` | int, `0` to `100000` | `60` | The QR codes drawn and the RDF answers one address may have a minute. 0 for no limit (see [The limit on what costs](#the-limit-on-what-costs-ct-3)) |

In the OpenHubs line citation is an optional feature of `repositorio`,
with `citation => dataset`: it describes dataset's records and fills the
slots of its dataset page, so it loads after it. Its patch adds
`citation => auth` (see [Use it in a product](#use-it-in-a-product)).
Nothing requires citation.

## What it adds to other pages

- The FAIR metadata of a published dataset's page, in its head
  (`dataset.view.head`, dataset 0.13.0): a `describedby` link to each
  description, its license and type, the record at its provider, the meta
  tags reference managers read (Highwire Press's `citation_*`, Dublin
  Core's) and its JSON-LD, written with `tojson` so no text of it ends the
  script. dataset draws the canonical landing and the cite-as it asks of
  this feature.
- The "Cite" of a published dataset, among the actions of its page
  (`dataset.view.actions`), and its dialog, which fetches the format a
  reader picks, with the QR code (`dataset.view.scripts`).
- The "Cite us!" card of the home page (`home.aside`).

## Taking over from dataset

uvlhub 2.12's addresses of the BibTeX and the QR were dataset's. They are
frozen (CO-1), and since dataset 0.13.0 they are this feature's: a feature
owns its addresses, and they answer from what renders them, escaped and
cached (CT-1, CT-3). Each keeps its methods (GET), its 2.12 file names
(`<id>_bibtex.bib`, `dataset_<id>_qr.<format>`) and its view's name, under
`citation.` (`dataset.dataset_qr_by_doi` is `citation.dataset_qr_by_doi`):

| Address | Endpoint | What |
|---------|----------|------|
| `/doi/<doi>/bibtex`, `/doi/<doi>/bibtex/` | `citation.export_bibtex_by_doi` | The BibTeX, a file; 410 or 451 for a withdrawn DOI |
| `/datasets/export-bibtex/<id>` | `citation.export_bibtex` | The same by id; 400 for its owner's draft, as 2.12 |
| `/datasets/export-bibtex-content/<id>` | `citation.export_bibtex_content` | `{content, filename}` |
| `/doi/<doi>/qr`, `/doi/<doi>/qr/` | `citation.dataset_qr_by_doi` | The QR, `?format=png`, `jpg` or `svg`, `?download=1`, cached with its ETag |
| `/datasets/<id>/qr`, `/datasets/<id>/qr/` | `citation.dataset_qr_by_id` | The same by id |

What changed in the bodies is what a client of 2.12 reads the same: the
BibTeX escapes LaTeX and carries the real year and version, its type says
`charset=utf-8`, and the QR carries the DOI's doi.org address, which leads
to the same landing.

They are registered as the app registers this feature's blueprint
(`routes.serve_v212_addresses`), and only when dataset has let go of them,
so no rule is answered twice in any order of releases:

- this feature says it takes them, `TAKES_V212_ADDRESSES`, and
  `takes_v212_addresses()` asks dataset's `serves_citation_routes()`;
- dataset 0.13.0 serves them itself, deprecated, while the product has no
  citation or an older one (its `routes_citation.py`, gone in 0.14.0), and
  lets go of them to a citation that takes them;
- dataset 0.12.0 and older has no `serves_citation_routes`: it serves them,
  and this feature registers none.

The landing, `/doi/<doi>/` and `/records/<public_id>/`, stays dataset's:
it is the dataset's page, its tombstone and its concept DOI's 302. What it
says of the dataset it asks of the service this feature registers,
`CitationService`, when the product has it:

| The landing's | What it asks citation |
|---------------|-----------------------|
| Content negotiation | `negotiate(request.accept_mimetypes)`, then `response(dataset, fmt.key)` (429 past the limit for the RDF) |
| `Link` | `link_header(dataset)`: one cite-as, the describedby, the items and the versions |
| `rel=canonical` and `rel=cite-as` of its page | `canonical_landing(dataset)`, and its DOI's address |
| Its head | the `dataset.view.head` slot this feature fills |
| The tombstone's citation | `citation_text(dataset)` |
| `/doi/<concept DOI>` | `latest_for_concept(doi)`, a 302 to its landing |

A hub without citation boots and answers its landing as its page, cited as
its DOI (`Link: rel="cite-as"`), its tombstone with dataset's own citation
line, its concept DOI from dataset's lineage, and, for one minor, the 2.12
addresses from dataset's deprecated copy.

## Develop

```bash
splent feature:test splent_feature_citation
splent feature:test splent_feature_citation --with dataset
splent feature:contract splent_feature_citation
```

The tests of 2.12's addresses skip with a dataset that still serves them
(0.12.0 and older): `--with dataset` tests them against dataset's working
copy, and so does a product that pins dataset 0.13.0. Release dataset 0.13.0
first.

## Release

```bash
splent feature:release splent_feature_citation --dry-run --bump minor
splent feature:release splent_feature_citation
```

The first command rehearses the release and changes nothing. The second
publishes it.
