datalegion-cli 1.2.1__tar.gz → 1.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/PKG-INFO +1 -1
  2. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/pyproject.toml +5 -1
  3. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/client.py +1 -1
  4. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/config.py +2 -1
  5. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/tests/test_config.py +16 -0
  6. datalegion_cli-1.2.2/tests/test_package_contents.py +56 -0
  7. datalegion_cli-1.2.1/.github/actionlint.yaml +0 -5
  8. datalegion_cli-1.2.1/.github/dependabot.yml +0 -42
  9. datalegion_cli-1.2.1/.github/workflows/ci.yml +0 -77
  10. datalegion_cli-1.2.1/.github/workflows/release.yml +0 -236
  11. datalegion_cli-1.2.1/.python-version +0 -1
  12. datalegion_cli-1.2.1/uv.lock +0 -658
  13. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/.gitignore +0 -0
  14. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/README.md +0 -0
  15. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/__init__.py +0 -0
  16. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/async_typer.py +0 -0
  17. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/commands/__init__.py +0 -0
  18. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/commands/company.py +0 -0
  19. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/commands/credits.py +0 -0
  20. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/commands/person.py +0 -0
  21. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/commands/utility.py +0 -0
  22. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/logging.py +0 -0
  23. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/main.py +0 -0
  24. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/output.py +0 -0
  25. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/src/datalegion_cli/update.py +0 -0
  26. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/tests/__init__.py +0 -0
  27. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/tests/conftest.py +0 -0
  28. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/tests/test_client.py +0 -0
  29. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/tests/test_commands.py +0 -0
  30. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/tests/test_logging.py +0 -0
  31. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/tests/test_output.py +0 -0
  32. {datalegion_cli-1.2.1 → datalegion_cli-1.2.2}/tests/test_update.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: datalegion-cli
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: CLI for the Data Legion API — agent-friendly, fully async
5
5
  Project-URL: Homepage, https://www.datalegion.ai
6
6
  Project-URL: Documentation, https://www.datalegion.ai/docs
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datalegion-cli"
3
- version = "1.2.1"
3
+ version = "1.2.2"
4
4
  description = "CLI for the Data Legion API — agent-friendly, fully async"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -57,6 +57,10 @@ build-backend = "hatchling.build"
57
57
  [tool.hatch.build.targets.wheel]
58
58
  packages = ["src/datalegion_cli"]
59
59
 
60
+ # The sdist carries the source and tests, never repo tooling (.github/, .claude/, lefthook.yml).
61
+ [tool.hatch.build.targets.sdist]
62
+ only-include = ["src/datalegion_cli", "tests"]
63
+
60
64
  [dependency-groups]
61
65
  dev = [
62
66
  "pytest>=8.3.0",
@@ -43,7 +43,7 @@ class LegionClient:
43
43
  timeout: float = 60.0,
44
44
  ):
45
45
  self._base_url = base_url or get_base_url()
46
- self._api_key = api_key or get_api_key()
46
+ self._api_key = (api_key or "").strip() or get_api_key()
47
47
  self._timeout = timeout
48
48
  self._client: httpx.AsyncClient | None = None
49
49
  self.last_credits_used: str | None = None
@@ -114,7 +114,8 @@ def save_config(cfg: dict[str, object]) -> None:
114
114
 
115
115
  def get_api_key() -> str:
116
116
  """Return the configured API key or raise."""
117
- key = str(load_config().get("api_key", ""))
117
+ # Trimmed: a key from a file or a paste with a trailing newline is otherwise refused by httpx.
118
+ key = str(load_config().get("api_key", "")).strip()
118
119
  if not key:
