Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fb/DH-5058 initial commit setting up project structure. Not working e2e #1

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .gitignore
Empty file.
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,23 @@
# dataherald-python
# Dataherald Python API library

The Dataherald Python library provides convenient access to the Dataherald API from any Python application. The
library contains type definitions and easy access methods to synchronous and asynchronous methods.

## Docmentation

The API documentation can be found here.

## Installation

`pip install dataherald`

## Usage

The library needs to be configured with your accounts secret key which is available in the organization page of your
[Dataherald Dashboard](https://admin.dataherald.ai)

```
import dataherald
dataherald.api_key = "dh-...."

```
34 changes: 34 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[project]
name = "dataherald"
version = "0.0.2"
authors = [
{ name="Dataherald", email="[email protected]" },
]
description = "The official Python library for the Dataherald NL-to-SQL API"
readme = "README.md"
requires-python = ">=3.8"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]

[project.urls]
Homepage = "https://github.com/dataherald/dataherald-python"
Issues = "https://github.com/dataherald/dataherald-python/issues"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build]
include = [
"src/*"
]

[tool.hatch.build.targets.wheel]
packages = ["src/dataherald"]

[tool.black]
line-length = 120
target-version = ["py37"]
10 changes: 10 additions & 0 deletions src/dataherald/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from ._client import (
Client,
Dataherald,
AsyncDataherald,
Timeout,
)

from ._exceptions import(
DataheraldError
)
24 changes: 24 additions & 0 deletions src/dataherald/_base_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import (
Generic,
TypeVar,
Union
)

_HttpxClientT = TypeVar("_HttpxClientT", bound=Union[httpx.Client, httpx.AsyncClient])


class BaseClient(Generic[_HttpxClientT]):

class SyncAPIClient(BaseClient[httpx.Client]):
_client: httpx.Client

def __init__():
super().__init__()



class AsyncAPIClient(BaseClient[httpx.AsyncClient]):
_client: httpx.AsyncClient

def __init__():
super().__init__()
59 changes: 59 additions & 0 deletions src/dataherald/_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import httpx

import os
from typing import Union

from . import resources, _exceptions
from ._base_client import DEFAULT_MAX_RETRIES, SyncAPIClient, AsyncAPIClient
from ._types import (
Timout,
NotGiven
)

from _exceptions import DataheraldError

class Dataherald(SyncAPIClient):
sql_generations: resources.SQLGenerations
nl_generations: resources.NLGenerations
organization: str | None


def __init__(
self,
timeout: Union[float, None],
api_key: str = None,
base_url:str | httpx.URL | None = None,
organization: str | None = None,
max_retries: int = DEFAULT_MAX_RETRIES,
) -> None:
"""
Constructs a new synchronous Dataherald client instance

This will infer the following arguements from the environment variables if they are not provided
- `api_key` - `DATAHERALD_API_KEY`
- `organization` - `DATAHERALD_ORG_ID`
"""
if api_key is None:
api_key = os.environ.get("DATAHERALD_API_KEY")
if api_key is None:
raise DataheraldError(
"No API key provided. Please provide an API key or set the DATAHERALD_API_KEY environment variable."
)
self.api_key = api_key

if organization is None:
organization = os.environ.get("OPENAI_ORG_ID")
self.organization = organization

if base_url is None:
base_url = os.environ.get("OPENAI_BASE_URL")
if base_url is None:
base_url = f"https://api.dataherald.ai/api/v1"



class AsyncDataherald(AsyncAPIClient):
pass



7 changes: 7 additions & 0 deletions src/dataherald/_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import httpx


# default timeout is 10 minutes
DEFAULT_TIMEOUT = httpx.Timeout(timeout=600.0, connect=5.0)
DEFAULT_MAX_RETRIES = 2
DEFAULT_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
48 changes: 48 additions & 0 deletions src/dataherald/_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import httpx
from typing import Any, Optional, cast
from typing_extensions import TypeGuard



#To-Do move these to a utils class
def is_dict(obj: object) -> TypeGuard[dict[object, object]]:
return isinstance(obj, dict)


class DataheraldError(Exception):
pass


class APIError(DataheraldError):
message: str
request: httpx.Request

body: object | None
"""The API response body.

If the API responded with a valid JSON structure then this property will be the
decoded result.

If it isn't a valid JSON structure then this will be the raw response.

If there was no response associated with this error then it will be `None`.
"""

code: Optional[str]
param: Optional[str]
type: Optional[str]

def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None:
super().__init__(message)
self.request = request
self.message = message
self.body = body

if is_dict(body):
self.code = cast(Any, body.get("code"))
self.param = cast(Any, body.get("param"))
self.type = cast(Any, body.get("type"))
else:
self.code = None
self.param = None
self.type = None
35 changes: 35 additions & 0 deletions src/dataherald/_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import asyncio
from typing import TYPE_CHECKING
import time


class SyncAPIResource:
_client: Dataherald

def __init__(self, client: Dataherald) -> None:
self._client = client
self._get = client.get
self._post = client.post
self._patch = client.patch
self._put = client.put
self._delete = client.delete
self._get_api_list = client.get_api_list

def _sleep(self, seconds: float) -> None:
time.sleep(seconds)


class AsyncAPIResource:
_client: AsyncDataherald

def __init__(self, client: AsyncDataherald) -> None:
self._client = client
self._get = client.get
self._post = client.post
self._patch = client.patch
self._put = client.put
self._delete = client.delete
self._get_api_list = client.get_api_list

async def _sleep(self, seconds: float) -> None:
await asyncio.sleep(seconds)
9 changes: 9 additions & 0 deletions src/dataherald/resources/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .sql_generations import SQLGenerations
from .nl_generation import NLGeneration
from .heartbeat import Heartbeat

__all__ = [
"SQLGenerations",
"NLGeneration",
"Heartbeat",
]
8 changes: 8 additions & 0 deletions src/dataherald/resources/heartbeat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import httpx
from.._resource import SyncAPIResource
from .._client import Dataherald

class HeartBeat(SyncAPIResource):
def __init__(self, client: Dataherald):
super.__init__(client)
return self.response(200, "OK")
Empty file.
Empty file.
5 changes: 5 additions & 0 deletions tests/heartbeat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from ..src.dataherald._client import Dataherald, AsyncDataherald


if __name__ == "__main__":
print('yello')