Skip to content

Commit cc0f703

Browse files
jlsajfjclaude
andcommitted
Scaffold Python package: hatchling build, src layout, CI
- Add pyproject.toml with hatchling backend, MIT license, Python 3.9+ support - Set up src/textql/ package layout with version, client stub, exception hierarchy - TextQL client reads TEXTQL_API_KEY / TEXTQL_BASE_URL from env; resource clients are deferred - Add pytest tests for client construction and configuration - Add GitHub Actions CI: ruff lint+format, pyright strict on src, pytest matrix Py 3.9-3.13 - Replace README placeholder with install/quickstart and status notice Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 97b2f1f commit cc0f703

9 files changed

Lines changed: 375 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
concurrency:
9+
group: ${{ github.workflow }}-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
lint:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-python@v5
18+
with:
19+
python-version: "3.12"
20+
- name: Create venv
21+
run: python -m venv .venv
22+
- name: Install
23+
run: |
24+
.venv/bin/pip install --upgrade pip
25+
.venv/bin/pip install -e ".[dev]"
26+
- name: Ruff
27+
run: .venv/bin/ruff check .
28+
- name: Ruff format check
29+
run: .venv/bin/ruff format --check .
30+
- name: Pyright
31+
run: .venv/bin/pyright
32+
33+
test:
34+
runs-on: ubuntu-latest
35+
strategy:
36+
fail-fast: false
37+
matrix:
38+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
39+
steps:
40+
- uses: actions/checkout@v4
41+
- uses: actions/setup-python@v5
42+
with:
43+
python-version: ${{ matrix.python-version }}
44+
- name: Install
45+
run: pip install -e ".[dev]"
46+
- name: Pytest
47+
run: pytest

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Changelog
2+
3+
All notable changes to the `textql` Python SDK will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
### Added
11+
- Initial repository scaffolding: `pyproject.toml`, `src/textql/` package layout, exception hierarchy, sync client stub.
12+
- CI workflow: ruff lint + format check, pyright strict, pytest on Python 3.9–3.13.