119
120
  raise SystemExit(
120
121
  "No API key configured. Run `datalegion-cli config set api_key <your-key>` "
@@ -49,6 +49,22 @@ class TestSaveAndLoad:
49
49
  cfg = load_config()
50
50
  assert cfg["api_key"] == "legion_roundtrip"
51
51
 
52
+ def test_api_key_is_trimmed(self, tmp_config_dir, monkeypatch):
53
+ from datalegion_cli.config import get_api_key, save_config
54
+
55
+ monkeypatch.setenv("LEGION_API_KEY", "legion_fromenv\n")
56
+ assert get_api_key() == "legion_fromenv"
57
+ monkeypatch.delenv("LEGION_API_KEY")
58
+ save_config({"api_key": " legion_fromfile "})
59
+ assert get_api_key() == "legion_fromfile"
60
+
61
+ def test_whitespace_only_api_key_is_missing(self, tmp_config_dir, monkeypatch):
62
+ from datalegion_cli.config import get_api_key
63
+
64
+ monkeypatch.setenv("LEGION_API_KEY", " \n")
65
+ with pytest.raises(SystemExit):
66
+ get_api_key()
67
+
52
68
  def test_save_sets_file_permissions_600(self, tmp_config_dir):
53
69
  from datalegion_cli.config import CONFIG_FILE, save_config
54
70
 
@@ -0,0 +1,56 @@
1
+ """What the published wheel and sdist contain.
2
+
3
+ Customers install the wheel: it must hold only the ``datalegion_cli`` package, and its only
4
+ command is ``datalegion-cli``. The sdist carries the source and tests, never repo tooling.
5
+ """
6
+
7
+ import subprocess
8
+ import tarfile
9
+ import zipfile
10
+ from pathlib import Path
11
+
12
+ import pytest
13
+
14
+ ROOT = Path(__file__).resolve().parents[1]
15
+
16
+
17
+ @pytest.fixture(scope="module")
18
+ def dist(tmp_path_factory: pytest.TempPathFactory) -> Path:
19
+ out = tmp_path_factory.mktemp("dist")
20
+ # Each straight from the source tree: a default build makes the wheel from the sdist, which
21
+ # would hide a wheel misconfiguration behind the sdist's own include list.
22
+ for target in ("--sdist", "--wheel"):
23
+ subprocess.run(
24
+ ["uv", "build", target, "--quiet", "--out-dir", str(out)], cwd=ROOT, check=True
25
+ )
26
+ return out
27
+
28
+
29
+ def test_wheel_ships_only_the_cli(dist: Path) -> None:
30
+ (wheel,) = dist.glob("*.whl")
31
+ with zipfile.ZipFile(wheel) as archive:
32
+ names = archive.namelist()
33
+ commands = next(
34
+ (archive.read(n).decode() for n in names if n.endswith("entry_points.txt")), ""
35
+ )
36
+ top_level = {n.split("/")[0] for n in names if not n.split("/")[0].endswith(".dist-info")}
37
+ assert top_level == {"datalegion_cli"}
38
+ assert commands.split() == [
39
+ "[console_scripts]",
40
+ "datalegion-cli",
41
+ "=",
42
+ "datalegion_cli.main:run",
43
+ ]
44
+
45
+
46
+ def test_sdist_carries_no_repo_tooling(dist: Path) -> None:
47
+ (sdist,) = dist.glob("*.tar.gz")
48
+ with tarfile.open(sdist) as archive:
49
+ names = archive.getnames()
50
+ leaked = [
51
+ n
52
+ for n in names
53
+ if any(part in n.split("/") for part in (".claude", ".github", "scripts", "lefthook.yml"))
54
+ ]
55
+ assert leaked == []
56
+ assert any(n.endswith("src/datalegion_cli/main.py") for n in names)
@@ -1,5 +0,0 @@
1
- # actionlint (the `actionlint` job in .github/workflows/ci.yml) knows only GitHub-hosted
2
- # runner labels; Blacksmith's are custom.
3
- self-hosted-runner:
4
- labels:
5
- - blacksmith-*
@@ -1,42 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: "uv"
4
- directory: "/"
5
- schedule:
6
- interval: "weekly"
7
- day: "sunday"
8
- time: "09:00"
9
- timezone: "America/Los_Angeles"
10
- cooldown:
11
- default-days: 1
12
- labels:
13
- - "dependencies"
14
- - "python"
15
- commit-message:
16
- prefix: "chore(deps)"
17
- groups:
18
- minor-patch:
19
- patterns:
20
- - "*"
21
- update-types:
22
- - "minor"
23
- - "patch"
24
-
25
- - package-ecosystem: "github-actions"
26
- directory: "/"
27
- schedule:
28
- interval: "weekly"
29
- day: "sunday"
30
- time: "09:00"
31
- timezone: "America/Los_Angeles"
32
- cooldown:
33
- default-days: 1
34
- labels:
35
- - "dependencies"
36
- - "github-actions"
37
- commit-message:
38
- prefix: "chore(deps)"
39
- groups:
40
- actions:
41
- patterns:
42
- - "*"
@@ -1,77 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- pull_request:
5
- branches: [main]
6
-
7
- jobs:
8
- lint:
9
- runs-on: blacksmith-2vcpu-ubuntu-2404
10
- steps:
11
- - uses: actions/checkout@v7
12
- - uses: astral-sh/setup-uv@v10.2.0
13
- with:
14
- enable-cache: true
15
- # .python-version: the 3.11 floor
16
- - run: uv sync --frozen --group dev
17
- - run: uv run ruff check src/ tests/
18
- - run: uv run ruff format --check src/ tests/
19
-
20
- test-python:
21
- name: test (Python ${{ matrix.python-version }})
22
- runs-on: blacksmith-2vcpu-ubuntu-2404
23
- strategy:
24
- fail-fast: false
25
- matrix:
26
- # Every version pyproject.toml's classifiers claim.
27
- python-version: ["3.11", "3.12", "3.13", "3.14"]
28
- # Without this uv takes the interpreter from .python-version (the 3.11 floor)
29
- # for every leg.
30
- env:
31
- UV_PYTHON: ${{ matrix.python-version }}
32
- steps:
33
- - uses: actions/checkout@v7
34
- - uses: astral-sh/setup-uv@v10.2.0
35
- with:
36
- enable-cache: true
37
- - run: uv python install ${{ matrix.python-version }}
38
- - run: uv sync --frozen --group dev
39
- - name: Check the interpreter is the matrix version
40
- run: uv run python -c "import sys; v = '%d.%d' % sys.version_info[:2]; assert v == '${{ matrix.python-version }}', f'ran {sys.version}, matrix says ${{ matrix.python-version }}'; print(sys.version)"
41
- - run: uv run pytest
42
-
43
- # The main ruleset requires a check named `test`; it passes only when every
44
- # Python leg did.
45
- test:
46
- needs: test-python
47
- if: always()
48
- runs-on: blacksmith-2vcpu-ubuntu-2404
49
- steps:
50
- - run: '[ "${{ needs.test-python.result }}" = "success" ]'
51
-
52
- actionlint:
53
- name: actionlint
54
- runs-on: blacksmith-2vcpu-ubuntu-2404
55
- timeout-minutes: 5
56
- permissions:
57
- contents: read
58
- steps:
59
- - uses: actions/checkout@v7
60
- # Pinned and checksum-verified (the upgrade sweep moves the version and both digests).
61
- # actionlint runs shellcheck on every `run:` script when shellcheck is on PATH, so
62
- # install it rather than let those checks skip silently.
63
- - name: Lint workflows (actionlint + shellcheck)
64
- env:
65
- ACTIONLINT_VERSION: 1.7.12
66
- run: |
67
- case "$(uname -m)" in
68
- x86_64) arch=amd64; sha=8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 ;;
69
- aarch64) arch=arm64; sha=325e971b6ba9bfa504672e29be93c24981eeb1c07576d730e9f7c8805afff0c6 ;;
70
- *) echo "unsupported arch $(uname -m)"; exit 1 ;;
71
- esac
72
- f="actionlint_${ACTIONLINT_VERSION}_linux_${arch}.tar.gz"
73
- curl -fsSL -o "/tmp/$f" "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/$f"
74
- echo "$sha /tmp/$f" | sha256sum -c -
75
- tar -xzf "/tmp/$f" -C /tmp actionlint
76
- command -v shellcheck >/dev/null || { sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck; }
77
- /tmp/actionlint -color
@@ -1,236 +0,0 @@
1
- name: Release
2
-
3
- on:
4
- push:
5
- tags:
6
- - "v*"
7
-
8
- concurrency:
9
- group: ${{ github.workflow }}-${{ github.ref }}
10
- cancel-in-progress: true
11
-
12
- jobs:
13
- lint:
14
- name: Lint & Build
15
- runs-on: blacksmith-2vcpu-ubuntu-2404
16
- timeout-minutes: 5
17
- permissions:
18
- contents: read
19
- steps:
20
- - name: Checkout
21
- uses: actions/checkout@v7
22
-
23
- - name: Install uv
24
- uses: astral-sh/setup-uv@v10.2.0
25
- with:
26
- enable-cache: true
27
- cache-dependency-glob: "uv.lock"
28
-
29
- - name: Set up Python
30
- run: uv python install 3.11
31
-
32
- - name: Install dependencies
33
- run: uv sync --frozen --group dev
34
-
35
- - name: Run lint
36
- run: uv run ruff check src/ tests/
37
-
38
- - name: Check formatting
39
- run: uv run ruff format --check src/ tests/
40
-
41
- - name: Build
42
- run: uv build
43
-
44
- - name: Prune uv cache
45
- if: always()
46
- run: uv cache prune --ci
47
-
48
- test:
49
- name: Test (Python ${{ matrix.python-version }})
50
- runs-on: blacksmith-2vcpu-ubuntu-2404
51
- timeout-minutes: 10
52
- permissions:
53
- contents: read
54
- # Without this uv takes the interpreter from .python-version (the 3.11 floor)
55
- # for every leg.
56
- env:
57
- UV_PYTHON: ${{ matrix.python-version }}
58
- strategy:
59
- fail-fast: false
60
- matrix:
61
- python-version: ["3.11", "3.12", "3.13", "3.14"]
62
- steps:
63
- - name: Checkout
64
- uses: actions/checkout@v7
65
-
66
- - name: Install uv
67
- uses: astral-sh/setup-uv@v10.2.0
68
- with:
69
- enable-cache: true
70
- cache-dependency-glob: "uv.lock"
71
-
72
- - name: Set up Python ${{ matrix.python-version }}
73
- run: uv python install ${{ matrix.python-version }}
74
-
75
- - name: Install dependencies
76
- run: uv sync --frozen --group dev
77
-
78
- - name: Check the interpreter is the matrix version
79
- run: uv run python -c "import sys; v = '%d.%d' % sys.version_info[:2]; assert v == '${{ matrix.python-version }}', f'ran {sys.version}, matrix says ${{ matrix.python-version }}'; print(sys.version)"
80
-
81
- - name: Run tests
82
- run: uv run pytest tests/ -v --tb=short
83
-
84
- - name: Prune uv cache
85
- if: always()
86
- run: uv cache prune --ci
87
-
88
- publish-pypi:
89
- name: Publish to PyPI
90
- needs: [lint, test]
91
- runs-on: blacksmith-2vcpu-ubuntu-2404
92
- timeout-minutes: 5
93
- permissions:
94
- contents: read
95
- id-token: write
96
- steps:
97
- - name: Checkout
98
- uses: actions/checkout@v7
99
-
100
- - name: Install uv
101
- uses: astral-sh/setup-uv@v10.2.0
102
-
103
- - name: Set up Python
104
- run: uv python install 3.11
105
-
106
- - name: Set version from tag
107
- run: |
108
- VERSION="${GITHUB_REF_NAME#v}"
109
- sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml
110
-
111
- - name: Build
112
- run: uv build
113
-
114
- - name: Check if version exists on PyPI
115
- id: pypi-check
116
- run: |
117
- VERSION="${GITHUB_REF_NAME#v}"
118
- if curl -sSf "https://pypi.org/pypi/datalegion-cli/${VERSION}/json" > /dev/null 2>&1; then
119
- echo "Version ${VERSION} already exists on PyPI, skipping publish"
120
- echo "exists=true" >> "$GITHUB_OUTPUT"
121
- else
122
- echo "exists=false" >> "$GITHUB_OUTPUT"
123
- fi
124
-
125
- - name: Publish to PyPI
126
- if: steps.pypi-check.outputs.exists != 'true'
127
- run: uv publish --trusted-publishing always
128
-
129
- update-homebrew:
130
- name: Update Homebrew Tap
131
- needs: [publish-pypi]
132
- runs-on: blacksmith-2vcpu-ubuntu-2404
133
- timeout-minutes: 10
134
- permissions:
135
- contents: read
136
- steps:
137
- - name: Wait for PyPI availability
138
- run: |
139
- VERSION="${GITHUB_REF_NAME#v}"
140
- for i in $(seq 1 30); do
141
- if curl -sSf "https://pypi.org/pypi/datalegion-cli/${VERSION}/json" > /dev/null 2>&1; then
142
- echo "Version ${VERSION} available on PyPI"
143
- exit 0
144
- fi
145
- echo "Waiting for PyPI... (attempt $i/30)"
146
- sleep 10
147
- done
148
- echo "::error::Timed out waiting for PyPI availability"
149
- exit 1
150
-
151
- - name: Generate Homebrew formula
152
- run: |
153
- VERSION="${GITHUB_REF_NAME#v}"
154
-
155
- # Get the actual sdist URL and digest from PyPI JSON API
156
- PYPI_JSON=$(curl -sSf "https://pypi.org/pypi/datalegion-cli/${VERSION}/json")
157
- URL=$(echo "$PYPI_JSON" | python3 -c "import sys,json; urls=json.load(sys.stdin)['urls']; print(next(u['url'] for u in urls if u['packagetype']=='sdist'))")
158
- SHA256=$(echo "$PYPI_JSON" | python3 -c "import sys,json; urls=json.load(sys.stdin)['urls']; print(next(u['digests']['sha256'] for u in urls if u['packagetype']=='sdist'))")
159
-
160
- cat > datalegion-cli.rb <<FORMULA
161
- class DatalegionCli < Formula
162
- desc "CLI for the Data Legion API - agent-friendly, fully async"
163
- homepage "https://www.datalegion.ai"
164
- url "${URL}"
165
- sha256 "${SHA256}"
166
- license "MIT"
167
-
168
- depends_on "python@3.13"
169
-
170
- def install
171
- venv = libexec/"venv"
172
- system Formula["python@3.13"].opt_bin/"python3.13", "-m", "venv", venv
173
- system venv/"bin/pip", "install", "datalegion-cli==#{version}"
174
- bin.install_symlink Dir[venv/"bin/datalegion-cli"]
175
- end
176
-
177
- test do
178
- assert_match version.to_s, shell_output("#{bin}/datalegion-cli version")
179
- end
180
- end
181
- FORMULA
182
-
183
- # Remove leading whitespace from heredoc indentation
184
- sed -i 's/^ //' datalegion-cli.rb
185
-
186
- - name: Push formula to tap
187
- env:
188
- GH_PAT: ${{ secrets.GH_PAT }}
189
- run: |
190
- VERSION="${GITHUB_REF_NAME#v}"
191
- git clone "https://x-access-token:${GH_PAT}@github.com/datalegion-ai/homebrew-tap.git" tap
192
- mkdir -p tap/Formula
193
- cp datalegion-cli.rb tap/Formula/datalegion-cli.rb
194
- cd tap
195
- git config user.name "github-actions[bot]"
196
- git config user.email "github-actions[bot]@users.noreply.github.com"
197
- git add Formula/datalegion-cli.rb
198
- git diff --staged --quiet || git commit -m "datalegion-cli ${VERSION}"
199
- git push
200
-
201
- github-release:
202
- name: Create GitHub Release
203
- needs: [publish-pypi]
204
- runs-on: blacksmith-2vcpu-ubuntu-2404
205
- timeout-minutes: 5
206
- permissions:
207
- contents: write
208
- steps:
209
- - name: Checkout
210
- uses: actions/checkout@v7
211
- with:
212
- ref: main
213
- token: ${{ secrets.GH_PAT }}
214
-
215
- - name: Update version in pyproject.toml
216
- run: |
217
- VERSION="${GITHUB_REF_NAME#v}"
218
- sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml
219
- git config user.name "github-actions[bot]"
220
- git config user.email "github-actions[bot]@users.noreply.github.com"
221
- git add pyproject.toml
222
- git diff --staged --quiet || git commit -m "chore: bump version to ${VERSION} [skip ci]"
223
- git push
224
-
225
- - name: Create Release
226
- uses: actions/github-script@v9
227
- with:
228
- script: |
229
- const tag = context.ref.replace('refs/tags/', '');
230
- await github.rest.repos.createRelease({
231
- owner: context.repo.owner,
232
- repo: context.repo.repo,
233
- tag_name: tag,
234
- name: tag,
235
- generate_release_notes: true,
236
- });
@@ -1 +0,0 @@
1
- 3.11