ojs 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ojs-0.7.2/.env.example +16 -0
- ojs-0.7.2/.github/dependabot.yml +10 -0
- ojs-0.7.2/.github/workflows/publish.yml +38 -0
- ojs-0.7.2/.github/workflows/test.yml +26 -0
- ojs-0.7.2/.gitignore +22 -0
- ojs-0.7.2/.pre-commit-config.yaml +23 -0
- ojs-0.7.2/.python-version +1 -0
- ojs-0.7.2/CHANGELOG.md +44 -0
- ojs-0.7.2/LICENSE +22 -0
- ojs-0.7.2/PKG-INFO +233 -0
- ojs-0.7.2/README.md +218 -0
- ojs-0.7.2/ojs/__init__.py +1 -0
- ojs-0.7.2/ojs/api/__init__.py +0 -0
- ojs-0.7.2/ojs/api/client.py +577 -0
- ojs-0.7.2/ojs/api/files.py +275 -0
- ojs-0.7.2/ojs/api/normalize.py +485 -0
- ojs-0.7.2/ojs/api/schemas.py +271 -0
- ojs-0.7.2/ojs/api/swagger.json +10374 -0
- ojs-0.7.2/ojs/api/sync.py +185 -0
- ojs-0.7.2/ojs/cli.py +680 -0
- ojs-0.7.2/ojs/py.typed +0 -0
- ojs-0.7.2/ojs/schema.py +214 -0
- ojs-0.7.2/ojs/utils.py +51 -0
- ojs-0.7.2/ojs/website/__init__.py +0 -0
- ojs-0.7.2/ojs/website/articles/__init__.py +0 -0
- ojs-0.7.2/ojs/website/articles/normalize.py +275 -0
- ojs-0.7.2/ojs/website/articles/schemas.py +186 -0
- ojs-0.7.2/ojs/website/reviews/__init__.py +0 -0
- ojs-0.7.2/ojs/website/reviews/normalize.py +50 -0
- ojs-0.7.2/ojs/website/reviews/schemas.py +102 -0
- ojs-0.7.2/pyproject.toml +65 -0
- ojs-0.7.2/tests/__init__.py +0 -0
- ojs-0.7.2/tests/test_ojs.py +1917 -0
- ojs-0.7.2/tests/test_schema.py +410 -0
- ojs-0.7.2/uv.lock +649 -0
ojs-0.7.2/.env.example
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copy to .env and fill in (or run `ojs init`). The CLI reads `.env` from the
|
|
2
|
+
# current directory, then falls back to ~/.config/ojs/.env (set OJS_CONFIG_PATH
|
|
3
|
+
# to point at a different file) for anything unset -- place this file at
|
|
4
|
+
# ~/.config/ojs/.env to use one config from any directory.
|
|
5
|
+
#
|
|
6
|
+
# .env is gitignored; keep it private -- it holds your API token. `ojs init`
|
|
7
|
+
# writes it 0600 on POSIX; on a shared host you can also `chmod 600 .env`. On
|
|
8
|
+
# Windows, file-mode restriction does not apply -- rely on directory/ACL access
|
|
9
|
+
# control.
|
|
10
|
+
|
|
11
|
+
# Required for the `ojs api` commands
|
|
12
|
+
OJS_BASE_URL=
|
|
13
|
+
OJS_API_KEY=
|
|
14
|
+
|
|
15
|
+
# Optional -- data directory override (default shown)
|
|
16
|
+
OJS_DATA_DIR=data/ojs-api
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Disabled by default. Publishing only runs once the repository variable
|
|
4
|
+
# PUBLISH_ENABLED is set to "true":
|
|
5
|
+
# gh variable set PUBLISH_ENABLED --body true # enable
|
|
6
|
+
# gh variable set PUBLISH_ENABLED --body false # disable (or `gh variable delete`)
|
|
7
|
+
# Until then, tag pushes trigger this workflow but both jobs skip.
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
tags: ["v*"]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
if: vars.PUBLISH_ENABLED == 'true'
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
- uses: astral-sh/setup-uv@v8.2.0
|
|
19
|
+
- run: uv build
|
|
20
|
+
- uses: actions/upload-artifact@v7
|
|
21
|
+
with:
|
|
22
|
+
name: dist
|
|
23
|
+
path: dist/
|
|
24
|
+
|
|
25
|
+
publish:
|
|
26
|
+
needs: build
|
|
27
|
+
if: vars.PUBLISH_ENABLED == 'true'
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
environment: pypi
|
|
30
|
+
permissions:
|
|
31
|
+
id-token: write
|
|
32
|
+
contents: read
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/download-artifact@v8
|
|
35
|
+
with:
|
|
36
|
+
name: dist
|
|
37
|
+
path: dist/
|
|
38
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [dev, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [dev, main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
- uses: astral-sh/setup-uv@v8.2.0
|
|
21
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
22
|
+
- run: uv sync --all-groups --python ${{ matrix.python-version }}
|
|
23
|
+
- run: uv run ruff check .
|
|
24
|
+
- run: uv run ruff format --check .
|
|
25
|
+
- run: uv run pyrefly check
|
|
26
|
+
- run: uv run pytest --cov --cov-report=term-missing
|
ojs-0.7.2/.gitignore
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.archive/
|
|
2
|
+
.claude/
|
|
3
|
+
.hypothesis/
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.ruff_cache/
|
|
6
|
+
.venv/
|
|
7
|
+
.vscode/
|
|
8
|
+
.worktrees/
|
|
9
|
+
|
|
10
|
+
__pycache__/
|
|
11
|
+
build/
|
|
12
|
+
dist/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
|
|
15
|
+
.DS_Store
|
|
16
|
+
|
|
17
|
+
.coverage
|
|
18
|
+
.coverage.*
|
|
19
|
+
|
|
20
|
+
.env
|
|
21
|
+
.env.*
|
|
22
|
+
!.env.example
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.15.15
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff-format
|
|
6
|
+
- id: ruff
|
|
7
|
+
args: [--fix]
|
|
8
|
+
- repo: local
|
|
9
|
+
hooks:
|
|
10
|
+
- id: pyrefly-check
|
|
11
|
+
name: pyrefly check
|
|
12
|
+
entry: uv run pyrefly check
|
|
13
|
+
language: system
|
|
14
|
+
types_or: [python, pyi]
|
|
15
|
+
pass_filenames: false
|
|
16
|
+
require_serial: true
|
|
17
|
+
- repo: local
|
|
18
|
+
hooks:
|
|
19
|
+
- id: planners-validate
|
|
20
|
+
name: validate plan frontmatter
|
|
21
|
+
entry: planners validate
|
|
22
|
+
language: system
|
|
23
|
+
files: ^\.planners/plans/[^/]+/plan\.md$
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
ojs-0.7.2/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.7.1] - 2026-06-08
|
|
11
|
+
|
|
12
|
+
- `OJS_DOWNLOADS_DIR` now defaults to `data/ojs-website` (was `$OJS_DATA_DIR/website-downloads`), placing website CSV exports beside the API data rather than nested under it.
|
|
13
|
+
|
|
14
|
+
## [0.7.0] - 2026-06-08
|
|
15
|
+
|
|
16
|
+
- Add a user-level config fallback (`~/.config/ojs/.env` by default, or the file named by `OJS_CONFIG_PATH`) for values not set in the local `.env`; drop the `ojs init --api-key` flag (the key comes only from `OJS_API_KEY` or the hidden prompt).
|
|
17
|
+
|
|
18
|
+
## [0.6.0] - 2026-06-08
|
|
19
|
+
|
|
20
|
+
- Make the CSV export globs fixed package constants (drop the `OJS_ARTICLES_GLOB` / `OJS_REVIEWS_GLOB` env vars); default the data directory to `data/ojs-api` with the API JSON at its root; infer the `submission_files` schema from all rows.
|
|
21
|
+
|
|
22
|
+
## [0.5.0] - 2026-06-05
|
|
23
|
+
|
|
24
|
+
- Add an `interval` column to the view-stats timelines so day/month points stay separable; write API JSON dumps atomically; always pull cumulative `publication_stats` in full; redact the API token from HTTP error messages; constrain file writes to the download tree; tolerate JSON `null` in normalization.
|
|
25
|
+
|
|
26
|
+
## [0.4.0] - 2026-06-05
|
|
27
|
+
|
|
28
|
+
- Add the typed schema framework (`ojs/schema.py`: `Column`/`Table`) as the runtime source of truth for normalization, with `ojs api schema` exporting `table_schemas.csv`; move the website CSV pipelines under `ojs.website`.
|
|
29
|
+
|
|
30
|
+
## [0.3.2] - 2026-06-05
|
|
31
|
+
|
|
32
|
+
- Log unservable files to `skipped.json`; drop zero-view days from the per-submission `views_timeline`; unwrap the `{items, itemsMax}` envelope for submission files.
|
|
33
|
+
|
|
34
|
+
## [0.3.0] - 2026-06-01
|
|
35
|
+
|
|
36
|
+
- Add `ojs api download` for submission file artifacts (manifest-tracked, incremental) and a journal-wide `views_timeline_totals` table; normalize empty localized values to `null`.
|
|
37
|
+
|
|
38
|
+
## [0.2.1] - 2026-05-28
|
|
39
|
+
|
|
40
|
+
- Bump runtime dependencies and dev tooling (polars, typer, ruff, pyrefly).
|
|
41
|
+
|
|
42
|
+
## [0.2.0] - 2026-05-28
|
|
43
|
+
|
|
44
|
+
- Add incremental fetch (`ojs api fetch --incremental`/`--full`, with a sync-state watermark) and publication view stats (`publication_stats`, `views_timeline`).
|
ojs-0.7.2/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ronald E. Robertson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
ojs-0.7.2/PKG-INFO
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ojs
|
|
3
|
+
Version: 0.7.2
|
|
4
|
+
Summary: Tools for working with the Open Journal Systems (OJS) API.
|
|
5
|
+
Project-URL: repository, https://github.com/gitronald/ojs
|
|
6
|
+
Author-email: gitronald <gitronald@users.noreply.github.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: httpx>=0.28.1
|
|
11
|
+
Requires-Dist: polars>=1.41.2
|
|
12
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
13
|
+
Requires-Dist: typer>=0.26.7
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# ojs
|
|
17
|
+
|
|
18
|
+
Tools for working with the Open Journal Systems (OJS) API.
|
|
19
|
+
|
|
20
|
+
Pulls submissions, publications, reviews, users, and publication view statistics
|
|
21
|
+
from an OJS journal's `/api/v1/*` REST API, downloads the attached file artifacts
|
|
22
|
+
(manuscripts, revisions, reviewer attachments, and production galleys), and
|
|
23
|
+
normalizes the JSON into typed relational tables backed by polars. Incremental
|
|
24
|
+
sync re-pulls only what changed since the last run, so routine top-ups stay
|
|
25
|
+
cheap. A typed schema layer (`Column`/`Table` classes) is the single source of
|
|
26
|
+
truth for normalization and doubles as exportable column documentation. Also
|
|
27
|
+
normalizes the OJS dashboard's Articles and Reviews CSV report exports. Ships a
|
|
28
|
+
Typer CLI for the common fetch, download, and normalize workflows. Built against
|
|
29
|
+
the [OJS 3.3 REST API](https://docs.pkp.sfu.ca/dev/api/ojs/3.3); other versions
|
|
30
|
+
are untested and may differ, as the REST API saw breaking changes between 3.2
|
|
31
|
+
and 3.3.
|
|
32
|
+
|
|
33
|
+
## Project Structure
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
ojs/
|
|
37
|
+
├── cli.py # Typer CLI: init, articles, reviews, api (+ schema docs)
|
|
38
|
+
├── schema.py # Typed schema framework: Column/Table, apply(), doc export
|
|
39
|
+
├── utils.py # HTML stripping + localized-field extraction
|
|
40
|
+
├── website/ # Manual website CSV-export pipelines
|
|
41
|
+
│ ├── articles/ # Wide CSV → submissions, authors, editors, decisions
|
|
42
|
+
│ └── reviews/ # Long CSV → reviews
|
|
43
|
+
└── api/ # REST pipeline
|
|
44
|
+
├── client.py # OJS REST client (httpx, pagination, retry, early-stop)
|
|
45
|
+
├── files.py # Submission file artifact downloads (disk layout, manifest)
|
|
46
|
+
├── normalize.py # JSON → relational tables (schema-driven)
|
|
47
|
+
├── schemas.py # API table schema classes
|
|
48
|
+
├── sync.py # Incremental sync: high-water-mark state, raw-JSON upsert
|
|
49
|
+
└── swagger.json # OJS API reference (snapshot)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv tool install ojs
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
As a project dependency:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
uv add ojs
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
From GitHub instead of PyPI:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv tool install git+https://github.com/gitronald/ojs.git
|
|
68
|
+
# or, as a dependency: uv add git+https://github.com/gitronald/ojs.git
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
From source (for development):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone https://github.com/gitronald/ojs.git
|
|
75
|
+
cd ojs
|
|
76
|
+
uv sync
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
The CLI reads from a `.env` file in the current directory. Run `ojs init` to
|
|
82
|
+
scaffold one — it prompts for the journal URL and API token, and writes `.env`
|
|
83
|
+
with `0600` permissions:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
ojs init
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Getting an API key.** In OJS, open your user profile
|
|
90
|
+
(`https://example.org/index.php/myjournal/user/profile`), select the **API Key**
|
|
91
|
+
tab, check **Enable external applications with the API key to access this
|
|
92
|
+
account**, and copy the key — use the **(re)generate** button if one isn't set
|
|
93
|
+
yet.
|
|
94
|
+
|
|
95
|
+
Values can also come from the environment. A user-level config file is loaded as
|
|
96
|
+
a fallback for anything not set in the current directory's `.env` (which takes
|
|
97
|
+
precedence): `~/.config/ojs/.env` by default, or the file named by
|
|
98
|
+
`OJS_CONFIG_PATH`.
|
|
99
|
+
|
|
100
|
+
| Variable | Default | Purpose |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| `OJS_BASE_URL` | (required for `api`) | OJS journal URL (e.g. `https://example.org/index.php/myjournal`) |
|
|
103
|
+
| `OJS_API_KEY` | (required for `api`) | OJS API token |
|
|
104
|
+
| `OJS_DATA_DIR` | `data/ojs-api` | Root for inputs and outputs |
|
|
105
|
+
| `OJS_DOWNLOADS_DIR` | `data/ojs-website` | Where CSV exports land |
|
|
106
|
+
| `OJS_ARTICLES_DIR` | `$OJS_DATA_DIR/articles` | Articles output dir |
|
|
107
|
+
| `OJS_REVIEWS_DIR` | `$OJS_DATA_DIR/reviews` | Reviews output dir |
|
|
108
|
+
| `OJS_API_DIR` | `$OJS_DATA_DIR` | API JSON dump dir |
|
|
109
|
+
| `OJS_FILES_DIR` | `$OJS_API_DIR/files` | Where downloaded submission files land |
|
|
110
|
+
|
|
111
|
+
## CLI Commands
|
|
112
|
+
|
|
113
|
+
`norm` reads the typed schema classes directly — no separate step is required.
|
|
114
|
+
`schema` exports a `table_schemas.csv` documenting each table's columns, dtypes,
|
|
115
|
+
source mapping, and whether each column appears in the normalized output
|
|
116
|
+
(`in_output`).
|
|
117
|
+
|
|
118
|
+
### API
|
|
119
|
+
|
|
120
|
+
Fetch raw JSON from the REST API, download file artifacts, and normalize into
|
|
121
|
+
relational tables.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
ojs api fetch # fetch raw JSON from the OJS REST API
|
|
125
|
+
ojs api download # download submission file artifacts (PDFs, etc.)
|
|
126
|
+
ojs api norm # normalize API JSON into relational tables
|
|
127
|
+
ojs api schema # export table_schemas.csv docs
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Articles
|
|
131
|
+
|
|
132
|
+
Normalize the OJS dashboard's Articles Report CSV export.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
ojs articles norm # normalize the most recent articles export
|
|
136
|
+
ojs articles schema # export table_schemas.csv docs
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Reviews
|
|
140
|
+
|
|
141
|
+
Normalize the OJS dashboard's Review Report CSV export.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
ojs reviews norm # normalize the most recent reviews export
|
|
145
|
+
ojs reviews schema # export table_schemas.csv docs
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Article view stats
|
|
149
|
+
|
|
150
|
+
`ojs api fetch` also pulls publication view stats from the OJS `/stats/publications/*`
|
|
151
|
+
endpoints (skip with `--no-stats`). The API only exposes aggregated counts — the
|
|
152
|
+
finest granularity is **daily** (there are no per-event timestamps).
|
|
153
|
+
|
|
154
|
+
| Flag | Default | Purpose |
|
|
155
|
+
| --- | --- | --- |
|
|
156
|
+
| `--stats / --no-stats` | on | Toggle stats collection (e.g. when the API key lacks stats access) |
|
|
157
|
+
| `--stats-interval` | `day` | Timeline granularity: `day` or `month` |
|
|
158
|
+
| `--stats-since` | (none) | `dateStart` filter (`YYYY-MM-DD`) |
|
|
159
|
+
| `--stats-until` | (none) | `dateEnd` filter (`YYYY-MM-DD`) |
|
|
160
|
+
|
|
161
|
+
`ojs api norm` then writes three extra tables:
|
|
162
|
+
|
|
163
|
+
- `publication_stats` — one row per published submission with abstract, all-galley, PDF, HTML, and other view totals.
|
|
164
|
+
- `views_timeline` — long format (`submission_id`, `date`, `interval`, `views`, `kind`) with a per-submission abstract and galley series. `interval` records the granularity (`day` or `month`) a point was fetched at, so a file mixing both stays separable — filter on it rather than summing across intervals.
|
|
165
|
+
- `views_timeline_totals` — long format (`date`, `interval`, `views`, `kind`) with the journal-wide abstract and galley series, from the aggregate `/stats/publications/{abstract,galley}` endpoints (the data behind the OJS statistics-page graph). Use this for journal-wide totals rather than summing `views_timeline`.
|
|
166
|
+
|
|
167
|
+
If the API key lacks stats access, `fetch` prints a warning and skips the stats files, and `norm` simply omits the two tables.
|
|
168
|
+
|
|
169
|
+
### Submission files
|
|
170
|
+
|
|
171
|
+
OJS attaches the actual file artifacts (manuscripts, revisions, reviewer
|
|
172
|
+
attachments, production galleys) to each submission. `ojs api download` fetches
|
|
173
|
+
their metadata and then downloads the binaries.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
ojs api fetch --files # also dump file metadata -> submission_files.json
|
|
177
|
+
ojs api download # download all files for all submissions
|
|
178
|
+
ojs api download -s 123 -s 456 # only these submissions (repeatable)
|
|
179
|
+
ojs api download --type galleys # only published galley files
|
|
180
|
+
ojs api download --type review # only review files / revisions / attachments
|
|
181
|
+
ojs api download --file-stage 4 --file-stage 15 # raw fileStage ids
|
|
182
|
+
ojs api download --no-revisions # current files only, skip prior revisions
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
| Flag | Default | Purpose |
|
|
186
|
+
| --- | --- | --- |
|
|
187
|
+
| `--submission-id` / `-s` | all | Limit to these submission ids (repeatable) |
|
|
188
|
+
| `--type` | `all` | `all`, `galleys` (published), or `review` |
|
|
189
|
+
| `--file-stage` | (none) | Raw `fileStage` id(s); overrides `--type` |
|
|
190
|
+
| `--revisions / --no-revisions` | on | Also download prior revisions of each file |
|
|
191
|
+
| `--fetch / --no-fetch` | on | Refresh file metadata first (off: use stored JSON) |
|
|
192
|
+
|
|
193
|
+
Files are laid out under `OJS_FILES_DIR` as
|
|
194
|
+
`<submission_id>/<stage>/<fileId>_<name>`. A manifest (`manifest.json`) records
|
|
195
|
+
every artifact by its immutable physical `fileId`, so reruns skip files already
|
|
196
|
+
on disk — new uploads and revisions are downloaded incrementally.
|
|
197
|
+
|
|
198
|
+
**Rounds and revisions.** OJS tracks two distinct axes. A file's *stage*
|
|
199
|
+
(`fileStage`) says where in the workflow it lives; review files additionally
|
|
200
|
+
carry an `assocId` naming the **review round** they belong to. Separately, each
|
|
201
|
+
file's `revisions[]` holds prior uploads of that same logical file. `ojs api
|
|
202
|
+
norm` writes a `submission_files` table with one row per current file, including
|
|
203
|
+
`file_stage_label`, `review_round_id` (joins to `review_assignments.round_id`),
|
|
204
|
+
and `revision_count`. Downloads cover the current file plus every revision, each
|
|
205
|
+
keyed by its own `fileId`.
|
|
206
|
+
|
|
207
|
+
Downloading files requires an API token with permission to view them; the API
|
|
208
|
+
returns `403` for files the key cannot access.
|
|
209
|
+
|
|
210
|
+
### Incremental fetch
|
|
211
|
+
|
|
212
|
+
By default `ojs api fetch` does a full cold pull. For routine top-ups, `--incremental` fetches only what changed since the last successful sync and merges it into the existing JSON dumps, so `ojs api norm` stays a stateless re-derivation from the complete files.
|
|
213
|
+
|
|
214
|
+
| Flag | Purpose |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `--incremental` / `-i` | Fetch only records changed since the last sync, merging into the JSON dumps |
|
|
217
|
+
| `--since YYYY-MM-DD` | Override the stored watermark (implies `--incremental`) |
|
|
218
|
+
| `--full` | Force a complete pull and reset the sync state |
|
|
219
|
+
|
|
220
|
+
How it works:
|
|
221
|
+
|
|
222
|
+
- A high-water mark lives in `data/ojs-api/sync_state.json` (the last sync time, plus each submission's `dateLastActivity`). It advances only after a run fully succeeds, so a failed fetch never skips records on the next run.
|
|
223
|
+
- Submissions and extended submissions are pulled newest-first by `dateLastActivity` and stop early at the watermark. Publication details are skipped for submissions whose `dateLastActivity` is unchanged — the biggest saving, since that endpoint costs one request per submission.
|
|
224
|
+
- A one-day overlap buffer re-pulls the boundary on each run; merges are idempotent (upsert by id), so the overlap is harmless.
|
|
225
|
+
- View stats: `publication_stats` (cumulative totals) is always pulled in full, while the daily `views_timeline` is re-pulled over a rolling window and merged by `(submission_id, interval, date, kind)`, refreshing recent buckets without dropping history.
|
|
226
|
+
- Users are always pulled in full — the API exposes no recency sort for users.
|
|
227
|
+
|
|
228
|
+
The OJS API has no server-side "modified since" filter, so incremental cannot detect upstream deletions; run `ojs api fetch --full` periodically to reconcile.
|
|
229
|
+
|
|
230
|
+
## Security & privacy
|
|
231
|
+
|
|
232
|
+
- The API token lives in `.env` (the `init` prompt hides input). `.env` is gitignored — keep it out of version control and out of shared locations.
|
|
233
|
+
- The API JSON dumps contain personal data pulled from OJS: `users.json` holds user records **including email addresses**, and the author/submission tables carry author names, emails, and ORCIDs. These files are written with the process umask (typically `0644`, i.e. world-readable). On a shared or multi-user host, run with a restrictive umask (e.g. `umask 077`) or point `OJS_DATA_DIR` at a private directory so other local users can't read them.
|
ojs-0.7.2/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# ojs
|
|
2
|
+
|
|
3
|
+
Tools for working with the Open Journal Systems (OJS) API.
|
|
4
|
+
|
|
5
|
+
Pulls submissions, publications, reviews, users, and publication view statistics
|
|
6
|
+
from an OJS journal's `/api/v1/*` REST API, downloads the attached file artifacts
|
|
7
|
+
(manuscripts, revisions, reviewer attachments, and production galleys), and
|
|
8
|
+
normalizes the JSON into typed relational tables backed by polars. Incremental
|
|
9
|
+
sync re-pulls only what changed since the last run, so routine top-ups stay
|
|
10
|
+
cheap. A typed schema layer (`Column`/`Table` classes) is the single source of
|
|
11
|
+
truth for normalization and doubles as exportable column documentation. Also
|
|
12
|
+
normalizes the OJS dashboard's Articles and Reviews CSV report exports. Ships a
|
|
13
|
+
Typer CLI for the common fetch, download, and normalize workflows. Built against
|
|
14
|
+
the [OJS 3.3 REST API](https://docs.pkp.sfu.ca/dev/api/ojs/3.3); other versions
|
|
15
|
+
are untested and may differ, as the REST API saw breaking changes between 3.2
|
|
16
|
+
and 3.3.
|
|
17
|
+
|
|
18
|
+
## Project Structure
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
ojs/
|
|
22
|
+
├── cli.py # Typer CLI: init, articles, reviews, api (+ schema docs)
|
|
23
|
+
├── schema.py # Typed schema framework: Column/Table, apply(), doc export
|
|
24
|
+
├── utils.py # HTML stripping + localized-field extraction
|
|
25
|
+
├── website/ # Manual website CSV-export pipelines
|
|
26
|
+
│ ├── articles/ # Wide CSV → submissions, authors, editors, decisions
|
|
27
|
+
│ └── reviews/ # Long CSV → reviews
|
|
28
|
+
└── api/ # REST pipeline
|
|
29
|
+
├── client.py # OJS REST client (httpx, pagination, retry, early-stop)
|
|
30
|
+
├── files.py # Submission file artifact downloads (disk layout, manifest)
|
|
31
|
+
├── normalize.py # JSON → relational tables (schema-driven)
|
|
32
|
+
├── schemas.py # API table schema classes
|
|
33
|
+
├── sync.py # Incremental sync: high-water-mark state, raw-JSON upsert
|
|
34
|
+
└── swagger.json # OJS API reference (snapshot)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv tool install ojs
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
As a project dependency:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv add ojs
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
From GitHub instead of PyPI:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv tool install git+https://github.com/gitronald/ojs.git
|
|
53
|
+
# or, as a dependency: uv add git+https://github.com/gitronald/ojs.git
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
From source (for development):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git clone https://github.com/gitronald/ojs.git
|
|
60
|
+
cd ojs
|
|
61
|
+
uv sync
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Configuration
|
|
65
|
+
|
|
66
|
+
The CLI reads from a `.env` file in the current directory. Run `ojs init` to
|
|
67
|
+
scaffold one — it prompts for the journal URL and API token, and writes `.env`
|
|
68
|
+
with `0600` permissions:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
ojs init
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Getting an API key.** In OJS, open your user profile
|
|
75
|
+
(`https://example.org/index.php/myjournal/user/profile`), select the **API Key**
|
|
76
|
+
tab, check **Enable external applications with the API key to access this
|
|
77
|
+
account**, and copy the key — use the **(re)generate** button if one isn't set
|
|
78
|
+
yet.
|
|
79
|
+
|
|
80
|
+
Values can also come from the environment. A user-level config file is loaded as
|
|
81
|
+
a fallback for anything not set in the current directory's `.env` (which takes
|
|
82
|
+
precedence): `~/.config/ojs/.env` by default, or the file named by
|
|
83
|
+
`OJS_CONFIG_PATH`.
|
|
84
|
+
|
|
85
|
+
| Variable | Default | Purpose |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| `OJS_BASE_URL` | (required for `api`) | OJS journal URL (e.g. `https://example.org/index.php/myjournal`) |
|
|
88
|
+
| `OJS_API_KEY` | (required for `api`) | OJS API token |
|
|
89
|
+
| `OJS_DATA_DIR` | `data/ojs-api` | Root for inputs and outputs |
|
|
90
|
+
| `OJS_DOWNLOADS_DIR` | `data/ojs-website` | Where CSV exports land |
|
|
91
|
+
| `OJS_ARTICLES_DIR` | `$OJS_DATA_DIR/articles` | Articles output dir |
|
|
92
|
+
| `OJS_REVIEWS_DIR` | `$OJS_DATA_DIR/reviews` | Reviews output dir |
|
|
93
|
+
| `OJS_API_DIR` | `$OJS_DATA_DIR` | API JSON dump dir |
|
|
94
|
+
| `OJS_FILES_DIR` | `$OJS_API_DIR/files` | Where downloaded submission files land |
|
|
95
|
+
|
|
96
|
+
## CLI Commands
|
|
97
|
+
|
|
98
|
+
`norm` reads the typed schema classes directly — no separate step is required.
|
|
99
|
+
`schema` exports a `table_schemas.csv` documenting each table's columns, dtypes,
|
|
100
|
+
source mapping, and whether each column appears in the normalized output
|
|
101
|
+
(`in_output`).
|
|
102
|
+
|
|
103
|
+
### API
|
|
104
|
+
|
|
105
|
+
Fetch raw JSON from the REST API, download file artifacts, and normalize into
|
|
106
|
+
relational tables.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
ojs api fetch # fetch raw JSON from the OJS REST API
|
|
110
|
+
ojs api download # download submission file artifacts (PDFs, etc.)
|
|
111
|
+
ojs api norm # normalize API JSON into relational tables
|
|
112
|
+
ojs api schema # export table_schemas.csv docs
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Articles
|
|
116
|
+
|
|
117
|
+
Normalize the OJS dashboard's Articles Report CSV export.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
ojs articles norm # normalize the most recent articles export
|
|
121
|
+
ojs articles schema # export table_schemas.csv docs
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Reviews
|
|
125
|
+
|
|
126
|
+
Normalize the OJS dashboard's Review Report CSV export.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
ojs reviews norm # normalize the most recent reviews export
|
|
130
|
+
ojs reviews schema # export table_schemas.csv docs
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Article view stats
|
|
134
|
+
|
|
135
|
+
`ojs api fetch` also pulls publication view stats from the OJS `/stats/publications/*`
|
|
136
|
+
endpoints (skip with `--no-stats`). The API only exposes aggregated counts — the
|
|
137
|
+
finest granularity is **daily** (there are no per-event timestamps).
|
|
138
|
+
|
|
139
|
+
| Flag | Default | Purpose |
|
|
140
|
+
| --- | --- | --- |
|
|
141
|
+
| `--stats / --no-stats` | on | Toggle stats collection (e.g. when the API key lacks stats access) |
|
|
142
|
+
| `--stats-interval` | `day` | Timeline granularity: `day` or `month` |
|
|
143
|
+
| `--stats-since` | (none) | `dateStart` filter (`YYYY-MM-DD`) |
|
|
144
|
+
| `--stats-until` | (none) | `dateEnd` filter (`YYYY-MM-DD`) |
|
|
145
|
+
|
|
146
|
+
`ojs api norm` then writes three extra tables:
|
|
147
|
+
|
|
148
|
+
- `publication_stats` — one row per published submission with abstract, all-galley, PDF, HTML, and other view totals.
|
|
149
|
+
- `views_timeline` — long format (`submission_id`, `date`, `interval`, `views`, `kind`) with a per-submission abstract and galley series. `interval` records the granularity (`day` or `month`) a point was fetched at, so a file mixing both stays separable — filter on it rather than summing across intervals.
|
|
150
|
+
- `views_timeline_totals` — long format (`date`, `interval`, `views`, `kind`) with the journal-wide abstract and galley series, from the aggregate `/stats/publications/{abstract,galley}` endpoints (the data behind the OJS statistics-page graph). Use this for journal-wide totals rather than summing `views_timeline`.
|
|
151
|
+
|
|
152
|
+
If the API key lacks stats access, `fetch` prints a warning and skips the stats files, and `norm` simply omits the two tables.
|
|
153
|
+
|
|
154
|
+
### Submission files
|
|
155
|
+
|
|
156
|
+
OJS attaches the actual file artifacts (manuscripts, revisions, reviewer
|
|
157
|
+
attachments, production galleys) to each submission. `ojs api download` fetches
|
|
158
|
+
their metadata and then downloads the binaries.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
ojs api fetch --files # also dump file metadata -> submission_files.json
|
|
162
|
+
ojs api download # download all files for all submissions
|
|
163
|
+
ojs api download -s 123 -s 456 # only these submissions (repeatable)
|
|
164
|
+
ojs api download --type galleys # only published galley files
|
|
165
|
+
ojs api download --type review # only review files / revisions / attachments
|
|
166
|
+
ojs api download --file-stage 4 --file-stage 15 # raw fileStage ids
|
|
167
|
+
ojs api download --no-revisions # current files only, skip prior revisions
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
| Flag | Default | Purpose |
|
|
171
|
+
| --- | --- | --- |
|
|
172
|
+
| `--submission-id` / `-s` | all | Limit to these submission ids (repeatable) |
|
|
173
|
+
| `--type` | `all` | `all`, `galleys` (published), or `review` |
|
|
174
|
+
| `--file-stage` | (none) | Raw `fileStage` id(s); overrides `--type` |
|
|
175
|
+
| `--revisions / --no-revisions` | on | Also download prior revisions of each file |
|
|
176
|
+
| `--fetch / --no-fetch` | on | Refresh file metadata first (off: use stored JSON) |
|
|
177
|
+
|
|
178
|
+
Files are laid out under `OJS_FILES_DIR` as
|
|
179
|
+
`<submission_id>/<stage>/<fileId>_<name>`. A manifest (`manifest.json`) records
|
|
180
|
+
every artifact by its immutable physical `fileId`, so reruns skip files already
|
|
181
|
+
on disk — new uploads and revisions are downloaded incrementally.
|
|
182
|
+
|
|
183
|
+
**Rounds and revisions.** OJS tracks two distinct axes. A file's *stage*
|
|
184
|
+
(`fileStage`) says where in the workflow it lives; review files additionally
|
|
185
|
+
carry an `assocId` naming the **review round** they belong to. Separately, each
|
|
186
|
+
file's `revisions[]` holds prior uploads of that same logical file. `ojs api
|
|
187
|
+
norm` writes a `submission_files` table with one row per current file, including
|
|
188
|
+
`file_stage_label`, `review_round_id` (joins to `review_assignments.round_id`),
|
|
189
|
+
and `revision_count`. Downloads cover the current file plus every revision, each
|
|
190
|
+
keyed by its own `fileId`.
|
|
191
|
+
|
|
192
|
+
Downloading files requires an API token with permission to view them; the API
|
|
193
|
+
returns `403` for files the key cannot access.
|
|
194
|
+
|
|
195
|
+
### Incremental fetch
|
|
196
|
+
|
|
197
|
+
By default `ojs api fetch` does a full cold pull. For routine top-ups, `--incremental` fetches only what changed since the last successful sync and merges it into the existing JSON dumps, so `ojs api norm` stays a stateless re-derivation from the complete files.
|
|
198
|
+
|
|
199
|
+
| Flag | Purpose |
|
|
200
|
+
| --- | --- |
|
|
201
|
+
| `--incremental` / `-i` | Fetch only records changed since the last sync, merging into the JSON dumps |
|
|
202
|
+
| `--since YYYY-MM-DD` | Override the stored watermark (implies `--incremental`) |
|
|
203
|
+
| `--full` | Force a complete pull and reset the sync state |
|
|
204
|
+
|
|
205
|
+
How it works:
|
|
206
|
+
|
|
207
|
+
- A high-water mark lives in `data/ojs-api/sync_state.json` (the last sync time, plus each submission's `dateLastActivity`). It advances only after a run fully succeeds, so a failed fetch never skips records on the next run.
|
|
208
|
+
- Submissions and extended submissions are pulled newest-first by `dateLastActivity` and stop early at the watermark. Publication details are skipped for submissions whose `dateLastActivity` is unchanged — the biggest saving, since that endpoint costs one request per submission.
|
|
209
|
+
- A one-day overlap buffer re-pulls the boundary on each run; merges are idempotent (upsert by id), so the overlap is harmless.
|
|
210
|
+
- View stats: `publication_stats` (cumulative totals) is always pulled in full, while the daily `views_timeline` is re-pulled over a rolling window and merged by `(submission_id, interval, date, kind)`, refreshing recent buckets without dropping history.
|
|
211
|
+
- Users are always pulled in full — the API exposes no recency sort for users.
|
|
212
|
+
|
|
213
|
+
The OJS API has no server-side "modified since" filter, so incremental cannot detect upstream deletions; run `ojs api fetch --full` periodically to reconcile.
|
|
214
|
+
|
|
215
|
+
## Security & privacy
|
|
216
|
+
|
|
217
|
+
- The API token lives in `.env` (the `init` prompt hides input). `.env` is gitignored — keep it out of version control and out of shared locations.
|
|
218
|
+
- The API JSON dumps contain personal data pulled from OJS: `users.json` holds user records **including email addresses**, and the author/submission tables carry author names, emails, and ORCIDs. These files are written with the process umask (typically `0644`, i.e. world-readable). On a shared or multi-user host, run with a restrictive umask (e.g. `umask 077`) or point `OJS_DATA_DIR` at a private directory so other local users can't read them.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.2"
|
|
File without changes
|