Compare commits
10 Commits
ac29893867
...
082de3d5ea
| Author | SHA1 | Date | |
|---|---|---|---|
| 082de3d5ea | |||
| e158946211 | |||
| f1eb64c102 | |||
| cec8908e96 | |||
| 9d89a5f48c | |||
| afb10313c5 | |||
| eba3bc4955 | |||
| eb7fad2477 | |||
| d8f37c6e70 | |||
| 2ed361698e |
@@ -15,16 +15,20 @@ jobs:
|
||||
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Install build dependencies
|
||||
enable-cache: true
|
||||
- name: Setup project
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r scripts/requirements.txt pyinstaller
|
||||
uv python install 3.13
|
||||
make setup
|
||||
- name: Build binary
|
||||
run: |
|
||||
pyinstaller --onefile --name planka-cli scripts/planka_cli.py
|
||||
make build
|
||||
- name: Smoke test binary
|
||||
run: |
|
||||
./dist/planka-cli --help
|
||||
- name: Package tarball
|
||||
run: |
|
||||
VERSION="${GITHUB_REF_NAME#v}"
|
||||
@@ -49,5 +53,4 @@ jobs:
|
||||
brew tap voydz/homebrew-tap
|
||||
brew bump-formula-pr planka-cli \
|
||||
--url "$URL" \
|
||||
--sha256 "$SHA256" \
|
||||
--version "$VERSION"
|
||||
--sha256 "$SHA256"
|
||||
|
||||
+1
-2
@@ -28,5 +28,4 @@ htmlcov/
|
||||
.idea/
|
||||
.DS_Store
|
||||
|
||||
# PyInstaller
|
||||
*.spec
|
||||
# PyInstaller (planka-cli.spec is committed for reproducible builds)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
.PHONY: setup run build clean
|
||||
.PHONY: setup run build smoke clean lint lint-fix test check
|
||||
|
||||
.DEFAULT_GOAL := check
|
||||
|
||||
setup:
|
||||
uv venv
|
||||
@@ -7,8 +9,31 @@ setup:
|
||||
run:
|
||||
uv run python scripts/planka_cli.py status
|
||||
|
||||
lint:
|
||||
uv run ruff check scripts/ tests/
|
||||
uv run ruff format --check scripts/ tests/
|
||||
|
||||
lint-fix:
|
||||
uv run ruff check --fix scripts/ tests/
|
||||
uv run ruff format scripts/ tests/
|
||||
|
||||
test:
|
||||
uv run pytest tests/ -v
|
||||
|
||||
check: lint test
|
||||
|
||||
build:
|
||||
uv run pyinstaller --onefile --name planka-cli scripts/planka_cli.py
|
||||
uv run pyinstaller planka-cli.spec --noconfirm
|
||||
|
||||
smoke: build
|
||||
@set -e; \
|
||||
tmp_home="$$(mktemp -d)"; \
|
||||
trap 'rm -rf "$$tmp_home"' EXIT; \
|
||||
env -i PATH="/usr/bin:/bin:/usr/sbin:/sbin" HOME="$$tmp_home" \
|
||||
PYTHONNOUSERSITE=1 PYTHONPATH= PYTHONHOME= \
|
||||
VIRTUAL_ENV= CONDA_PREFIX= CONDA_DEFAULT_ENV= PIPENV_ACTIVE= \
|
||||
PYENV_VERSION= UV_PROJECT_ENV= \
|
||||
./dist/planka-cli --help
|
||||
|
||||
clean:
|
||||
rm -rf dist build *.spec __pycache__ scripts/__pycache__
|
||||
rm -rf dist build __pycache__ scripts/__pycache__ tests/__pycache__ .pytest_cache
|
||||
|
||||
@@ -15,7 +15,7 @@ scan work, create cards, and stay on top of notifications with fast, scriptable
|
||||
## Quick start
|
||||
|
||||
Requirements: A Planka account. For Homebrew installs, macOS + Homebrew. For source/pipx
|
||||
installs, Python 3.10+.
|
||||
installs, Python 3.11+.
|
||||
|
||||
### Install (Homebrew, recommended)
|
||||
|
||||
@@ -47,9 +47,10 @@ planka-cli boards list
|
||||
|
||||
## Authentication
|
||||
|
||||
`login` stores credentials in a `.env` file next to the `planka-cli` binary (or next to
|
||||
`scripts/planka_cli.py` when running from source). You can also set credentials via
|
||||
environment variables:
|
||||
`login` stores credentials in `~/.config/planka-cli/tokens/credentials.json` by default.
|
||||
Override with `--tokenstore PATH` or the `PLANKATOKENS` environment variable. You can also
|
||||
set credentials via environment variables. Note: `--tokenstore` is a global option and
|
||||
must appear before the command name.
|
||||
|
||||
```bash
|
||||
export PLANKA_URL=https://planka.example
|
||||
@@ -69,6 +70,7 @@ planka-cli projects list
|
||||
planka-cli boards list [PROJECT_ID]
|
||||
planka-cli lists list <BOARD_ID>
|
||||
planka-cli cards list <LIST_ID>
|
||||
planka-cli cards show <CARD_ID>
|
||||
|
||||
planka-cli cards create <LIST_ID> "Card title" --description "Details"
|
||||
planka-cli cards update <CARD_ID> --name "New title"
|
||||
@@ -102,6 +104,9 @@ pip install -e .
|
||||
```bash
|
||||
make setup
|
||||
make run
|
||||
make lint
|
||||
make test
|
||||
make check
|
||||
make build
|
||||
```
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ This skill provides a CLI wrapper around the `plankapy` library to interact with
|
||||
brew install planka-cli
|
||||
```
|
||||
|
||||
Source/pipx installs require Python 3.11+ to use plankapy v2.
|
||||
|
||||
2. **Configuration:**
|
||||
Use the `login` command to store credentials:
|
||||
```bash
|
||||
@@ -52,6 +54,9 @@ planka-cli lists list <BOARD_ID>
|
||||
# List Cards in a List
|
||||
planka-cli cards list <LIST_ID>
|
||||
|
||||
# Show a Card (includes attachments with URLs and comment text)
|
||||
planka-cli cards show <CARD_ID>
|
||||
|
||||
# Create a Card
|
||||
planka-cli cards create <LIST_ID> "Card title"
|
||||
|
||||
@@ -80,6 +85,11 @@ planka-cli boards list
|
||||
planka-cli cards list 1619901252164912136
|
||||
```
|
||||
|
||||
**Show card details for card ID 1619901252164912137:**
|
||||
```bash
|
||||
planka-cli cards show 1619901252164912137
|
||||
```
|
||||
|
||||
**Create a card in list ID 1619901252164912136:**
|
||||
```bash
|
||||
planka-cli cards create 1619901252164912136 "Ship CLI"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
from PyInstaller.utils.hooks import collect_submodules
|
||||
from PyInstaller.utils.hooks import collect_all
|
||||
|
||||
datas = []
|
||||
binaries = []
|
||||
hiddenimports = ['plankapy.v2', 'plankapy.v2.interface', 'plankapy.v2.models', 'plankapy.v2.api', 'plankapy.v2.utils']
|
||||
hiddenimports += collect_submodules('plankapy.v2.models')
|
||||
hiddenimports += collect_submodules('plankapy.v2.api')
|
||||
hiddenimports += collect_submodules('rich._unicode_data')
|
||||
tmp_ret = collect_all('plankapy.v2')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['scripts/planka_cli.py'],
|
||||
pathex=[],
|
||||
binaries=binaries,
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=['scripts/pyi_rth_plankapy.py'],
|
||||
excludes=['plankapy.v1'],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='planka-cli',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
+16
-3
@@ -6,11 +6,10 @@ build-backend = "setuptools.build_meta"
|
||||
name = "planka-cli"
|
||||
version = "0.1.0"
|
||||
description = "CLI for Planka using plankapy."
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"plankapy",
|
||||
"plankapy>=2.2.2",
|
||||
"typer",
|
||||
"python-dotenv",
|
||||
"rich",
|
||||
]
|
||||
|
||||
@@ -20,7 +19,21 @@ planka-cli = "scripts.planka_cli:app"
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pyinstaller",
|
||||
"ruff",
|
||||
"pytest",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py311"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I", "W"]
|
||||
ignore = ["E501"] # Line too long - handled by formatter
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py"]
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["scripts"]
|
||||
|
||||
+362
-53
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
@@ -7,29 +8,17 @@ from typing import Optional, Union
|
||||
|
||||
import click
|
||||
import typer
|
||||
from dotenv import load_dotenv, set_key
|
||||
from plankapy.v2 import Card, Planka
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from typer.core import TyperGroup
|
||||
|
||||
def get_binary_dir() -> Path:
|
||||
argv0 = sys.argv[0]
|
||||
path = Path(argv0)
|
||||
if not path.is_absolute():
|
||||
resolved = shutil.which(argv0)
|
||||
if resolved:
|
||||
path = Path(resolved)
|
||||
if path.exists():
|
||||
return path.resolve().parent if path.is_file() else path.resolve()
|
||||
return Path(__file__).resolve().parent
|
||||
TOKEN_ENV_VAR = "PLANKATOKENS"
|
||||
DEFAULT_TOKEN_DIR = Path.home() / ".config" / "planka-cli" / "tokens"
|
||||
CREDENTIALS_FILENAME = "credentials.json"
|
||||
TOKENSTORE_OVERRIDE: Optional[str] = None
|
||||
|
||||
|
||||
ENV_PATH = get_binary_dir() / ".env"
|
||||
|
||||
# Load environment variables from the .env next to the binary/script.
|
||||
load_dotenv(dotenv_path=ENV_PATH)
|
||||
|
||||
class HelpOnUnknownCommandGroup(TyperGroup):
|
||||
def get_command(self, ctx: click.Context, cmd_name: str):
|
||||
command = super().get_command(ctx, cmd_name)
|
||||
@@ -52,18 +41,71 @@ app.add_typer(lists_app, name="lists")
|
||||
app.add_typer(cards_app, name="cards")
|
||||
app.add_typer(notifications_app, name="notifications")
|
||||
|
||||
|
||||
@app.callback(invoke_without_command=True)
|
||||
def main(ctx: typer.Context):
|
||||
def main(
|
||||
ctx: typer.Context,
|
||||
tokenstore: Optional[str] = typer.Option(None, "--tokenstore", help="Token storage path."),
|
||||
):
|
||||
"""Planka CLI."""
|
||||
global TOKENSTORE_OVERRIDE
|
||||
TOKENSTORE_OVERRIDE = tokenstore
|
||||
if ctx.invoked_subcommand is None:
|
||||
click.echo(ctx.get_help())
|
||||
|
||||
|
||||
def get_token_dir(tokenstore: Optional[str] = None) -> Path:
|
||||
if tokenstore:
|
||||
return Path(tokenstore).expanduser().resolve()
|
||||
if TOKENSTORE_OVERRIDE:
|
||||
return Path(TOKENSTORE_OVERRIDE).expanduser().resolve()
|
||||
env = os.environ.get(TOKEN_ENV_VAR)
|
||||
if env:
|
||||
return Path(env).expanduser().resolve()
|
||||
return DEFAULT_TOKEN_DIR
|
||||
|
||||
|
||||
def get_credentials_path(tokenstore: Optional[str] = None) -> Path:
|
||||
return get_token_dir(tokenstore) / CREDENTIALS_FILENAME
|
||||
|
||||
|
||||
def load_stored_credentials(tokenstore: Optional[str] = None) -> dict[str, str]:
|
||||
credentials_path = get_credentials_path(tokenstore)
|
||||
if not credentials_path.exists():
|
||||
return {}
|
||||
try:
|
||||
data = json.loads(credentials_path.read_text())
|
||||
except json.JSONDecodeError as exc:
|
||||
console.print(
|
||||
f"[bold red]Error:[/bold red] Invalid credentials file at {credentials_path}: {exc}"
|
||||
)
|
||||
raise typer.Exit(1) from exc
|
||||
if not isinstance(data, dict):
|
||||
console.print(
|
||||
f"[bold red]Error:[/bold red] Credentials file at {credentials_path} must be a JSON object."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
return {str(key): str(value) for key, value in data.items()}
|
||||
|
||||
|
||||
def get_env_config() -> tuple[Optional[str], Optional[str], Optional[str]]:
|
||||
return (
|
||||
os.getenv("PLANKA_URL"),
|
||||
os.getenv("PLANKA_USERNAME"),
|
||||
os.getenv("PLANKA_PASSWORD"),
|
||||
)
|
||||
planka_url = os.getenv("PLANKA_URL")
|
||||
planka_username = os.getenv("PLANKA_USERNAME")
|
||||
planka_password = os.getenv("PLANKA_PASSWORD")
|
||||
|
||||
if planka_url and planka_username and planka_password:
|
||||
return planka_url, planka_username, planka_password
|
||||
|
||||
stored = load_stored_credentials()
|
||||
if not planka_url:
|
||||
planka_url = stored.get("PLANKA_URL")
|
||||
if not planka_username:
|
||||
planka_username = stored.get("PLANKA_USERNAME")
|
||||
if not planka_password:
|
||||
planka_password = stored.get("PLANKA_PASSWORD")
|
||||
|
||||
return planka_url, planka_username, planka_password
|
||||
|
||||
|
||||
def parse_iso_datetime(value: Optional[str]) -> Optional[datetime]:
|
||||
if value is None:
|
||||
@@ -78,6 +120,7 @@ def parse_iso_datetime(value: Optional[str]) -> Optional[datetime]:
|
||||
"Invalid datetime. Use ISO-8601 like 2025-01-31 or 2025-01-31T10:30:00Z."
|
||||
) from exc
|
||||
|
||||
|
||||
def parse_position(value: Optional[str]) -> Optional[Union[str, int]]:
|
||||
if value is None:
|
||||
return None
|
||||
@@ -89,6 +132,7 @@ def parse_position(value: Optional[str]) -> Optional[Union[str, int]]:
|
||||
except ValueError as exc:
|
||||
raise typer.BadParameter("Position must be 'top', 'bottom', or an integer.") from exc
|
||||
|
||||
|
||||
def find_list(planka: Planka, list_id: str):
|
||||
for project in planka.projects:
|
||||
for board in project.boards:
|
||||
@@ -97,6 +141,17 @@ def find_list(planka: Planka, list_id: str):
|
||||
return list_item
|
||||
return None
|
||||
|
||||
|
||||
def find_list_with_board(planka: Planka, list_id: str):
|
||||
"""Find a list and return both the list and its parent board."""
|
||||
for project in planka.projects:
|
||||
for board in project.boards:
|
||||
for list_item in board.lists:
|
||||
if list_item.id == list_id:
|
||||
return list_item, board
|
||||
return None, None
|
||||
|
||||
|
||||
def get_card_by_id(planka: Planka, card_id: str) -> Optional[Card]:
|
||||
try:
|
||||
card_data = planka.endpoints.getCard(card_id)["item"]
|
||||
@@ -104,6 +159,7 @@ def get_card_by_id(planka: Planka, card_id: str) -> Optional[Card]:
|
||||
return None
|
||||
return Card(card_data, planka)
|
||||
|
||||
|
||||
def make_table(title: str) -> Table:
|
||||
return Table(
|
||||
title=title,
|
||||
@@ -114,6 +170,7 @@ def make_table(title: str) -> Table:
|
||||
box=None,
|
||||
)
|
||||
|
||||
|
||||
def render_notifications(title: str, notifications: list) -> None:
|
||||
if not notifications:
|
||||
console.print("No notifications found.")
|
||||
@@ -138,19 +195,20 @@ def render_notifications(title: str, notifications: list) -> None:
|
||||
|
||||
console.print(table)
|
||||
|
||||
|
||||
def get_planka() -> Planka:
|
||||
planka_url, planka_username, planka_password = get_env_config()
|
||||
if not planka_url or not planka_username or not planka_password:
|
||||
credentials_path = get_credentials_path()
|
||||
console.print(
|
||||
"[bold red]Error:[/bold red] Missing credentials. "
|
||||
f"Set PLANKA_URL, PLANKA_USERNAME, and PLANKA_PASSWORD or run "
|
||||
f"`planka-cli login --url ... --username ... --password ...` "
|
||||
f"to write {ENV_PATH}."
|
||||
f"to write {credentials_path}."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
try:
|
||||
# Initialize Planka client (v2) and authenticate
|
||||
planka = Planka(planka_url)
|
||||
planka.login(username=planka_username, password=planka_password)
|
||||
return planka
|
||||
@@ -158,57 +216,74 @@ def get_planka() -> Planka:
|
||||
console.print(f"[bold red]Connection Error:[/bold red] {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@app.command()
|
||||
def login(
|
||||
url: str = typer.Option(..., "--url", "-u", help="Planka base URL"),
|
||||
username: str = typer.Option(..., "--username", "-n", help="Planka username"),
|
||||
password: str = typer.Option(..., "--password", "-p", help="Planka password"),
|
||||
):
|
||||
"""Store credentials in a .env file next to the binary."""
|
||||
"""Store credentials in ~/.config/planka-cli/tokens/credentials.json."""
|
||||
credentials_path = get_credentials_path()
|
||||
try:
|
||||
ENV_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
set_key(str(ENV_PATH), "PLANKA_URL", url)
|
||||
set_key(str(ENV_PATH), "PLANKA_USERNAME", username)
|
||||
set_key(str(ENV_PATH), "PLANKA_PASSWORD", password)
|
||||
credentials_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
credentials_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"PLANKA_URL": url,
|
||||
"PLANKA_USERNAME": username,
|
||||
"PLANKA_PASSWORD": password,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
except OSError as e:
|
||||
console.print(f"[bold red]Error:[/bold red] Could not write {ENV_PATH}: {e}")
|
||||
console.print(f"[bold red]Error:[/bold red] Could not write {credentials_path}: {e}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
try:
|
||||
os.chmod(ENV_PATH, 0o600)
|
||||
os.chmod(credentials_path, 0o600)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
console.print(f"[green]Saved credentials to[/green] {ENV_PATH}")
|
||||
console.print(f"[green]Saved credentials to[/green] {credentials_path}")
|
||||
|
||||
|
||||
@app.command()
|
||||
def logout():
|
||||
"""Delete the stored .env file next to the binary."""
|
||||
if not ENV_PATH.exists():
|
||||
console.print(f"No stored credentials found at {ENV_PATH}")
|
||||
"""Delete the stored ~/.config/planka-cli/tokens/credentials.json file."""
|
||||
token_dir = get_token_dir()
|
||||
credentials_path = get_credentials_path()
|
||||
if not credentials_path.exists():
|
||||
console.print(f"No stored credentials found at {credentials_path}")
|
||||
return
|
||||
|
||||
try:
|
||||
ENV_PATH.unlink()
|
||||
shutil.rmtree(token_dir)
|
||||
except OSError as e:
|
||||
console.print(f"[bold red]Error:[/bold red] Could not delete {ENV_PATH}: {e}")
|
||||
console.print(f"[bold red]Error:[/bold red] Could not delete {credentials_path}: {e}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print("[green]Logged out.[/green] Removed stored credentials.")
|
||||
|
||||
|
||||
@app.command()
|
||||
def status():
|
||||
"""Check connection and print current user info."""
|
||||
planka = get_planka()
|
||||
try:
|
||||
user = planka.me
|
||||
console.print(f"[green]Connected![/green] Logged in as: [bold]{user.username}[/bold] (ID: {user.id})")
|
||||
console.print(
|
||||
f"[green]Connected![/green] Logged in as: [bold]{user.username}[/bold] (ID: {user.id})"
|
||||
)
|
||||
if user.name:
|
||||
console.print(f"Name: {user.name}")
|
||||
console.print(f"Email: {user.email}")
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error fetching status:[/bold red] {e}")
|
||||
|
||||
|
||||
@projects_app.command("list")
|
||||
def list_projects():
|
||||
"""List all projects."""
|
||||
@@ -231,8 +306,11 @@ def list_projects():
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
|
||||
@boards_app.command("list")
|
||||
def list_boards(project_id: Optional[str] = typer.Argument(None, help="Project ID to filter by")):
|
||||
def list_boards(
|
||||
project_id: Optional[str] = typer.Argument(None, help="Project ID to filter by"),
|
||||
):
|
||||
"""List boards. Optionally filter by Project ID."""
|
||||
planka = get_planka()
|
||||
try:
|
||||
@@ -256,19 +334,25 @@ def list_boards(project_id: Optional[str] = typer.Argument(None, help="Project I
|
||||
console.print("No boards found.")
|
||||
return
|
||||
|
||||
planka_url, _, _ = get_env_config()
|
||||
table = make_table(title)
|
||||
table.add_column("ID", justify="right", style="cyan", no_wrap=True)
|
||||
table.add_column("Name", style="magenta")
|
||||
table.add_column("Project ID", justify="right")
|
||||
table.add_column("URL", style="magenta")
|
||||
|
||||
for b in boards_list:
|
||||
project_id_value = b.schema.get("projectId")
|
||||
table.add_row(str(b.id), b.name, str(project_id_value))
|
||||
board_url = None
|
||||
if planka_url and b.id:
|
||||
board_url = f"{planka_url.rstrip('/')}/boards/{b.id}"
|
||||
table.add_row(str(b.id), b.name, str(project_id_value), str(board_url or "-"))
|
||||
|
||||
console.print(table)
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
|
||||
@lists_app.command("list")
|
||||
def list_lists(board_id: str):
|
||||
"""List all lists in a board."""
|
||||
@@ -284,7 +368,7 @@ def list_lists(board_id: str):
|
||||
break
|
||||
if target_board:
|
||||
break
|
||||
|
||||
|
||||
if not target_board:
|
||||
console.print(f"[red]Board {board_id} not found.[/red]")
|
||||
return
|
||||
@@ -292,46 +376,256 @@ def list_lists(board_id: str):
|
||||
table = make_table(f"Lists in Board: {target_board.name}")
|
||||
table.add_column("ID", justify="right", style="cyan", no_wrap=True)
|
||||
table.add_column("Name", style="magenta")
|
||||
table.add_column("Board ID", justify="right")
|
||||
table.add_column("Position", justify="right")
|
||||
|
||||
for l in target_board.lists:
|
||||
table.add_row(str(l.id), l.name, str(l.position))
|
||||
for list_item in target_board.lists:
|
||||
table.add_row(
|
||||
str(list_item.id), list_item.name, str(target_board.id), str(list_item.position)
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
|
||||
@cards_app.command("list")
|
||||
def list_cards(list_id: str):
|
||||
"""List all cards in a list."""
|
||||
planka = get_planka()
|
||||
try:
|
||||
target_list = find_list(planka, list_id)
|
||||
|
||||
target_list, target_board = find_list_with_board(planka, list_id)
|
||||
|
||||
if not target_list:
|
||||
console.print(f"[red]List {list_id} not found.[/red]")
|
||||
return
|
||||
|
||||
planka_url, _, _ = get_env_config()
|
||||
board_id = target_board.id if target_board else None
|
||||
table = make_table(f"Cards in List: {target_list.name}")
|
||||
table.add_column("ID", justify="right", style="cyan", no_wrap=True)
|
||||
table.add_column("Name", style="magenta")
|
||||
table.add_column("List ID", justify="right")
|
||||
table.add_column("Position", justify="right")
|
||||
table.add_column("URL", style="magenta")
|
||||
|
||||
for c in target_list.cards:
|
||||
table.add_row(str(c.id), c.name, str(c.position))
|
||||
card_url = None
|
||||
if planka_url and board_id and c.id:
|
||||
card_url = f"{planka_url.rstrip('/')}/boards/{board_id}/cards/{c.id}"
|
||||
table.add_row(str(c.id), c.name, str(list_id), str(c.position), str(card_url or "-"))
|
||||
|
||||
console.print(table)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
|
||||
@cards_app.command("show")
|
||||
def show_card(card_id: str):
|
||||
"""Show details for a card."""
|
||||
planka = get_planka()
|
||||
try:
|
||||
card = get_card_by_id(planka, card_id)
|
||||
if not card:
|
||||
console.print(f"[red]Card {card_id} not found.[/red]")
|
||||
return
|
||||
|
||||
table = make_table(f"Card: {card.name}")
|
||||
table.add_column("Field", style="cyan", no_wrap=True)
|
||||
table.add_column("Value", style="magenta")
|
||||
|
||||
def safe_attr(obj: object, name: str) -> object:
|
||||
try:
|
||||
return getattr(obj, name)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def normalize_url(base_url: Optional[str], value: Optional[str]) -> Optional[str]:
|
||||
if not value:
|
||||
return None
|
||||
candidate = str(value).strip()
|
||||
if not candidate:
|
||||
return None
|
||||
if candidate.startswith("http://") or candidate.startswith("https://"):
|
||||
return candidate
|
||||
if base_url:
|
||||
return f"{base_url.rstrip('/')}/{candidate.lstrip('/')}"
|
||||
return candidate
|
||||
|
||||
def extract_attachment_url(attachment: object, base_url: Optional[str]) -> Optional[str]:
|
||||
data = safe_attr(attachment, "data")
|
||||
if isinstance(data, dict):
|
||||
for key in (
|
||||
"url",
|
||||
"downloadUrl",
|
||||
"download_url",
|
||||
"link",
|
||||
"href",
|
||||
"path",
|
||||
):
|
||||
value = data.get(key)
|
||||
if isinstance(value, str) and value.strip():
|
||||
return normalize_url(base_url, value)
|
||||
file_info = data.get("file")
|
||||
if isinstance(file_info, dict):
|
||||
for key in (
|
||||
"url",
|
||||
"downloadUrl",
|
||||
"download_url",
|
||||
"path",
|
||||
"thumbnailUrl",
|
||||
"thumbUrl",
|
||||
"thumbnail_url",
|
||||
):
|
||||
value = file_info.get(key)
|
||||
if isinstance(value, str) and value.strip():
|
||||
return normalize_url(base_url, value)
|
||||
direct_url = safe_attr(attachment, "url")
|
||||
if isinstance(direct_url, str) and direct_url.strip():
|
||||
return normalize_url(base_url, direct_url)
|
||||
return None
|
||||
|
||||
def add_row(label: str, value: object) -> None:
|
||||
if value is None or value == "":
|
||||
table.add_row(label, "-")
|
||||
else:
|
||||
table.add_row(label, str(value))
|
||||
|
||||
try:
|
||||
schema = card.schema or {}
|
||||
except Exception:
|
||||
schema = {}
|
||||
list_id_value = schema.get("listId")
|
||||
board_id_value = schema.get("boardId")
|
||||
list_name_value = None
|
||||
list_obj = safe_attr(card, "list")
|
||||
if list_obj is not None:
|
||||
list_id_value = safe_attr(list_obj, "id") or list_id_value
|
||||
list_name_value = safe_attr(list_obj, "name")
|
||||
if not board_id_value:
|
||||
list_schema = safe_attr(list_obj, "schema")
|
||||
if isinstance(list_schema, dict):
|
||||
board_id_value = list_schema.get("boardId")
|
||||
|
||||
list_display = "-"
|
||||
if list_name_value and list_id_value:
|
||||
list_display = f"{list_name_value} ({list_id_value})"
|
||||
elif list_name_value:
|
||||
list_display = list_name_value
|
||||
elif list_id_value:
|
||||
list_display = list_id_value
|
||||
|
||||
due_date = safe_attr(card, "due_date") or schema.get("dueDate")
|
||||
due_completed = safe_attr(card, "due_date_completed")
|
||||
if due_completed is None:
|
||||
due_completed = schema.get("isDueCompleted")
|
||||
if isinstance(due_completed, bool):
|
||||
due_completed = "yes" if due_completed else "no"
|
||||
|
||||
attachments_error = None
|
||||
comments_error = None
|
||||
try:
|
||||
attachments = list(safe_attr(card, "attachments") or [])
|
||||
except Exception as exc:
|
||||
attachments = []
|
||||
attachments_error = str(exc)
|
||||
try:
|
||||
comments = list(safe_attr(card, "comments") or [])
|
||||
except Exception as exc:
|
||||
comments = []
|
||||
comments_error = str(exc)
|
||||
|
||||
comments_count = safe_attr(card, "comments_count")
|
||||
if comments_count is None and comments_error is None:
|
||||
comments_count = len(comments)
|
||||
|
||||
planka_url, _, _ = get_env_config()
|
||||
card_url = None
|
||||
if planka_url and board_id_value and card.id:
|
||||
card_url = f"{planka_url.rstrip('/')}/boards/{board_id_value}/cards/{card.id}"
|
||||
|
||||
add_row("ID", card.id)
|
||||
add_row("URL", card_url)
|
||||
add_row("Name", card.name)
|
||||
add_row("Description", safe_attr(card, "description") or schema.get("description"))
|
||||
add_row("Board ID", board_id_value)
|
||||
add_row("List", list_display)
|
||||
add_row("Position", safe_attr(card, "position") or schema.get("position"))
|
||||
add_row("Type", safe_attr(card, "type") or schema.get("type"))
|
||||
add_row("Due Date", due_date)
|
||||
add_row("Due Completed", due_completed)
|
||||
if attachments_error:
|
||||
add_row("Attachments", f"Error: {attachments_error}")
|
||||
else:
|
||||
add_row("Attachments", len(attachments))
|
||||
if comments_error:
|
||||
add_row("Comments", f"Error: {comments_error}")
|
||||
else:
|
||||
add_row("Comments", comments_count)
|
||||
add_row("Created At", safe_attr(card, "created_at") or schema.get("createdAt"))
|
||||
add_row("Updated At", safe_attr(card, "updated_at") or schema.get("updatedAt"))
|
||||
|
||||
console.print(table)
|
||||
|
||||
if attachments and attachments_error is None:
|
||||
attachments_table = make_table("Attachments")
|
||||
attachments_table.add_column("ID", justify="right", style="cyan", no_wrap=True)
|
||||
attachments_table.add_column("Name", style="magenta")
|
||||
attachments_table.add_column("Type", style="magenta")
|
||||
attachments_table.add_column("URL", style="magenta")
|
||||
attachments_table.add_column("Created At", justify="right")
|
||||
|
||||
for attachment in attachments:
|
||||
attachment_url = extract_attachment_url(attachment, planka_url)
|
||||
attachments_table.add_row(
|
||||
str(safe_attr(attachment, "id") or "-"),
|
||||
str(safe_attr(attachment, "name") or "-"),
|
||||
str(safe_attr(attachment, "type") or "-"),
|
||||
str(attachment_url or "-"),
|
||||
str(safe_attr(attachment, "created_at") or "-"),
|
||||
)
|
||||
|
||||
console.print(attachments_table)
|
||||
|
||||
if comments and comments_error is None:
|
||||
comments_table = make_table("Comments")
|
||||
comments_table.add_column("ID", justify="right", style="cyan", no_wrap=True)
|
||||
comments_table.add_column("User", style="magenta")
|
||||
comments_table.add_column("Text", style="magenta")
|
||||
comments_table.add_column("Created At", justify="right")
|
||||
|
||||
for comment in comments:
|
||||
user = safe_attr(comment, "user")
|
||||
user_label = (
|
||||
safe_attr(user, "name")
|
||||
or safe_attr(user, "username")
|
||||
or safe_attr(user, "id")
|
||||
or "-"
|
||||
)
|
||||
text = safe_attr(comment, "text")
|
||||
text_label = " ".join(str(text).split()) if text else "-"
|
||||
comments_table.add_row(
|
||||
str(safe_attr(comment, "id") or "-"),
|
||||
str(user_label),
|
||||
text_label,
|
||||
str(safe_attr(comment, "created_at") or "-"),
|
||||
)
|
||||
|
||||
console.print(comments_table)
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
|
||||
@cards_app.command("create")
|
||||
def create_card(
|
||||
list_id: str = typer.Argument(..., help="List ID to create the card in"),
|
||||
name: str = typer.Argument(..., help="Card name/title"),
|
||||
description: Optional[str] = typer.Option(None, "--description", "-d", help="Card description"),
|
||||
position: str = typer.Option("bottom", "--position", "-p", help="Position: top, bottom, or integer"),
|
||||
position: str = typer.Option(
|
||||
"bottom", "--position", "-p", help="Position: top, bottom, or integer"
|
||||
),
|
||||
card_type: str = typer.Option("project", "--type", "-t", help="Card type (project, story)"),
|
||||
due_date: Optional[str] = typer.Option(None, "--due-date", help="Due date (ISO-8601)"),
|
||||
due_completed: bool = typer.Option(False, "--due-completed", help="Mark due date as completed"),
|
||||
@@ -363,15 +657,24 @@ def create_card(
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
|
||||
@cards_app.command("update")
|
||||
def update_card(
|
||||
card_id: str = typer.Argument(..., help="Card ID to update"),
|
||||
name: Optional[str] = typer.Option(None, "--name", help="New card name"),
|
||||
description: Optional[str] = typer.Option(None, "--description", "-d", help="New card description"),
|
||||
clear_description: bool = typer.Option(False, "--clear-description", help="Clear the description"),
|
||||
position: Optional[str] = typer.Option(None, "--position", "-p", help="Position: top, bottom, or integer"),
|
||||
description: Optional[str] = typer.Option(
|
||||
None, "--description", "-d", help="New card description"
|
||||
),
|
||||
clear_description: bool = typer.Option(
|
||||
False, "--clear-description", help="Clear the description"
|
||||
),
|
||||
position: Optional[str] = typer.Option(
|
||||
None, "--position", "-p", help="Position: top, bottom, or integer"
|
||||
),
|
||||
list_id: Optional[str] = typer.Option(None, "--list-id", help="Move to a new list"),
|
||||
card_type: Optional[str] = typer.Option(None, "--type", "-t", help="Card type (project, story)"),
|
||||
card_type: Optional[str] = typer.Option(
|
||||
None, "--type", "-t", help="Card type (project, story)"
|
||||
),
|
||||
due_date: Optional[str] = typer.Option(None, "--due-date", help="Due date (ISO-8601)"),
|
||||
clear_due_date: bool = typer.Option(False, "--clear-due-date", help="Clear the due date"),
|
||||
due_completed: Optional[bool] = typer.Option(
|
||||
@@ -382,7 +685,9 @@ def update_card(
|
||||
):
|
||||
"""Update an existing card."""
|
||||
if description is not None and clear_description:
|
||||
console.print("[bold red]Error:[/bold red] Use either --description or --clear-description.")
|
||||
console.print(
|
||||
"[bold red]Error:[/bold red] Use either --description or --clear-description."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
if due_date is not None and clear_due_date:
|
||||
console.print("[bold red]Error:[/bold red] Use either --due-date or --clear-due-date.")
|
||||
@@ -430,6 +735,7 @@ def update_card(
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
|
||||
@cards_app.command("delete")
|
||||
def delete_card(
|
||||
card_id: str = typer.Argument(..., help="Card ID to delete"),
|
||||
@@ -454,6 +760,7 @@ def delete_card(
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
|
||||
@notifications_app.command("all")
|
||||
def all_notifications():
|
||||
"""List all notifications."""
|
||||
@@ -463,6 +770,7 @@ def all_notifications():
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
|
||||
@notifications_app.command("unread")
|
||||
def unread_notifications():
|
||||
"""List unread notifications."""
|
||||
@@ -472,5 +780,6 @@ def unread_notifications():
|
||||
except Exception as e:
|
||||
console.print(f"[bold red]Error:[/bold red] {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# PyInstaller runtime hook: stub out plankapy.v1 to avoid circular import issues
|
||||
import sys
|
||||
import types
|
||||
|
||||
|
||||
class _DummyPlanka:
|
||||
"""Stub for plankapy.v1.Planka to satisfy root __init__.py"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise NotImplementedError("plankapy.v1 is not available in this build; use plankapy.v2")
|
||||
|
||||
|
||||
class _DummyPasswordAuth:
|
||||
"""Stub for plankapy.v1.PasswordAuth to satisfy root __init__.py"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
raise NotImplementedError("plankapy.v1 is not available in this build; use plankapy.v2")
|
||||
|
||||
|
||||
# Create a dummy v1 module to satisfy plankapy's root __init__.py
|
||||
v1_stub = types.ModuleType("plankapy.v1")
|
||||
v1_stub.Planka = _DummyPlanka
|
||||
v1_stub.PasswordAuth = _DummyPasswordAuth
|
||||
sys.modules["plankapy.v1"] = v1_stub
|
||||
@@ -1,4 +0,0 @@
|
||||
plankapy
|
||||
typer
|
||||
python-dotenv
|
||||
rich
|
||||
@@ -0,0 +1,72 @@
|
||||
"""Tests for planka-cli."""
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from scripts.planka_cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
class TestHelp:
|
||||
"""Test help output for all commands."""
|
||||
|
||||
def test_main_help(self):
|
||||
"""Main command should show help."""
|
||||
result = runner.invoke(app, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "Planka CLI" in result.output
|
||||
|
||||
def test_projects_help(self):
|
||||
"""Projects subcommand should show help."""
|
||||
result = runner.invoke(app, ["projects", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "Manage projects" in result.output
|
||||
|
||||
def test_boards_help(self):
|
||||
"""Boards subcommand should show help."""
|
||||
result = runner.invoke(app, ["boards", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "Manage boards" in result.output
|
||||
|
||||
def test_lists_help(self):
|
||||
"""Lists subcommand should show help."""
|
||||
result = runner.invoke(app, ["lists", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "Manage lists" in result.output
|
||||
|
||||
def test_cards_help(self):
|
||||
"""Cards subcommand should show help."""
|
||||
result = runner.invoke(app, ["cards", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "Manage cards" in result.output
|
||||
|
||||
def test_notifications_help(self):
|
||||
"""Notifications subcommand should show help."""
|
||||
result = runner.invoke(app, ["notifications", "--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "Manage notifications" in result.output
|
||||
|
||||
|
||||
class TestUnknownCommand:
|
||||
"""Test behavior with unknown commands."""
|
||||
|
||||
def test_unknown_subcommand_shows_help(self):
|
||||
"""Unknown subcommand should show help and exit with code 2."""
|
||||
result = runner.invoke(app, ["unknown"])
|
||||
assert result.exit_code == 2
|
||||
|
||||
|
||||
class TestLoginLogout:
|
||||
"""Test login/logout commands (without actually connecting)."""
|
||||
|
||||
def test_login_requires_options(self):
|
||||
"""Login without options should fail."""
|
||||
result = runner.invoke(app, ["login"])
|
||||
assert result.exit_code != 0
|
||||
|
||||
def test_logout_without_credentials(self, tmp_path, monkeypatch):
|
||||
"""Logout without stored credentials should be graceful."""
|
||||
monkeypatch.setenv("PLANKATOKENS", str(tmp_path))
|
||||
result = runner.invoke(app, ["logout"])
|
||||
assert result.exit_code == 0
|
||||
assert "No stored credentials" in result.output
|
||||
@@ -1,10 +1,9 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.11"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.13'",
|
||||
"python_full_version >= '3.11' and python_full_version < '3.13'",
|
||||
"python_full_version < '3.11'",
|
||||
"python_full_version < '3.13'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -37,95 +36,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.1"
|
||||
@@ -193,6 +103,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||
wheels = [
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macholib"
|
||||
version = "1.16.4"
|
||||
@@ -249,10 +168,8 @@ name = "planka-cli"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "plankapy", version = "1.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "plankapy", version = "2.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.13'" },
|
||||
{ name = "plankapy", version = "2.2.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" },
|
||||
{ name = "plankapy", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "rich" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
@@ -260,32 +177,22 @@ dependencies = [
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "pyinstaller" },
|
||||
{ name = "pytest" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "plankapy" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "plankapy", specifier = ">=2.2.2" },
|
||||
{ name = "rich" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "pyinstaller" }]
|
||||
|
||||
[[package]]
|
||||
name = "plankapy"
|
||||
version = "1.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "requests", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/f1/60b984fe9caf2dca21b7e87609fd99be2be547c48fcd58c870c4e8790c2d/plankapy-1.0.2.tar.gz", hash = "sha256:7f25642f521e2b7db45426d570f926a90f51d37f3b1246cef3fa186a75140fc2", size = 21205, upload-time = "2025-01-16T14:51:17.258Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/03/6e/ee59dec2e1a5ea8a49e73102326f3b130f7167c5940f392f91c0084b8cb4/plankapy-1.0.2-py3-none-any.whl", hash = "sha256:1338de7f97ed51fcf5cf343f4a39dc9655f4d35b0d47cabd87a8f74364af01b6", size = 20470, upload-time = "2025-01-16T14:51:15.136Z" },
|
||||
dev = [
|
||||
{ name = "pyinstaller" },
|
||||
{ name = "pytest" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -293,7 +200,7 @@ name = "plankapy"
|
||||
version = "2.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11' and python_full_version < '3.13'",
|
||||
"python_full_version < '3.13'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9b/57/29987afb2f9862b627a38648ee899e754b05de998cec386e1ca467f6efbd/plankapy-2.2.2.tar.gz", hash = "sha256:bd832f97f9e63e651946b4d1410ceb96c7cee1732cdddfe796ed60bf6404b6f3", size = 46442, upload-time = "2025-05-13T02:05:40.261Z" }
|
||||
wheels = [
|
||||
@@ -315,6 +222,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/57/db8f36d86e649ec19e1978a665c6b6cde945f9d72f28f326154d3705562b/plankapy-2.3.0-py3-none-any.whl", hash = "sha256:699532cfff4e5ca7e47409d17ff64c34aff85a41ea14887a87adc1770b3c1066", size = 135703, upload-time = "2026-01-27T00:11:41.456Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
@@ -366,12 +282,19 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.1"
|
||||
name = "pytest"
|
||||
version = "9.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -383,21 +306,6 @@ wheels = [
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi", marker = "python_full_version < '3.11'" },
|
||||
{ name = "charset-normalizer", marker = "python_full_version < '3.11'" },
|
||||
{ name = "idna", marker = "python_full_version < '3.11'" },
|
||||
{ name = "urllib3", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "14.3.1"
|
||||
@@ -411,6 +319,31 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/87/2a/a1810c8627b9ec8c57ec5ec325d306701ae7be50235e8fd81266e002a3cc/rich-14.3.1-py3-none-any.whl", hash = "sha256:da750b1aebbff0b372557426fb3f35ba56de8ef954b3190315eb64076d6fb54e", size = 309952, upload-time = "2026-01-24T21:40:42.969Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "80.10.2"
|
||||
@@ -452,12 +385,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac8
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user