README.md

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,69 @@
1-
# textql-python
2-
Official Python SDK for the TextQL Platform API
1+
# TextQL Python SDK
2+
3+
[![PyPI version](https://img.shields.io/pypi/v/textql.svg)](https://pypi.org/project/textql/)
4+
[![Python versions](https://img.shields.io/pypi/pyversions/textql.svg)](https://pypi.org/project/textql/)
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6+
7+
Official Python SDK for the [TextQL Platform API](https://docs.textql.com).
8+
9+
> **Status:** v0.1.0 is a scaffolding release. Resource clients (`chat`, `playbooks`, `sandbox`, `connectors`) are stubs and not yet functional.
10+
11+
## Installation
12+
13+
```bash
14+
pip install textql
15+
```
16+
17+
Requires Python 3.9+.
18+
19+
## Quickstart
20+
21+
```python
22+
from textql import TextQL
23+
24+
client = TextQL(api_key="tql_...") # or set TEXTQL_API_KEY in the environment
25+
```
26+
27+
Once resource clients land, the surface will look like:
28+
29+
```python
30+
# Simple chat
31+
response = client.chat.create(question="What was total revenue last quarter?")
32+
33+
# Streaming
34+
for event in client.chat.stream(question="..."):
35+
print(event.text, end="", flush=True)
36+
37+
# File upload
38+
response = client.chat.create(
39+
question="Analyze this",
40+
files=[{"path": "./sales.csv"}],
41+
)
42+
```
43+
44+
## Configuration
45+
46+
| Option | Env var | Default |
47+
|---|---|---|
48+
| `api_key` | `TEXTQL_API_KEY` | — (required) |
49+
| `base_url` | `TEXTQL_BASE_URL` | `https://api.textql.com` |
50+
| `timeout` || `60.0` seconds |
51+
52+
## Development
53+
54+
```bash
55+
pip install -e ".[dev]"
56+
ruff check . && ruff format --check .
57+
pyright
58+
pytest
59+
```
60+
61+
## Links
62+
63+
- [API documentation](https://docs.textql.com)
64+
- [Changelog](CHANGELOG.md)
65+
- [Issues](https://github.com/TextQLLabs/textql-python/issues)
66+
67+
## License
68+
69+
[MIT](LICENSE)

pyproject.toml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "textql"
7+
description = "Official Python SDK for the TextQL Platform API"
8+
readme = "README.md"
9+
license = { file = "LICENSE" }
10+
requires-python = ">=3.9"
11+
authors = [{ name = "TextQL", email = "support@textql.com" }]
12+
keywords = ["textql", "sdk", "api", "analytics", "ana"]
13+
classifiers = [
14+
"Development Status :: 3 - Alpha",
15+
"Intended Audience :: Developers",
16+
"License :: OSI Approved :: MIT License",
17+
"Programming Language :: Python :: 3",
18+
"Programming Language :: Python :: 3.9",
19+
"Programming Language :: Python :: 3.10",
20+
"Programming Language :: Python :: 3.11",
21+
"Programming Language :: Python :: 3.12",
22+
"Programming Language :: Python :: 3.13",
23+
"Topic :: Software Development :: Libraries :: Python Modules",
24+
]
25+
dynamic = ["version"]
26+
dependencies = [
27+
"httpx >=0.27,<1",
28+
"typing_extensions >=4.7; python_version < '3.11'",
29+
]
30+
31+
[project.optional-dependencies]
32+
dev = [
33+
"pytest >=8",
34+
"pytest-asyncio >=0.23",
35+
"ruff >=0.6",
36+
"pyright >=1.1.380",
37+
"respx >=0.21",
38+
]
39+
40+
[project.urls]
41+
Homepage = "https://textql.com"
42+
Documentation = "https://docs.textql.com"
43+
Repository = "https://github.com/TextQLLabs/textql-python"
44+
Issues = "https://github.com/TextQLLabs/textql-python/issues"
45+
Changelog = "https://github.com/TextQLLabs/textql-python/blob/main/CHANGELOG.md"
46+
47+
[tool.hatch.version]
48+
path = "src/textql/_version.py"
49+
50+
[tool.hatch.build.targets.wheel]
51+
packages = ["src/textql"]
52+
53+
[tool.ruff]
54+
line-length = 100
55+
target-version = "py39"
56+
src = ["src", "tests"]
57+
58+
[tool.ruff.lint]
59+
select = [
60+
"E", # pycodestyle errors
61+
"F", # pyflakes
62+
"I", # isort
63+
"B", # bugbear
64+
"UP", # pyupgrade
65+
"RUF", # ruff-specific
66+
]
67+
68+
[tool.pyright]
69+
include = ["src", "tests"]
70+
pythonVersion = "3.9"
71+
typeCheckingMode = "strict"
72+
venvPath = "."
73+
venv = ".venv"
74+
75+
[[tool.pyright.executionEnvironments]]
76+
root = "tests"
77+
reportUnknownMemberType = "none"
78+
reportUnknownParameterType = "none"
79+
reportUnknownVariableType = "none"
80+
reportMissingParameterType = "none"
81+
82+
[tool.pytest.ini_options]
83+
testpaths = ["tests"]
84+
asyncio_mode = "auto"

src/textql/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from ._client import TextQL
2+
from ._exceptions import (
3+
APIConnectionError,
4+
APIError,
5+
APITimeoutError,
6+
AuthenticationError,
7+
NotFoundError,
8+
PermissionDeniedError,
9+
RateLimitError,
10+
TextQLError,
11+
)
12+
from ._version import __version__
13+
14+
__all__ = [
15+
"APIConnectionError",
16+
"APIError",
17+
"APITimeoutError",
18+
"AuthenticationError",
19+
"NotFoundError",
20+
"PermissionDeniedError",
21+
"RateLimitError",
22+
"TextQL",
23+
"TextQLError",
24+
"__version__",
25+
]

src/textql/_client.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from __future__ import annotations
2+
3+
import os
4+
from typing import Any
5+
6+
import httpx
7+
8+
from ._version import __version__
9+
10+
DEFAULT_BASE_URL = "https://api.textql.com"
11+
DEFAULT_TIMEOUT = 60.0
12+
13+
14+
class TextQL:
15+
"""Synchronous client for the TextQL Platform API.
16+
17+
Resource clients (`chat`, `playbooks`, `sandbox`, `connectors`) are stubs
18+
in v0.1.0 — they will be filled in as the v2 SDK takes shape.
19+
"""
20+
21+
def __init__(
22+
self,
23+
api_key: str | None = None,
24+
*,
25+
base_url: str | None = None,
26+
timeout: float = DEFAULT_TIMEOUT,
27+
http_client: httpx.Client | None = None,
28+
) -> None:
29+
key = api_key or os.environ.get("TEXTQL_API_KEY")
30+
if not key:
31+
raise ValueError(
32+
"No API key provided. Pass api_key=... or set TEXTQL_API_KEY in the environment."
33+
)
34+
35+
self.api_key = key
36+
resolved_base = base_url or os.environ.get("TEXTQL_BASE_URL") or DEFAULT_BASE_URL
37+
self.base_url = resolved_base.rstrip("/")
38+
self._http = http_client or httpx.Client(
39+
base_url=self.base_url,
40+
timeout=timeout,
41+
headers=self._default_headers(),
42+
)
43+
44+
def _default_headers(self) -> dict[str, str]:
45+
return {
46+
"Authorization": f"Bearer {self.api_key}",
47+
"User-Agent": f"textql-python/{__version__}",
48+
"Accept": "application/json",
49+
}
50+
51+
def close(self) -> None:
52+
self._http.close()
53+
54+
def __enter__(self) -> TextQL:
55+
return self
56+
57+
def __exit__(self, *exc: Any) -> None:
58+
self.close()

src/textql/_exceptions.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from __future__ import annotations
2+
3+
4+
class TextQLError(Exception):
5+
"""Base class for all TextQL SDK errors."""
6+
7+
8+
class APIError(TextQLError):
9+
def __init__(
10+
self,
11+
message: str,
12+
*,
13+
status_code: int | None = None,
14+
request_id: str | None = None,
15+
) -> None:
16+
super().__init__(message)
17+
self.status_code = status_code
18+
self.request_id = request_id
19+
20+
21+
class AuthenticationError(APIError):
22+
pass
23+
24+
25+
class PermissionDeniedError(APIError):
26+
pass
27+
28+
29+
class NotFoundError(APIError):
30+
pass
31+
32+
33+
class RateLimitError(APIError):
34+
pass
35+
36+
37+
class APIConnectionError(TextQLError):
38+
pass
39+
40+
41+
class APITimeoutError(APIConnectionError):
42+
pass

src/textql/_version.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "0.1.0"

tests/test_client.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from textql import TextQL, __version__
6+
7+
8+
def test_version_is_set() -> None:
9+
assert __version__
10+
11+
12+
def test_client_requires_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
13+
monkeypatch.delenv("TEXTQL_API_KEY", raising=False)
14+
with pytest.raises(ValueError, match="No API key"):
15+
TextQL()
16+
17+
18+
def test_client_reads_api_key_from_env(monkeypatch: pytest.MonkeyPatch) -> None:
19+
monkeypatch.setenv("TEXTQL_API_KEY", "tql_test_123")
20+
client = TextQL()
21+
assert client.api_key == "tql_test_123"
22+
23+
24+
def test_client_explicit_api_key_overrides_env(monkeypatch: pytest.MonkeyPatch) -> None:
25+
monkeypatch.setenv("TEXTQL_API_KEY", "from_env")
26+
client = TextQL(api_key="explicit")
27+
assert client.api_key == "explicit"
28+
29+
30+
def test_client_strips_trailing_slash_from_base_url() -> None:
31+
client = TextQL(api_key="tql_x", base_url="https://example.com/")
32+
assert client.base_url == "https://example.com"
33+
34+
35+
def test_client_context_manager() -> None:
36+
with TextQL(api_key="tql_x") as client:
37+
assert client.api_key == "tql_x"

0 commit comments

Comments
 (0)