podojo-cli 0.3.2__tar.gz → 0.4.0__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.
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/CHANGELOG.md +6 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/PKG-INFO +2 -1
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/pyproject.toml +2 -1
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/commands/auth.py +7 -1
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/config.py +14 -9
- podojo_cli-0.4.0/tests/conftest.py +45 -0
- podojo_cli-0.4.0/tests/test_auth.py +53 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/uv.lock +93 -1
- podojo_cli-0.3.2/tests/conftest.py +0 -18
- podojo_cli-0.3.2/tests/test_auth.py +0 -42
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/.github/workflows/publish.yml +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/.gitignore +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/CLAUDE.md +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/README.md +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/__init__.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/client.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/commands/__init__.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/commands/gdrive.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/commands/projects.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/commands/showreel.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/commands/transcripts.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/commands/usertests.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/commands/videos.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/gdrive/__init__.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/gdrive/list.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/gdrive/upload.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/main.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/video/__init__.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/src/podojo_cli/video/showreel.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/tests/test_gdrive.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/tests/test_projects.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/tests/test_showreel.py +0 -0
- {podojo_cli-0.3.2 → podojo_cli-0.4.0}/tests/test_usertests.py +0 -0
|
@@ -5,6 +5,12 @@ All notable changes to the Podojo CLI will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-04-05
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Store API key in OS credential store (Keychain/Credential Locker) via keyring instead of plaintext ~/.podojo.toml
|
|
12
|
+
- `auth login` now prompts for key with hidden input instead of accepting it as a positional argument
|
|
13
|
+
|
|
8
14
|
## [0.3.2] - 2026-04-04
|
|
9
15
|
|
|
10
16
|
### Added
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: podojo-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: CLI for the Podojo user research platform
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Python: >=3.12
|
|
7
7
|
Requires-Dist: google-api-python-client>=2.0
|
|
8
8
|
Requires-Dist: google-auth>=2.0
|
|
9
9
|
Requires-Dist: httpx>=0.28
|
|
10
|
+
Requires-Dist: keyring>=25.0
|
|
10
11
|
Requires-Dist: pyyaml>=6.0
|
|
11
12
|
Requires-Dist: rich>=13.0
|
|
12
13
|
Requires-Dist: typer>=0.15
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "podojo-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "CLI for the Podojo user research platform"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -12,6 +12,7 @@ dependencies = [
|
|
|
12
12
|
"pyyaml>=6.0",
|
|
13
13
|
"google-auth>=2.0",
|
|
14
14
|
"google-api-python-client>=2.0",
|
|
15
|
+
"keyring>=25.0",
|
|
15
16
|
]
|
|
16
17
|
|
|
17
18
|
[project.scripts]
|
|
@@ -24,8 +24,14 @@ def _verify_key(api_key: str) -> bool:
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
@app.command("login")
|
|
27
|
-
def login(
|
|
27
|
+
def login(
|
|
28
|
+
api_key: str = typer.Option(
|
|
29
|
+
None, "--key", "-k", help="Your Podojo API key (prompted if omitted)"
|
|
30
|
+
),
|
|
31
|
+
):
|
|
28
32
|
"""Save your API key and verify it works."""
|
|
33
|
+
if not api_key:
|
|
34
|
+
api_key = typer.prompt("API Key", hide_input=True)
|
|
29
35
|
console.print("Verifying key…", end=" ")
|
|
30
36
|
if not _verify_key(api_key):
|
|
31
37
|
console.print("[red]failed[/red]")
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
+
import keyring
|
|
4
5
|
import tomllib
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
KEYRING_SERVICE = "podojo-cli"
|
|
8
|
+
KEYRING_USERNAME = "api_key"
|
|
7
9
|
CONFIG_PATH = Path.home() / ".podojo.toml"
|
|
8
10
|
DEFAULT_BASE_URL = "https://podojo-fastapi-mcp.onrender.com"
|
|
9
11
|
|
|
@@ -13,21 +15,24 @@ def load_config() -> dict:
|
|
|
13
15
|
if CONFIG_PATH.exists():
|
|
14
16
|
config = tomllib.loads(CONFIG_PATH.read_text())
|
|
15
17
|
config.setdefault("base_url", os.getenv("PODOJO_BASE_URL", DEFAULT_BASE_URL))
|
|
16
|
-
|
|
18
|
+
api_key = (
|
|
19
|
+
os.getenv("PODOJO_API_KEY")
|
|
20
|
+
or keyring.get_password(KEYRING_SERVICE, KEYRING_USERNAME)
|
|
21
|
+
or ""
|
|
22
|
+
)
|
|
23
|
+
config.setdefault("api_key", api_key)
|
|
17
24
|
return config
|
|
18
25
|
|
|
19
26
|
|
|
20
27
|
def save_config(api_key: str):
|
|
21
|
-
|
|
22
|
-
if CONFIG_PATH.exists():
|
|
23
|
-
config = tomllib.loads(CONFIG_PATH.read_text())
|
|
24
|
-
config["api_key"] = api_key
|
|
25
|
-
config.setdefault("base_url", DEFAULT_BASE_URL)
|
|
26
|
-
lines = "\n".join(f'{k} = "{v}"' for k, v in config.items())
|
|
27
|
-
CONFIG_PATH.write_text(lines + "\n")
|
|
28
|
+
keyring.set_password(KEYRING_SERVICE, KEYRING_USERNAME, api_key)
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def clear_api_key():
|
|
32
|
+
try:
|
|
33
|
+
keyring.delete_password(KEYRING_SERVICE, KEYRING_USERNAME)
|
|
34
|
+
except keyring.errors.PasswordDeleteError:
|
|
35
|
+
pass
|
|
31
36
|
if not CONFIG_PATH.exists():
|
|
32
37
|
return
|
|
33
38
|
config = tomllib.loads(CONFIG_PATH.read_text())
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import keyring
|
|
4
|
+
import pytest
|
|
5
|
+
from typer.testing import CliRunner
|
|
6
|
+
|
|
7
|
+
from podojo_cli.main import app
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InMemoryKeyring(keyring.backend.KeyringBackend):
|
|
11
|
+
priority = 10
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self._store = {}
|
|
15
|
+
|
|
16
|
+
def set_password(self, service, username, password):
|
|
17
|
+
self._store[(service, username)] = password
|
|
18
|
+
|
|
19
|
+
def get_password(self, service, username):
|
|
20
|
+
return self._store.get((service, username))
|
|
21
|
+
|
|
22
|
+
def delete_password(self, service, username):
|
|
23
|
+
try:
|
|
24
|
+
del self._store[(service, username)]
|
|
25
|
+
except KeyError:
|
|
26
|
+
raise keyring.errors.PasswordDeleteError()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def runner():
|
|
31
|
+
return CliRunner()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def mock_keyring():
|
|
36
|
+
backend = InMemoryKeyring()
|
|
37
|
+
keyring.set_keyring(backend)
|
|
38
|
+
return backend
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture(autouse=True)
|
|
42
|
+
def mock_config(monkeypatch):
|
|
43
|
+
monkeypatch.setenv("PODOJO_BASE_URL", "http://test.local")
|
|
44
|
+
monkeypatch.setenv("PODOJO_API_KEY", "test-key")
|
|
45
|
+
monkeypatch.setattr("podojo_cli.config.CONFIG_PATH", Path("/nonexistent/.podojo.toml"))
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from unittest.mock import patch
|
|
2
|
+
|
|
3
|
+
from podojo_cli.config import KEYRING_SERVICE, KEYRING_USERNAME
|
|
4
|
+
from podojo_cli.main import app
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_login_valid_key(runner, httpx_mock, mock_keyring):
|
|
8
|
+
httpx_mock.add_response(
|
|
9
|
+
url="http://test.local/api/v1/projects?limit=1",
|
|
10
|
+
status_code=200,
|
|
11
|
+
json=[],
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
result = runner.invoke(app, ["auth", "login", "--key", "valid-key-abc"])
|
|
15
|
+
|
|
16
|
+
assert result.exit_code == 0
|
|
17
|
+
assert "Logged in successfully" in result.output
|
|
18
|
+
assert mock_keyring.get_password(KEYRING_SERVICE, KEYRING_USERNAME) == "valid-key-abc"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_login_prompted(runner, httpx_mock, mock_keyring):
|
|
22
|
+
httpx_mock.add_response(
|
|
23
|
+
url="http://test.local/api/v1/projects?limit=1",
|
|
24
|
+
status_code=200,
|
|
25
|
+
json=[],
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
result = runner.invoke(app, ["auth", "login"], input="prompted-key\n")
|
|
29
|
+
|
|
30
|
+
assert result.exit_code == 0
|
|
31
|
+
assert "Logged in successfully" in result.output
|
|
32
|
+
assert mock_keyring.get_password(KEYRING_SERVICE, KEYRING_USERNAME) == "prompted-key"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_login_invalid_key(runner, httpx_mock):
|
|
36
|
+
httpx_mock.add_response(
|
|
37
|
+
url="http://test.local/api/v1/projects?limit=1",
|
|
38
|
+
status_code=401,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
result = runner.invoke(app, ["auth", "login", "--key", "bad-key"])
|
|
42
|
+
|
|
43
|
+
assert result.exit_code != 0
|
|
44
|
+
assert "Invalid API key" in result.output
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_logout(runner, mock_keyring):
|
|
48
|
+
mock_keyring.set_password(KEYRING_SERVICE, KEYRING_USERNAME, "some-key")
|
|
49
|
+
|
|
50
|
+
result = runner.invoke(app, ["auth", "logout"])
|
|
51
|
+
|
|
52
|
+
assert result.exit_code == 0
|
|
53
|
+
assert mock_keyring.get_password(KEYRING_SERVICE, KEYRING_USERNAME) is None
|
|
@@ -363,6 +363,65 @@ wheels = [
|
|
|
363
363
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
364
364
|
]
|
|
365
365
|
|
|
366
|
+
[[package]]
|
|
367
|
+
name = "jaraco-classes"
|
|
368
|
+
version = "3.4.0"
|
|
369
|
+
source = { registry = "https://pypi.org/simple" }
|
|
370
|
+
dependencies = [
|
|
371
|
+
{ name = "more-itertools" },
|
|
372
|
+
]
|
|
373
|
+
sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" }
|
|
374
|
+
wheels = [
|
|
375
|
+
{ url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" },
|
|
376
|
+
]
|
|
377
|
+
|
|
378
|
+
[[package]]
|
|
379
|
+
name = "jaraco-context"
|
|
380
|
+
version = "6.1.2"
|
|
381
|
+
source = { registry = "https://pypi.org/simple" }
|
|
382
|
+
sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" }
|
|
383
|
+
wheels = [
|
|
384
|
+
{ url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" },
|
|
385
|
+
]
|
|
386
|
+
|
|
387
|
+
[[package]]
|
|
388
|
+
name = "jaraco-functools"
|
|
389
|
+
version = "4.4.0"
|
|
390
|
+
source = { registry = "https://pypi.org/simple" }
|
|
391
|
+
dependencies = [
|
|
392
|
+
{ name = "more-itertools" },
|
|
393
|
+
]
|
|
394
|
+
sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" }
|
|
395
|
+
wheels = [
|
|
396
|
+
{ url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" },
|
|
397
|
+
]
|
|
398
|
+
|
|
399
|
+
[[package]]
|
|
400
|
+
name = "jeepney"
|
|
401
|
+
version = "0.9.0"
|
|
402
|
+
source = { registry = "https://pypi.org/simple" }
|
|
403
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" }
|
|
404
|
+
wheels = [
|
|
405
|
+
{ url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" },
|
|
406
|
+
]
|
|
407
|
+
|
|
408
|
+
[[package]]
|
|
409
|
+
name = "keyring"
|
|
410
|
+
version = "25.7.0"
|
|
411
|
+
source = { registry = "https://pypi.org/simple" }
|
|
412
|
+
dependencies = [
|
|
413
|
+
{ name = "jaraco-classes" },
|
|
414
|
+
{ name = "jaraco-context" },
|
|
415
|
+
{ name = "jaraco-functools" },
|
|
416
|
+
{ name = "jeepney", marker = "sys_platform == 'linux'" },
|
|
417
|
+
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
|
|
418
|
+
{ name = "secretstorage", marker = "sys_platform == 'linux'" },
|
|
419
|
+
]
|
|
420
|
+
sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" }
|
|
421
|
+
wheels = [
|
|
422
|
+
{ url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" },
|
|
423
|
+
]
|
|
424
|
+
|
|
366
425
|
[[package]]
|
|
367
426
|
name = "markdown-it-py"
|
|
368
427
|
version = "4.0.0"
|
|
@@ -384,6 +443,15 @@ wheels = [
|
|
|
384
443
|
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
|
385
444
|
]
|
|
386
445
|
|
|
446
|
+
[[package]]
|
|
447
|
+
name = "more-itertools"
|
|
448
|
+
version = "11.0.1"
|
|
449
|
+
source = { registry = "https://pypi.org/simple" }
|
|
450
|
+
sdist = { url = "https://files.pythonhosted.org/packages/24/24/e0acc4bf54cba50c1d432c70a72a3df96db4a321b2c4c68432a60759044f/more_itertools-11.0.1.tar.gz", hash = "sha256:fefaf25b7ab08f0b45fa9f1892cae93b9fc0089ef034d39213bce15f1cc9e199", size = 144739, upload-time = "2026-04-02T16:17:45.061Z" }
|
|
451
|
+
wheels = [
|
|
452
|
+
{ url = "https://files.pythonhosted.org/packages/d8/f4/5e52c7319b8087acef603ed6e50dc325c02eaa999355414830468611f13c/more_itertools-11.0.1-py3-none-any.whl", hash = "sha256:eaf287826069452a8f61026c597eae2428b2d1ba2859083abbf240b46842ce6d", size = 72182, upload-time = "2026-04-02T16:17:43.724Z" },
|
|
453
|
+
]
|
|
454
|
+
|
|
387
455
|
[[package]]
|
|
388
456
|
name = "packaging"
|
|
389
457
|
version = "26.0"
|
|
@@ -404,12 +472,13 @@ wheels = [
|
|
|
404
472
|
|
|
405
473
|
[[package]]
|
|
406
474
|
name = "podojo-cli"
|
|
407
|
-
version = "0.3.
|
|
475
|
+
version = "0.3.2"
|
|
408
476
|
source = { editable = "." }
|
|
409
477
|
dependencies = [
|
|
410
478
|
{ name = "google-api-python-client" },
|
|
411
479
|
{ name = "google-auth" },
|
|
412
480
|
{ name = "httpx" },
|
|
481
|
+
{ name = "keyring" },
|
|
413
482
|
{ name = "pyyaml" },
|
|
414
483
|
{ name = "rich" },
|
|
415
484
|
{ name = "typer" },
|
|
@@ -426,6 +495,7 @@ requires-dist = [
|
|
|
426
495
|
{ name = "google-api-python-client", specifier = ">=2.0" },
|
|
427
496
|
{ name = "google-auth", specifier = ">=2.0" },
|
|
428
497
|
{ name = "httpx", specifier = ">=0.28" },
|
|
498
|
+
{ name = "keyring", specifier = ">=25.0" },
|
|
429
499
|
{ name = "pyyaml", specifier = ">=6.0" },
|
|
430
500
|
{ name = "rich", specifier = ">=13.0" },
|
|
431
501
|
{ name = "typer", specifier = ">=0.15" },
|
|
@@ -541,6 +611,15 @@ wheels = [
|
|
|
541
611
|
{ url = "https://files.pythonhosted.org/packages/e2/d2/1eb1ea9c84f0d2033eb0b49675afdc71aa4ea801b74615f00f3c33b725e3/pytest_httpx-0.36.0-py3-none-any.whl", hash = "sha256:bd4c120bb80e142df856e825ec9f17981effb84d159f9fa29ed97e2357c3a9c8", size = 20229, upload-time = "2025-12-02T16:34:56.45Z" },
|
|
542
612
|
]
|
|
543
613
|
|
|
614
|
+
[[package]]
|
|
615
|
+
name = "pywin32-ctypes"
|
|
616
|
+
version = "0.2.3"
|
|
617
|
+
source = { registry = "https://pypi.org/simple" }
|
|
618
|
+
sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" }
|
|
619
|
+
wheels = [
|
|
620
|
+
{ url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" },
|
|
621
|
+
]
|
|
622
|
+
|
|
544
623
|
[[package]]
|
|
545
624
|
name = "pyyaml"
|
|
546
625
|
version = "6.0.3"
|
|
@@ -627,6 +706,19 @@ wheels = [
|
|
|
627
706
|
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
|
|
628
707
|
]
|
|
629
708
|
|
|
709
|
+
[[package]]
|
|
710
|
+
name = "secretstorage"
|
|
711
|
+
version = "3.5.0"
|
|
712
|
+
source = { registry = "https://pypi.org/simple" }
|
|
713
|
+
dependencies = [
|
|
714
|
+
{ name = "cryptography" },
|
|
715
|
+
{ name = "jeepney" },
|
|
716
|
+
]
|
|
717
|
+
sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" }
|
|
718
|
+
wheels = [
|
|
719
|
+
{ url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
|
|
720
|
+
]
|
|
721
|
+
|
|
630
722
|
[[package]]
|
|
631
723
|
name = "shellingham"
|
|
632
724
|
version = "1.5.4"
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from typer.testing import CliRunner
|
|
5
|
-
|
|
6
|
-
from podojo_cli.main import app
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@pytest.fixture
|
|
10
|
-
def runner():
|
|
11
|
-
return CliRunner()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@pytest.fixture(autouse=True)
|
|
15
|
-
def mock_config(monkeypatch):
|
|
16
|
-
monkeypatch.setenv("PODOJO_BASE_URL", "http://test.local")
|
|
17
|
-
monkeypatch.setenv("PODOJO_API_KEY", "test-key")
|
|
18
|
-
monkeypatch.setattr("podojo_cli.config.CONFIG_PATH", Path("/nonexistent/.podojo.toml"))
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from unittest.mock import patch
|
|
2
|
-
|
|
3
|
-
from podojo_cli.main import app
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_login_valid_key(runner, httpx_mock, tmp_path):
|
|
7
|
-
httpx_mock.add_response(
|
|
8
|
-
url="http://test.local/api/v1/projects?limit=1",
|
|
9
|
-
status_code=200,
|
|
10
|
-
json=[],
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
config_path = tmp_path / ".podojo.toml"
|
|
14
|
-
with patch("podojo_cli.config.CONFIG_PATH", config_path):
|
|
15
|
-
result = runner.invoke(app, ["auth", "login", "valid-key-abc"])
|
|
16
|
-
|
|
17
|
-
assert result.exit_code == 0
|
|
18
|
-
assert "Logged in successfully" in result.output
|
|
19
|
-
assert 'api_key = "valid-key-abc"' in config_path.read_text()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def test_login_invalid_key(runner, httpx_mock):
|
|
23
|
-
httpx_mock.add_response(
|
|
24
|
-
url="http://test.local/api/v1/projects?limit=1",
|
|
25
|
-
status_code=401,
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
result = runner.invoke(app, ["auth", "login", "bad-key"])
|
|
29
|
-
|
|
30
|
-
assert result.exit_code != 0
|
|
31
|
-
assert "Invalid API key" in result.output
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def test_logout(runner, tmp_path):
|
|
35
|
-
config_path = tmp_path / ".podojo.toml"
|
|
36
|
-
config_path.write_text('api_key = "some-key"\n')
|
|
37
|
-
|
|
38
|
-
with patch("podojo_cli.config.CONFIG_PATH", config_path):
|
|
39
|
-
result = runner.invoke(app, ["auth", "logout"])
|
|
40
|
-
|
|
41
|
-
assert result.exit_code == 0
|
|
42
|
-
assert "api_key" not in config_path.read_text() if config_path.exists() else True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|