snowleopard 0.1.0__tar.gz → 0.1.1__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.
- snowleopard-0.1.1/.gitignore +13 -0
- snowleopard-0.1.1/CONTRIBUTING.md +60 -0
- snowleopard-0.1.1/LICENSE +21 -0
- snowleopard-0.1.1/PKG-INFO +119 -0
- snowleopard-0.1.1/README.md +85 -0
- snowleopard-0.1.1/pyproject.toml +59 -0
- snowleopard-0.1.1/src/snowleopard/__init__.py +13 -0
- snowleopard-0.1.1/src/snowleopard/async_client.py +62 -0
- snowleopard-0.1.1/src/snowleopard/cli.py +112 -0
- snowleopard-0.1.1/src/snowleopard/client.py +64 -0
- snowleopard-0.1.1/src/snowleopard/client_base.py +66 -0
- snowleopard-0.1.1/src/snowleopard/models.py +144 -0
- snowleopard-0.1.0/LICENSE +0 -2
- snowleopard-0.1.0/PKG-INFO +0 -16
- snowleopard-0.1.0/README.md +0 -1
- snowleopard-0.1.0/pyproject.toml +0 -39
- snowleopard-0.1.0/setup.cfg +0 -4
- snowleopard-0.1.0/src/snowleopard/__init__.py +0 -5
- snowleopard-0.1.0/src/snowleopard.egg-info/PKG-INFO +0 -16
- snowleopard-0.1.0/src/snowleopard.egg-info/SOURCES.txt +0 -9
- snowleopard-0.1.0/src/snowleopard.egg-info/dependency_links.txt +0 -1
- snowleopard-0.1.0/src/snowleopard.egg-info/requires.txt +0 -3
- snowleopard-0.1.0/src/snowleopard.egg-info/top_level.txt +0 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing to the Snow Leopard Python SDK!
|
|
4
|
+
|
|
5
|
+
Questions? Reach out on [Discord](https://discord.gg/WGAyr8NpEX)
|
|
6
|
+
|
|
7
|
+
## Getting Started
|
|
8
|
+
|
|
9
|
+
### Prerequisites
|
|
10
|
+
|
|
11
|
+
- Python 3.11 (our default dev version)
|
|
12
|
+
- [uv](https://docs.astral.sh/uv/) package manager
|
|
13
|
+
|
|
14
|
+
### Setup
|
|
15
|
+
|
|
16
|
+
0. **Fork this repository**
|
|
17
|
+
If you are unfamiliar contributing to open source projects, see [first-contributions](https://github.com/firstcontributions/first-contributions) for a great walkthrough.
|
|
18
|
+
|
|
19
|
+
1. **Clone your fork**
|
|
20
|
+
```bash
|
|
21
|
+
git clone https://github.com/SnowLeopard-AI/snowleopard_py.git
|
|
22
|
+
cd snowleopard_py
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
2. **Install dependencies**
|
|
26
|
+
```bash
|
|
27
|
+
uv sync --dev
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
3. **Running tests**
|
|
31
|
+
```bash
|
|
32
|
+
uv run pytest
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Testing with Different Python Versions
|
|
36
|
+
|
|
37
|
+
To test against a specific Python version:
|
|
38
|
+
```bash
|
|
39
|
+
uv run --python 3.10 pytest
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### Re-Recording Cassettes or Writing New Tests
|
|
44
|
+
|
|
45
|
+
Tests use cassettes to be able to easily mock snowleopard network traffic. No configuration is needed when re-running
|
|
46
|
+
tests, but to re-record or author new tests a .env file is needed in tests.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
touch tests/.env
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```.dotenv
|
|
53
|
+
#SNOWLEOPARD_LOC=
|
|
54
|
+
#SUPERHEROES_DFID=
|
|
55
|
+
#SNOWLEOPARD_API_KEY=
|
|
56
|
+
SNOWLEOPARD_TEST_RECORD_MODE=once
|
|
57
|
+
```
|
|
58
|
+
See [pyvcr docs](https://vcrpy.readthedocs.io/en/latest/usage.html#record-modes) for more information on recording
|
|
59
|
+
modes. Once will re-record only if the file is missing, so to re-record you must delete it first.
|
|
60
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Snow Leopard AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: snowleopard
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: snowleopard.ai client library
|
|
5
|
+
Author-email: Snowy <hello@snowleopard.ai>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Snow Leopard AI
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
29
|
+
Classifier: Environment :: Console
|
|
30
|
+
Classifier: Intended Audience :: Developers
|
|
31
|
+
Requires-Python: >=3.8
|
|
32
|
+
Requires-Dist: httpx>=0.28.1
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# Snow Leopard SDK for Python
|
|
36
|
+
|
|
37
|
+
Python client library for [Snow Leopard Playground](https://try.snowleopard.ai) APIs.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install snowleopard
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from snowleopard import SnowLeopardPlaygroundClient
|
|
49
|
+
|
|
50
|
+
# Initialize the client (or AsyncSnowLeopardPlaygroundClient)
|
|
51
|
+
client = SnowLeopardPlaygroundClient(api_key="your-api-key")
|
|
52
|
+
|
|
53
|
+
# Query your data in natural language
|
|
54
|
+
response = client.retrieve(
|
|
55
|
+
datafile_id="your-datafile-id",
|
|
56
|
+
user_query="How many users signed up last month?"
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Getting Started
|
|
61
|
+
|
|
62
|
+
1. **Get your API key** from [https://auth.snowleopard.ai/account/api_keys](https://auth.snowleopard.ai/account/api_keys)
|
|
63
|
+
2. **Upload your datafiles** at [https://try.snowleopard.ai](https://try.snowleopard.ai)
|
|
64
|
+
3. **Set your API key** via environment variable:
|
|
65
|
+
```bash
|
|
66
|
+
export SNOWLEOPARD_API_KEY="your-api-key"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or pass it directly to the client:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
SnowLeopardPlaygroundClient(api_key="your-api-key")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Usage
|
|
76
|
+
|
|
77
|
+
### Synchronous Client
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from snowleopard import SnowLeopardPlaygroundClient
|
|
81
|
+
|
|
82
|
+
with SnowLeopardPlaygroundClient() as client:
|
|
83
|
+
# Get data directly from a natural language query
|
|
84
|
+
response = client.retrieve("datafile-id", "What's the total revenue?")
|
|
85
|
+
print(response.data)
|
|
86
|
+
|
|
87
|
+
# Stream natural language summary of live data
|
|
88
|
+
for chunk in client.response("datafile-id", "Show me top 10 customers"):
|
|
89
|
+
print(chunk)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Async Client
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from snowleopard import AsyncSnowLeopardPlaygroundClient
|
|
96
|
+
|
|
97
|
+
async with AsyncSnowLeopardPlaygroundClient() as client:
|
|
98
|
+
# Get complete results
|
|
99
|
+
response = await client.retrieve("datafile-id", "What's the total revenue?")
|
|
100
|
+
print(response.data)
|
|
101
|
+
|
|
102
|
+
# Get streaming results
|
|
103
|
+
async for chunk in client.response("datafile-id", "Show me top 10 customers"):
|
|
104
|
+
print(chunk)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### CLI
|
|
108
|
+
|
|
109
|
+
The SDK includes a command-line interface:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install snowleopard
|
|
113
|
+
snowy retrieve <datafile-id> "How many records are there?"
|
|
114
|
+
snowy response <datafile-id> "Summarize the data"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Contributing
|
|
118
|
+
|
|
119
|
+
For SDK developer docs and how to contribute, see [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Snow Leopard SDK for Python
|
|
2
|
+
|
|
3
|
+
Python client library for [Snow Leopard Playground](https://try.snowleopard.ai) APIs.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install snowleopard
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from snowleopard import SnowLeopardPlaygroundClient
|
|
15
|
+
|
|
16
|
+
# Initialize the client (or AsyncSnowLeopardPlaygroundClient)
|
|
17
|
+
client = SnowLeopardPlaygroundClient(api_key="your-api-key")
|
|
18
|
+
|
|
19
|
+
# Query your data in natural language
|
|
20
|
+
response = client.retrieve(
|
|
21
|
+
datafile_id="your-datafile-id",
|
|
22
|
+
user_query="How many users signed up last month?"
|
|
23
|
+
)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Getting Started
|
|
27
|
+
|
|
28
|
+
1. **Get your API key** from [https://auth.snowleopard.ai/account/api_keys](https://auth.snowleopard.ai/account/api_keys)
|
|
29
|
+
2. **Upload your datafiles** at [https://try.snowleopard.ai](https://try.snowleopard.ai)
|
|
30
|
+
3. **Set your API key** via environment variable:
|
|
31
|
+
```bash
|
|
32
|
+
export SNOWLEOPARD_API_KEY="your-api-key"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or pass it directly to the client:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
SnowLeopardPlaygroundClient(api_key="your-api-key")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Synchronous Client
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from snowleopard import SnowLeopardPlaygroundClient
|
|
47
|
+
|
|
48
|
+
with SnowLeopardPlaygroundClient() as client:
|
|
49
|
+
# Get data directly from a natural language query
|
|
50
|
+
response = client.retrieve("datafile-id", "What's the total revenue?")
|
|
51
|
+
print(response.data)
|
|
52
|
+
|
|
53
|
+
# Stream natural language summary of live data
|
|
54
|
+
for chunk in client.response("datafile-id", "Show me top 10 customers"):
|
|
55
|
+
print(chunk)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Async Client
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from snowleopard import AsyncSnowLeopardPlaygroundClient
|
|
62
|
+
|
|
63
|
+
async with AsyncSnowLeopardPlaygroundClient() as client:
|
|
64
|
+
# Get complete results
|
|
65
|
+
response = await client.retrieve("datafile-id", "What's the total revenue?")
|
|
66
|
+
print(response.data)
|
|
67
|
+
|
|
68
|
+
# Get streaming results
|
|
69
|
+
async for chunk in client.response("datafile-id", "Show me top 10 customers"):
|
|
70
|
+
print(chunk)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### CLI
|
|
74
|
+
|
|
75
|
+
The SDK includes a command-line interface:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install snowleopard
|
|
79
|
+
snowy retrieve <datafile-id> "How many records are there?"
|
|
80
|
+
snowy response <datafile-id> "Summarize the data"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Contributing
|
|
84
|
+
|
|
85
|
+
For SDK developer docs and how to contribute, see [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "snowleopard"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
|
|
5
|
+
description = "snowleopard.ai client library"
|
|
6
|
+
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
license = {file = "LICENSE"}
|
|
9
|
+
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Snowy", email = "hello@snowleopard.ai" }
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
# list of classifier strings, defined here: https://pypi.org/classifiers/
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
requires-python = ">=3.8"
|
|
22
|
+
|
|
23
|
+
dependencies = [
|
|
24
|
+
"httpx>=0.28.1",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[dependency-groups]
|
|
28
|
+
dev = [
|
|
29
|
+
"pytest>=8.3.5",
|
|
30
|
+
"pytest-asyncio>=0.24.0",
|
|
31
|
+
"pytest-recording>=0.13.2",
|
|
32
|
+
"python-dotenv>=1.0.1",
|
|
33
|
+
"ruff>=0.14.4",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
snowy = "snowleopard.cli:main"
|
|
38
|
+
|
|
39
|
+
[build-system]
|
|
40
|
+
requires = ["hatchling"]
|
|
41
|
+
build-backend = "hatchling.build"
|
|
42
|
+
|
|
43
|
+
[tool.hatch.version]
|
|
44
|
+
path = "src/snowleopard/__init__.py"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.sdist]
|
|
47
|
+
# NOTE: .gitignore cannot be excluded from the sdist, see:
|
|
48
|
+
# https://github.com/pypa/hatch/discussions/368
|
|
49
|
+
exclude = [
|
|
50
|
+
"/.github",
|
|
51
|
+
"/tests",
|
|
52
|
+
"/uv.lock",
|
|
53
|
+
"/.python-version",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[tool.hatch.build.targets.wheel]
|
|
57
|
+
packages = [
|
|
58
|
+
"src/snowleopard",
|
|
59
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# copyright 2025 Snow Leopard, Inc
|
|
3
|
+
# released under the MIT license - see LICENSE file
|
|
4
|
+
|
|
5
|
+
from snowleopard.async_client import AsyncSnowLeopardPlaygroundClient
|
|
6
|
+
from snowleopard.client import SnowLeopardPlaygroundClient
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.1"
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"SnowLeopardPlaygroundClient",
|
|
12
|
+
"AsyncSnowLeopardPlaygroundClient",
|
|
13
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# copyright 2025 Snow Leopard, Inc
|
|
3
|
+
# released under the MIT license - see LICENSE file
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import AsyncGenerator, Optional, Dict, Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from snowleopard.client_base import SLClientBase
|
|
10
|
+
from snowleopard.models import ResponseDataObjects, RetrieveResponseObjects, parse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AsyncSnowLeopardPlaygroundClient(SLClientBase):
|
|
14
|
+
client: httpx.AsyncClient
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
api_key: Optional[str] = None,
|
|
19
|
+
timeout: Optional[httpx.Timeout] = None,
|
|
20
|
+
loc: str = None,
|
|
21
|
+
):
|
|
22
|
+
config = self._config(api_key, timeout, loc)
|
|
23
|
+
self.client = httpx.AsyncClient(
|
|
24
|
+
base_url=config.loc, headers=config.headers(), timeout=config.timeout
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
async def retrieve(
|
|
28
|
+
self,
|
|
29
|
+
datafile_id: str,
|
|
30
|
+
user_query: str,
|
|
31
|
+
known_data: Optional[Dict[str, Any]] = None,
|
|
32
|
+
) -> RetrieveResponseObjects:
|
|
33
|
+
resp = await self.client.post(
|
|
34
|
+
url=self._build_path(datafile_id, "retrieve"),
|
|
35
|
+
json=self._build_request_body(user_query, known_data),
|
|
36
|
+
)
|
|
37
|
+
return self._parse_retrieve(resp)
|
|
38
|
+
|
|
39
|
+
async def response(
|
|
40
|
+
self,
|
|
41
|
+
datafile_id: str,
|
|
42
|
+
user_query: str,
|
|
43
|
+
known_data: Optional[Dict[str, Any]] = None,
|
|
44
|
+
) -> AsyncGenerator[ResponseDataObjects, None]:
|
|
45
|
+
async with self.client.stream(
|
|
46
|
+
"POST",
|
|
47
|
+
self._build_path(datafile_id, "response"),
|
|
48
|
+
json=self._build_request_body(user_query, known_data),
|
|
49
|
+
) as resp:
|
|
50
|
+
resp.raise_for_status()
|
|
51
|
+
async for line in resp.aiter_lines():
|
|
52
|
+
yield parse(json.loads(line))
|
|
53
|
+
|
|
54
|
+
async def __aenter__(self):
|
|
55
|
+
await self.client.__aenter__()
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
59
|
+
await self.client.__aexit__(exc_type, exc_val, exc_tb)
|
|
60
|
+
|
|
61
|
+
async def close(self):
|
|
62
|
+
await self.client.aclose()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# copyright 2025 Snow Leopard, Inc
|
|
3
|
+
# released under the MIT license - see LICENSE file
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
import argparse
|
|
9
|
+
from typing import List, Optional, Dict, Any
|
|
10
|
+
|
|
11
|
+
from httpx import HTTPStatusError
|
|
12
|
+
from snowleopard import __version__, SnowLeopardPlaygroundClient
|
|
13
|
+
from snowleopard.models import RetrieveResponseError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _create_parser() -> argparse.ArgumentParser:
|
|
17
|
+
"""Create and return the argument parser."""
|
|
18
|
+
parser = argparse.ArgumentParser(
|
|
19
|
+
prog="snowy", description="Snow Leopard client library CLI"
|
|
20
|
+
)
|
|
21
|
+
parser.add_argument("--apikey", "-a", required=False, help="Snow Leopard API key")
|
|
22
|
+
parser.add_argument("--loc", "-l", required=False, help="Snow Leopard location")
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--version", action="version", version=f"%(prog)s {__version__}"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
parser.set_defaults(command_func=None)
|
|
28
|
+
subparsers = parser.add_subparsers(
|
|
29
|
+
dest="command",
|
|
30
|
+
metavar="command",
|
|
31
|
+
help="run with <command name> --help for more info",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
retrieve = subparsers.add_parser(
|
|
35
|
+
"retrieve", help="Retrieve data for natural language query"
|
|
36
|
+
)
|
|
37
|
+
retrieve.set_defaults(command_func=_retrieve)
|
|
38
|
+
response = subparsers.add_parser(
|
|
39
|
+
"response", help="Get streaming response for natural language query"
|
|
40
|
+
)
|
|
41
|
+
response.set_defaults(command_func=_response)
|
|
42
|
+
|
|
43
|
+
for subparser in (retrieve, response):
|
|
44
|
+
subparser.add_argument(
|
|
45
|
+
"--knownData",
|
|
46
|
+
"-d",
|
|
47
|
+
action="append",
|
|
48
|
+
help="Known data in key=value format (can be specified multiple times)",
|
|
49
|
+
)
|
|
50
|
+
subparser.add_argument("datafile", type=str, help="ID for Datafile to query")
|
|
51
|
+
subparser.add_argument("question", type=str, help="Natural language query")
|
|
52
|
+
|
|
53
|
+
return parser
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _parse_known_data(known_data_list: Optional[List[str]]) -> Optional[Dict[str, Any]]:
|
|
57
|
+
if not known_data_list:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
result = {}
|
|
61
|
+
for item in known_data_list:
|
|
62
|
+
if "=" not in item:
|
|
63
|
+
print(f"Error: Invalid knownData format '{item}'. Expected key=value", file=sys.stderr)
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
key, value = item.split("=", 1)
|
|
66
|
+
result[key] = value
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _get_client(parsed_args):
|
|
71
|
+
try:
|
|
72
|
+
client = SnowLeopardPlaygroundClient(api_key=parsed_args.apikey, loc=parsed_args.loc)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(str(e), file=sys.stderr)
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
return client
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _retrieve(parsed_args):
|
|
80
|
+
try:
|
|
81
|
+
known_data = _parse_known_data(parsed_args.knownData)
|
|
82
|
+
with _get_client(parsed_args) as client:
|
|
83
|
+
resp = client.retrieve(parsed_args.datafile, parsed_args.question, known_data)
|
|
84
|
+
print(json.dumps(dataclasses.asdict(resp)))
|
|
85
|
+
if isinstance(resp, RetrieveResponseError):
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
except HTTPStatusError as e:
|
|
88
|
+
print(str(e), file=sys.stderr)
|
|
89
|
+
sys.exit(1)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _response(parsed_args):
|
|
93
|
+
try:
|
|
94
|
+
known_data = _parse_known_data(parsed_args.knownData)
|
|
95
|
+
with _get_client(parsed_args) as client:
|
|
96
|
+
for chunk in client.response(parsed_args.datafile, parsed_args.question, known_data):
|
|
97
|
+
print(json.dumps(dataclasses.asdict(chunk)))
|
|
98
|
+
except HTTPStatusError as e:
|
|
99
|
+
print(str(e), file=sys.stderr)
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def main(args: Optional[List[str]] = None) -> None:
|
|
104
|
+
"""CLI entry point for snowleopard."""
|
|
105
|
+
parser = _create_parser()
|
|
106
|
+
parsed_args = parser.parse_args(args=args)
|
|
107
|
+
|
|
108
|
+
if parsed_args.command_func is not None:
|
|
109
|
+
parsed_args.command_func(parsed_args)
|
|
110
|
+
else:
|
|
111
|
+
parser.print_help(file=sys.stderr)
|
|
112
|
+
sys.exit(1)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# copyright 2025 Snow Leopard, Inc
|
|
3
|
+
# released under the MIT license - see LICENSE file
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Optional, Generator, Dict, Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from snowleopard.client_base import SLClientBase
|
|
10
|
+
from snowleopard.models import parse, RetrieveResponseObjects, ResponseDataObjects
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SnowLeopardPlaygroundClient(SLClientBase):
|
|
14
|
+
client: httpx.Client
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
api_key: Optional[str] = None,
|
|
19
|
+
timeout: Optional[httpx.Timeout] = None,
|
|
20
|
+
loc: str = None,
|
|
21
|
+
):
|
|
22
|
+
config = self._config(api_key, timeout, loc)
|
|
23
|
+
self.client = httpx.Client(
|
|
24
|
+
base_url=config.loc, headers=config.headers(), timeout=config.timeout
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def retrieve(
|
|
28
|
+
self,
|
|
29
|
+
datafile_id: str,
|
|
30
|
+
user_query: str,
|
|
31
|
+
known_data: Optional[Dict[str, Any]] = None,
|
|
32
|
+
) -> RetrieveResponseObjects:
|
|
33
|
+
resp = self.client.post(
|
|
34
|
+
url=self._build_path(datafile_id, "retrieve"),
|
|
35
|
+
json=self._build_request_body(user_query, known_data),
|
|
36
|
+
)
|
|
37
|
+
if resp.status_code not in (200, 409):
|
|
38
|
+
resp.raise_for_status()
|
|
39
|
+
return self._parse_retrieve(resp)
|
|
40
|
+
|
|
41
|
+
def response(
|
|
42
|
+
self,
|
|
43
|
+
datafile_id: str,
|
|
44
|
+
user_query: str,
|
|
45
|
+
known_data: Optional[Dict[str, Any]] = None,
|
|
46
|
+
) -> Generator[ResponseDataObjects, None, None]:
|
|
47
|
+
with self.client.stream(
|
|
48
|
+
"POST",
|
|
49
|
+
url=self._build_path(datafile_id, "response"),
|
|
50
|
+
json=self._build_request_body(user_query, known_data),
|
|
51
|
+
) as resp:
|
|
52
|
+
resp.raise_for_status()
|
|
53
|
+
for line in resp.iter_lines():
|
|
54
|
+
yield parse(json.loads(line))
|
|
55
|
+
|
|
56
|
+
def __enter__(self):
|
|
57
|
+
self.client.__enter__()
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
61
|
+
self.client.__exit__(exc_type, exc_val, exc_tb)
|
|
62
|
+
|
|
63
|
+
def close(self):
|
|
64
|
+
self.client.close()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# copyright 2025 Snow Leopard, Inc
|
|
3
|
+
# released under the MIT license - see LICENSE file
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Optional, Dict, Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from snowleopard.models import parse
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class SLConfig:
|
|
16
|
+
api_key: str
|
|
17
|
+
timeout: httpx.Timeout
|
|
18
|
+
loc: str
|
|
19
|
+
|
|
20
|
+
def headers(self):
|
|
21
|
+
headers = {}
|
|
22
|
+
if self.api_key:
|
|
23
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
24
|
+
return headers
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SLClientBase:
|
|
28
|
+
@staticmethod
|
|
29
|
+
def _config(
|
|
30
|
+
api_key: Optional[str], timeout: Optional[httpx.Timeout], loc: Optional[str]
|
|
31
|
+
) -> SLConfig:
|
|
32
|
+
api_key = api_key or os.environ.get("SNOWLEOPARD_API_KEY")
|
|
33
|
+
if api_key is None:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
'Missing required argument "api_key" and environment variable "SNOWLEOPARD_API_KEY" not set'
|
|
36
|
+
)
|
|
37
|
+
timeout = timeout or httpx.Timeout(
|
|
38
|
+
connect=5.0, read=600.0, write=10.0, pool=5.0
|
|
39
|
+
)
|
|
40
|
+
loc = loc or os.environ.get("SNOWLEOPARD_LOC", "https://api.snowleopard.ai")
|
|
41
|
+
if not loc:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
'Missing required argument "loc" and environment variable "SNOWLEOPARD_LOC" not set'
|
|
44
|
+
)
|
|
45
|
+
return SLConfig(api_key, timeout, loc)
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def _build_path(datafile_id: str, endpoint: str) -> str:
|
|
49
|
+
return f"datafiles/{datafile_id}/{endpoint}"
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def _build_request_body(
|
|
53
|
+
user_query: str, known_data: Optional[Dict[str, Any]] = None
|
|
54
|
+
) -> Dict[str, Any]:
|
|
55
|
+
body = {"userQuery": user_query}
|
|
56
|
+
if known_data is not None:
|
|
57
|
+
body["knownData"] = known_data
|
|
58
|
+
return body
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def _parse_retrieve(resp):
|
|
62
|
+
try:
|
|
63
|
+
return parse(resp.json())
|
|
64
|
+
except Exception:
|
|
65
|
+
resp.raise_for_status()
|
|
66
|
+
raise
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# copyright 2025 Snow Leopard, Inc
|
|
3
|
+
# released under the MIT license - see LICENSE file
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, fields
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Any, Dict, List, Optional, Union
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StrEnum(str, Enum):
|
|
13
|
+
"""String enum compatible with Python 3.8"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class RetrieveResponse:
|
|
19
|
+
objType = "retrieveResponse"
|
|
20
|
+
|
|
21
|
+
callId: str
|
|
22
|
+
data: List[Union[SchemaData, ErrorSchemaData]]
|
|
23
|
+
responseStatus: ResponseStatus
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class RetrieveResponseError:
|
|
28
|
+
objType = "apiError"
|
|
29
|
+
|
|
30
|
+
callId: str
|
|
31
|
+
responseStatus: str
|
|
32
|
+
description: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class SchemaData:
|
|
37
|
+
objType = "schemaData"
|
|
38
|
+
|
|
39
|
+
schemaId: str
|
|
40
|
+
schemaType: str
|
|
41
|
+
query: str
|
|
42
|
+
rows: List[Dict[str, Any]]
|
|
43
|
+
querySummary: Dict[str, Any]
|
|
44
|
+
rowMax: int
|
|
45
|
+
isTrimmed: bool
|
|
46
|
+
callId: str = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class ErrorSchemaData:
|
|
51
|
+
objType = "errorSchemaData"
|
|
52
|
+
|
|
53
|
+
schemaType: str
|
|
54
|
+
schemaId: str
|
|
55
|
+
query: str
|
|
56
|
+
error: str
|
|
57
|
+
querySummary: Dict[str, Any]
|
|
58
|
+
datastoreExceptionInfo: Optional[str] = None
|
|
59
|
+
callId: str = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class ResponseStart:
|
|
64
|
+
objType = "responseStart"
|
|
65
|
+
|
|
66
|
+
callId: str
|
|
67
|
+
userQuery: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class ResponseData:
|
|
72
|
+
objType = "responseData"
|
|
73
|
+
|
|
74
|
+
callId: str
|
|
75
|
+
data: List[Union[SchemaData, ErrorSchemaData]]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class EarlyTermination:
|
|
80
|
+
objType = "earlyTermination"
|
|
81
|
+
|
|
82
|
+
callId: str
|
|
83
|
+
responseStatus: ResponseStatus
|
|
84
|
+
reason: str
|
|
85
|
+
extra: dict
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class ResponseLLMResult:
|
|
90
|
+
objType = "responseResult"
|
|
91
|
+
|
|
92
|
+
callId: str
|
|
93
|
+
responseStatus: ResponseStatus
|
|
94
|
+
llmResponse: Dict[str, Any]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class ResponseStatus(StrEnum):
|
|
98
|
+
SUCCESS = "SUCCESS"
|
|
99
|
+
NOT_FOUND_IN_SCHEMA = "NOT_FOUND_IN_SCHEMA"
|
|
100
|
+
UNKNOWN = "UNKNOWN"
|
|
101
|
+
INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR"
|
|
102
|
+
AUTHORIZATION_FAILED = "AUTHORIZATION_FAILED"
|
|
103
|
+
LLM_ERROR = "LLM_ERROR"
|
|
104
|
+
LLM_TOKEN_LIMIT_REACHED = "LLM_TOKEN_LIMIT_REACHED"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
_PARSE_OBJS = {
|
|
108
|
+
o.objType: o
|
|
109
|
+
for o in (
|
|
110
|
+
RetrieveResponse,
|
|
111
|
+
RetrieveResponseError,
|
|
112
|
+
SchemaData,
|
|
113
|
+
ErrorSchemaData,
|
|
114
|
+
ResponseStart,
|
|
115
|
+
ResponseData,
|
|
116
|
+
EarlyTermination,
|
|
117
|
+
ResponseLLMResult,
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
RetrieveResponseObjects = Union[RetrieveResponse, RetrieveResponseError]
|
|
122
|
+
|
|
123
|
+
ResponseDataObjects = Union[
|
|
124
|
+
ErrorSchemaData,
|
|
125
|
+
ResponseStart,
|
|
126
|
+
ResponseData,
|
|
127
|
+
EarlyTermination,
|
|
128
|
+
ResponseLLMResult,
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def parse(obj):
|
|
133
|
+
if isinstance(obj, dict):
|
|
134
|
+
kind = _PARSE_OBJS.get(obj.get("__type__"))
|
|
135
|
+
if kind:
|
|
136
|
+
keys = {f.name for f in fields(kind)}
|
|
137
|
+
kwargs = {k: parse(v) for k, v in obj.items() if k in keys}
|
|
138
|
+
return kind(**kwargs)
|
|
139
|
+
else:
|
|
140
|
+
return obj
|
|
141
|
+
elif isinstance(obj, list):
|
|
142
|
+
return [parse(v) for v in obj]
|
|
143
|
+
else:
|
|
144
|
+
return obj
|
snowleopard-0.1.0/LICENSE
DELETED
snowleopard-0.1.0/PKG-INFO
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: snowleopard
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: snowleopard.ai client library
|
|
5
|
-
License: copyright 2024 Snow Leopard, Inc
|
|
6
|
-
all rights reserved
|
|
7
|
-
|
|
8
|
-
Classifier: Development Status :: 1 - Planning
|
|
9
|
-
Classifier: Environment :: Console
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Requires-Python: >=3.8
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENSE
|
|
14
|
-
Requires-Dist: importlib_metadata>=4.6; python_version < "3.10"
|
|
15
|
-
|
|
16
|
-
# SnowLeopard-ai python client library
|
snowleopard-0.1.0/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# SnowLeopard-ai python client library
|
snowleopard-0.1.0/pyproject.toml
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
[build-system]
|
|
4
|
-
requires = ["setuptools>=61.0.0", "wheel"]
|
|
5
|
-
build-backend = "setuptools.build_meta"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
[project]
|
|
9
|
-
name = "snowleopard"
|
|
10
|
-
dynamic = ["version"]
|
|
11
|
-
|
|
12
|
-
description = "snowleopard.ai client library"
|
|
13
|
-
|
|
14
|
-
readme = "README.md"
|
|
15
|
-
license = {file = "LICENSE"}
|
|
16
|
-
|
|
17
|
-
# list of classifier strings, defined here: https://pypi.org/classifiers/
|
|
18
|
-
classifiers = [
|
|
19
|
-
"Development Status :: 1 - Planning",
|
|
20
|
-
"Environment :: Console",
|
|
21
|
-
"Intended Audience :: Developers",
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
# see https://devguide.python.org/versions/#versions for end-of-life dates
|
|
25
|
-
# next update: 3.8 EoL is 2024-10
|
|
26
|
-
requires-python = ">=3.8"
|
|
27
|
-
|
|
28
|
-
dependencies = [
|
|
29
|
-
"importlib_metadata >= 4.6 ; python_version < '3.10'"
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
[tool.setuptools]
|
|
34
|
-
|
|
35
|
-
[tool.setuptools.packages.find]
|
|
36
|
-
where = ["src"]
|
|
37
|
-
|
|
38
|
-
[tool.setuptools.dynamic]
|
|
39
|
-
version = { attr = "snowleopard.__version__" }
|
snowleopard-0.1.0/setup.cfg
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: snowleopard
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: snowleopard.ai client library
|
|
5
|
-
License: copyright 2024 Snow Leopard, Inc
|
|
6
|
-
all rights reserved
|
|
7
|
-
|
|
8
|
-
Classifier: Development Status :: 1 - Planning
|
|
9
|
-
Classifier: Environment :: Console
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Requires-Python: >=3.8
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENSE
|
|
14
|
-
Requires-Dist: importlib_metadata>=4.6; python_version < "3.10"
|
|
15
|
-
|
|
16
|
-
# SnowLeopard-ai python client library
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
LICENSE
|
|
2
|
-
README.md
|
|
3
|
-
pyproject.toml
|
|
4
|
-
src/snowleopard/__init__.py
|
|
5
|
-
src/snowleopard.egg-info/PKG-INFO
|
|
6
|
-
src/snowleopard.egg-info/SOURCES.txt
|
|
7
|
-
src/snowleopard.egg-info/dependency_links.txt
|
|
8
|
-
src/snowleopard.egg-info/requires.txt
|
|
9
|
-
src/snowleopard.egg-info/top_level.txt
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
snowleopard
|