Skip to content

feat(channels): add CLI for managing publication channels#77

Merged
torbjokv merged 6 commits into
mainfrom
NP-51227-channels-cli
Jun 2, 2026
Merged

feat(channels): add CLI for managing publication channels#77
torbjokv merged 6 commits into
mainfrom
NP-51227-channels-cli

Conversation

@torbjokv

@torbjokv torbjokv commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new channels command group for managing entries in the NVA Publication Channel Registry. Designed for 3rd-line support.

  • search/get/create/update/delete subcommands
  • Type-agnostic: get, update, delete auto-detect publisher vs serial-publication
  • search merges journal/series/publisher hits into one Rich table
  • delete uses DELETE /publication-channels-v2/channel/{id} with confirmation prompt (--yes to skip)
  • Read endpoints (search, get) call API without bearer token since they are public; write endpoints use the existing backend client-credentials secret

Usage examples

# Search across all kinds (journals, series, publishers)
uv run cli.py channels search "Nature"

# Restrict to one kind and a specific year
uv run cli.py channels search "Nature" --kind publisher --year 2024
uv run cli.py channels search "Nature" --kind journal

# Fetch a single channel (kind is auto-detected)
uv run cli.py channels get 151f411d-68cd-4c7a-9cbb-daf00e0326ce
uv run cli.py channels get 151f411d-68cd-4c7a-9cbb-daf00e0326ce --year 2024

# Create channels — kind is inferred from flags
uv run cli.py channels create --name "Acme" --isbn 978-82-12                   # → publisher
uv run cli.py channels create --name "New Journal" --print-issn 1234-5678      # → serial-publication (journal)
uv run cli.py channels create --name "New Series" --print-issn 1234-5678 --kind series

# Update — type detected from the channel itself
uv run cli.py channels update 151f411d-68cd-4c7a-9cbb-daf00e0326ce --name "Updated name"
uv run cli.py channels update 151f411d-68cd-4c7a-9cbb-daf00e0326ce --isbn 978-1            # publisher
uv run cli.py channels update 151f411d-68cd-4c7a-9cbb-daf00e0326ce --print-issn 1234-5678  # serial

# Delete (asks for confirmation; --yes to skip)
uv run cli.py channels delete 151f411d-68cd-4c7a-9cbb-daf00e0326ce
uv run cli.py channels delete 151f411d-68cd-4c7a-9cbb-daf00e0326ce --yes

https://sikt.atlassian.net/browse/NP-51227

@torbjokv torbjokv requested a review from LarsV123 June 1, 2026 08:22
Comment thread commands/services/channels_api.py Outdated
Comment on lines +113 to +116
self,
name: str,
print_issn: str | None = None,
online_issn: str | None = None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary to rewrite this now, but: Would it be easier to have one or two dataclasses as the parameter in these methods? Then each method only needs one parameter and we can do the validation in the dataclass constructor.

Comment thread commands/services/channels_api.py Outdated
params = {"query": query, "offset": offset, "size": size}
if year is not None:
params["year"] = year
response = requests.get(self._channel_url(kind), params=params)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The various request calls in this file should probably have a timeout set, like in search_api.py:56.

Comment thread commands/channels.py Outdated
Comment on lines +30 to +33
return func(*args, **kwargs)
except ChannelNotFoundError as exc:
raise click.ClickException(str(exc))
except requests.HTTPError as exc:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we should be trying to handle network errors in the CLI layer (and dragging in requests to do so). I'm also not sure HTTPError covers everything that can go wrong (ConnectionError/Timeout/JSONDecodeError?). Might want to catch requests.RequestException instead.

Could move this to the service layer and map to a new ChannelApiError, same as the ChannelNotFoundError.

Comment thread commands/services/channels_api.py Outdated
Comment on lines +186 to +187
if response.text:
return response.json()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't covered by tests. If we need it, maybe add a test case?

Comment thread commands/services/channels_api.py Outdated
Comment on lines +97 to +110
def create_journal(
self,
name: str,
print_issn: str | None = None,
online_issn: str | None = None,
) -> dict:
body = _drop_none(
{
"name": name,
"printIssn": print_issn,
"onlineIssn": online_issn,
}
)
return self._post(KIND_JOURNAL, body)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't covered by tests. If we need it, maybe add a test case?

body = json.loads(put_calls[0].request.body)
assert body == {"type": "UpdateSerialPublicationRequest", "name": "New name"}


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to add another test case here to verify that ISSN gets set properly on update. Something like this for example:

  @mock_aws
  @responses.activate
  def test_update_serial_sends_issn_fields_in_body():
      _seed_aws()
      _add_cognito()
      responses.add(responses.GET, f"{SERIAL_URL}/{AN_IDENTIFIER}", json=_a_serial_hit())
      responses.add(responses.PUT, f"{SERIAL_URL}/{AN_IDENTIFIER}", status=202)

      runner = CliRunner()
      result = runner.invoke(
          channels,
          [
              "update",
              AN_IDENTIFIER,
              "--print-issn",
              "1234-5678",
              "--online-issn",
              "8765-4321",
          ],
          obj=_ctx(),
      )

      assert result.exit_code == 0, result.output
      put_calls = [c for c in responses.calls if c.request.method == "PUT"]
      assert len(put_calls) == 1
      body = json.loads(put_calls[0].request.body)
      assert body == {
          "type": "UpdateSerialPublicationRequest",
          "printIssn": "1234-5678",
          "onlineIssn": "8765-4321",
      }

@torbjokv torbjokv merged commit 5b09e2d into main Jun 2, 2026
4 checks passed
@torbjokv torbjokv deleted the NP-51227-channels-cli branch June 2, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants