datalegion-cli 1.2.0__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 (30) hide show
  1. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/PKG-INFO +4 -3
  2. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/README.md +1 -1
  3. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/pyproject.toml +6 -1
  4. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/client.py +5 -1
  5. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/config.py +2 -1
  6. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/tests/test_client.py +9 -0
  7. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/tests/test_config.py +16 -0
  8. datalegion_cli-1.2.2/tests/test_package_contents.py +56 -0
  9. datalegion_cli-1.2.0/.github/dependabot.yml +0 -42
  10. datalegion_cli-1.2.0/.github/workflows/ci.yml +0 -33
  11. datalegion_cli-1.2.0/.github/workflows/release.yml +0 -229
  12. datalegion_cli-1.2.0/uv.lock +0 -645
  13. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/.gitignore +0 -0
  14. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/__init__.py +0 -0
  15. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/async_typer.py +0 -0
  16. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/commands/__init__.py +0 -0
  17. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/commands/company.py +0 -0
  18. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/commands/credits.py +0 -0
  19. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/commands/person.py +0 -0
  20. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/commands/utility.py +0 -0
  21. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/logging.py +0 -0
  22. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/main.py +0 -0
  23. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/output.py +0 -0
  24. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/src/datalegion_cli/update.py +0 -0
  25. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/tests/__init__.py +0 -0
  26. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/tests/conftest.py +0 -0
  27. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/tests/test_commands.py +0 -0
  28. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/tests/test_logging.py +0 -0
  29. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/tests/test_output.py +0 -0
  30. {datalegion_cli-1.2.0 → datalegion_cli-1.2.2}/tests/test_update.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: datalegion-cli
3
- Version: 1.2.0
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
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
20
21
  Classifier: Typing :: Typed
21
22
  Requires-Python: >=3.11
22
23
  Requires-Dist: cryptography>=44.0.0
@@ -246,7 +247,7 @@ The CLI is designed to be called by AI agents and scripts. Key patterns:
246
247
 
247
248
  ```bash
248
249
  # All output is JSON to stdout — parse it directly
249
- datalegion-cli person enrich --email john@example.com | jq '.matches[0].full_name'
250
+ datalegion-cli person enrich --email john@example.com | jq '.matches[0].person.full_name'
250
251
 
251
252
  # Pipe JSON payloads via stdin
252
253
  echo '{"email":"john@example.com","company":"Acme"}' | datalegion-cli person enrich --stdin
@@ -213,7 +213,7 @@ The CLI is designed to be called by AI agents and scripts. Key patterns:
213
213
 
214
214
  ```bash
215
215
  # All output is JSON to stdout — parse it directly
216
- datalegion-cli person enrich --email john@example.com | jq '.matches[0].full_name'
216
+ datalegion-cli person enrich --email john@example.com | jq '.matches[0].person.full_name'
217
217
 
218
218
  # Pipe JSON payloads via stdin
219
219
  echo '{"email":"john@example.com","company":"Acme"}' | datalegion-cli person enrich --stdin
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datalegion-cli"
3
- version = "1.2.0"
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"
@@ -24,6 +24,7 @@ classifiers = [
24
24
  "Programming Language :: Python :: 3.11",
25
25
  "Programming Language :: Python :: 3.12",
26
26
  "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
27
28
  "Environment :: Console",
28
29
  "Typing :: Typed",
29
30
  ]
@@ -56,6 +57,10 @@ build-backend = "hatchling.build"
56
57
  [tool.hatch.build.targets.wheel]
57
58
  packages = ["src/datalegion_cli"]
58
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
+
59
64
  [dependency-groups]
60
65
  dev = [
61
66
  "pytest>=8.3.0",
@@ -7,6 +7,7 @@ from typing import Any
7
7
  import httpx
8
8
  from loguru import logger
9
9
 
10
+ from datalegion_cli import __version__
10
11
  from datalegion_cli.config import get_api_key, get_base_url
11
12
 
12
13
 
@@ -42,7 +43,7 @@ class LegionClient:
42
43
  timeout: float = 60.0,
43
44
  ):
44
45
  self._base_url = base_url or get_base_url()
45
- self._api_key = api_key or get_api_key()
46
+ self._api_key = (api_key or "").strip() or get_api_key()
46
47
  self._timeout = timeout
47
48
  self._client: httpx.AsyncClient | None = None
48
49
  self.last_credits_used: str | None = None
@@ -56,6 +57,9 @@ class LegionClient:
56
57
  headers={
57
58
  "API-Key": self._api_key,
58
59
  "Content-Type": "application/json",
60
+ # Lets the API tell CLI traffic apart (its request log
61
+ # classifies datalegion-cli/* as 'cli').
62
+ "User-Agent": f"datalegion-cli/{__version__}",
59
63
  },
60
64
  )
61
65
  return self._client
@@ -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>` "
@@ -47,6 +47,15 @@ class TestLegionClientInit:
47
47
  assert client._api_key == "legion_k"
48
48
  assert client._timeout == 10.0
49
49
 
50
+ async def test_sends_identifying_user_agent(self):
51
+ """The API's request log classifies datalegion-cli/* as CLI traffic."""
52
+ from datalegion_cli import __version__
53
+
54
+ client = LegionClient(base_url="https://x.local", api_key="legion_k")
55
+ http = await client._get_client()
56
+ assert http.headers["User-Agent"] == f"datalegion-cli/{__version__}"
57
+ await client.close()
58
+
50
59
  def test_credit_headers_init_none(self, mock_api_key):
51
60
  client = LegionClient(base_url="https://x.local", api_key=mock_api_key)
52
61
  assert client.last_credits_used is None
@@ -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,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,33 +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@v9.0.0
13
- with:
14
- enable-cache: true
15
- - uses: actions/setup-python@v7
16
- with:
17
- python-version: "3.11"
18
- - run: uv sync --group dev
19
- - run: uv run ruff check src/ tests/
20
- - run: uv run ruff format --check src/ tests/
21
-
22
- test:
23
- runs-on: blacksmith-2vcpu-ubuntu-2404
24
- steps:
25
- - uses: actions/checkout@v7
26
- - uses: astral-sh/setup-uv@v9.0.0
27
- with:
28
- enable-cache: true
29
- - uses: actions/setup-python@v7
30
- with:
31
- python-version: "3.11"
32
- - run: uv sync --group dev
33
- - run: uv run pytest
@@ -1,229 +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@v9.0.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
- strategy:
55
- fail-fast: false
56
- matrix:
57
- python-version: ["3.11", "3.12", "3.13"]
58
- steps:
59
- - name: Checkout
60
- uses: actions/checkout@v7
61
-
62
- - name: Install uv
63
- uses: astral-sh/setup-uv@v9.0.0
64
- with:
65
- enable-cache: true
66
- cache-dependency-glob: "uv.lock"
67
-
68
- - name: Set up Python ${{ matrix.python-version }}
69
- run: uv python install ${{ matrix.python-version }}
70
-
71
- - name: Install dependencies
72
- run: uv sync --frozen --group dev
73
-
74
- - name: Run tests
75
- run: uv run pytest tests/ -v --tb=short
76
-
77
- - name: Prune uv cache
78
- if: always()
79
- run: uv cache prune --ci
80
-
81
- publish-pypi:
82
- name: Publish to PyPI
83
- needs: [lint, test]
84
- runs-on: blacksmith-2vcpu-ubuntu-2404
85
- timeout-minutes: 5
86
- permissions:
87
- contents: read
88
- id-token: write
89
- steps:
90
- - name: Checkout
91
- uses: actions/checkout@v7
92
-
93
- - name: Install uv
94
- uses: astral-sh/setup-uv@v9.0.0
95
-
96
- - name: Set up Python
97
- run: uv python install 3.11
98
-
99
- - name: Set version from tag
100
- run: |
101
- VERSION="${GITHUB_REF_NAME#v}"
102
- sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml
103
-
104
- - name: Build
105
- run: uv build
106
-
107
- - name: Check if version exists on PyPI
108
- id: pypi-check
109
- run: |
110
- VERSION="${GITHUB_REF_NAME#v}"
111
- if curl -sSf "https://pypi.org/pypi/datalegion-cli/${VERSION}/json" > /dev/null 2>&1; then
112
- echo "Version ${VERSION} already exists on PyPI, skipping publish"
113
- echo "exists=true" >> "$GITHUB_OUTPUT"
114
- else
115
- echo "exists=false" >> "$GITHUB_OUTPUT"
116
- fi
117
-
118
- - name: Publish to PyPI
119
- if: steps.pypi-check.outputs.exists != 'true'
120
- run: uv publish --trusted-publishing always
121
-
122
- update-homebrew:
123
- name: Update Homebrew Tap
124
- needs: [publish-pypi]
125
- runs-on: blacksmith-2vcpu-ubuntu-2404
126
- timeout-minutes: 10
127
- permissions:
128
- contents: read
129
- steps:
130
- - name: Wait for PyPI availability
131
- run: |
132
- VERSION="${GITHUB_REF_NAME#v}"
133
- for i in $(seq 1 30); do
134
- if curl -sSf "https://pypi.org/pypi/datalegion-cli/${VERSION}/json" > /dev/null 2>&1; then
135
- echo "Version ${VERSION} available on PyPI"
136
- exit 0
137
- fi
138
- echo "Waiting for PyPI... (attempt $i/30)"
139
- sleep 10
140
- done
141
- echo "::error::Timed out waiting for PyPI availability"
142
- exit 1
143
-
144
- - name: Generate Homebrew formula
145
- run: |
146
- VERSION="${GITHUB_REF_NAME#v}"
147
-
148
- # Get the actual sdist URL and digest from PyPI JSON API
149
- PYPI_JSON=$(curl -sSf "https://pypi.org/pypi/datalegion-cli/${VERSION}/json")
150
- 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'))")
151
- 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'))")
152
-
153
- cat > datalegion-cli.rb <<FORMULA
154
- class DatalegionCli < Formula
155
- desc "CLI for the Data Legion API - agent-friendly, fully async"
156
- homepage "https://www.datalegion.ai"
157
- url "${URL}"
158
- sha256 "${SHA256}"
159
- license "MIT"
160
-
161
- depends_on "python@3.13"
162
-
163
- def install
164
- venv = libexec/"venv"
165
- system Formula["python@3.13"].opt_bin/"python3.13", "-m", "venv", venv
166
- system venv/"bin/pip", "install", "datalegion-cli==#{version}"
167
- bin.install_symlink Dir[venv/"bin/datalegion-cli"]
168
- end
169
-
170
- test do
171
- assert_match version.to_s, shell_output("#{bin}/datalegion-cli version")
172
- end
173
- end
174
- FORMULA
175
-
176
- # Remove leading whitespace from heredoc indentation
177
- sed -i 's/^ //' datalegion-cli.rb
178
-
179
- - name: Push formula to tap
180
- env:
181
- GH_PAT: ${{ secrets.GH_PAT }}
182
- run: |
183
- VERSION="${GITHUB_REF_NAME#v}"
184
- git clone "https://x-access-token:${GH_PAT}@github.com/datalegion-ai/homebrew-tap.git" tap
185
- mkdir -p tap/Formula
186
- cp datalegion-cli.rb tap/Formula/datalegion-cli.rb
187
- cd tap
188
- git config user.name "github-actions[bot]"
189
- git config user.email "github-actions[bot]@users.noreply.github.com"
190
- git add Formula/datalegion-cli.rb
191
- git diff --staged --quiet || git commit -m "datalegion-cli ${VERSION}"
192
- git push
193
-
194
- github-release:
195
- name: Create GitHub Release
196
- needs: [publish-pypi]
197
- runs-on: blacksmith-2vcpu-ubuntu-2404
198
- timeout-minutes: 5
199
- permissions:
200
- contents: write
201
- steps:
202
- - name: Checkout
203
- uses: actions/checkout@v7
204
- with:
205
- ref: main
206
- token: ${{ secrets.GH_PAT }}
207
-
208
- - name: Update version in pyproject.toml
209
- run: |
210
- VERSION="${GITHUB_REF_NAME#v}"
211
- sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml
212
- git config user.name "github-actions[bot]"
213
- git config user.email "github-actions[bot]@users.noreply.github.com"
214
- git add pyproject.toml
215
- git diff --staged --quiet || git commit -m "chore: bump version to ${VERSION} [skip ci]"
216
- git push
217
-
218
- - name: Create Release
219
- uses: actions/github-script@v9
220
- with:
221
- script: |
222
- const tag = context.ref.replace('refs/tags/', '');
223
- await github.rest.repos.createRelease({
224
- owner: context.repo.owner,
225
- repo: context.repo.repo,
226
- tag_name: tag,
227
- name: tag,
228
- generate_release_notes: true,
229
- });