|
| 1 | +# Contributing |
| 2 | + |
| 3 | +tabletalk is a Python project managed with [uv](https://github.com/astral-sh/uv). Contributions are welcome — this guide covers environment setup, adding new providers, and the pull request process. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Development setup |
| 8 | + |
| 9 | +**Prerequisites:** Python 3.10+, [uv](https://github.com/astral-sh/uv) |
| 10 | + |
| 11 | +```bash |
| 12 | +git clone https://github.com/wtbates99/tabletalk.git |
| 13 | +cd tabletalk |
| 14 | + |
| 15 | +# Install the package and all dev dependencies in one step |
| 16 | +uv sync --extra all |
| 17 | +``` |
| 18 | + |
| 19 | +This installs tabletalk in editable mode along with all optional database drivers plus the dev tools (pytest, ruff, mypy, duckdb). |
| 20 | + |
| 21 | +--- |
| 22 | + |
| 23 | +## Running tests |
| 24 | + |
| 25 | +```bash |
| 26 | +# All tests (DuckDB and SQLite run without extra setup) |
| 27 | +uv run pytest |
| 28 | + |
| 29 | +# Specific test file |
| 30 | +uv run pytest tabletalk/tests/test_providers.py -v |
| 31 | +``` |
| 32 | + |
| 33 | +Tests live in `tabletalk/tests/`. PostgreSQL and MySQL tests require a running local instance — configure credentials in the test config if you need them. |
| 34 | + |
| 35 | +--- |
| 36 | + |
| 37 | +## Code style |
| 38 | + |
| 39 | +```bash |
| 40 | +# Lint and auto-fix |
| 41 | +uv run ruff check --fix tabletalk/ |
| 42 | + |
| 43 | +# Type checking |
| 44 | +uv run mypy tabletalk/ |
| 45 | +``` |
| 46 | + |
| 47 | +Line length is 100. The linter enforces `pyflakes`, `pycodestyle`, `isort`, and `pyupgrade` rules. Run both before pushing. |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +## Adding a database provider |
| 52 | + |
| 53 | +All database providers implement the `DatabaseProvider` ABC in `tabletalk/interfaces.py`. |
| 54 | + |
| 55 | +**1. Create the provider file** at `tabletalk/providers/mydb_provider.py`: |
| 56 | + |
| 57 | +```python |
| 58 | +from typing import Any, Dict, List, Optional |
| 59 | +from tabletalk.interfaces import DatabaseProvider |
| 60 | + |
| 61 | + |
| 62 | +class MyDBProvider(DatabaseProvider): |
| 63 | + def __init__(self, config: Dict[str, Any]): |
| 64 | + # initialize connection from config dict |
| 65 | + ... |
| 66 | + |
| 67 | + def execute_query(self, sql_query: str) -> List[Dict[str, Any]]: |
| 68 | + """Execute SQL and return a list of row dicts.""" |
| 69 | + ... |
| 70 | + |
| 71 | + def get_client(self) -> Any: |
| 72 | + """Return the raw connection/client object.""" |
| 73 | + ... |
| 74 | + |
| 75 | + def get_database_type_map(self) -> Dict[str, str]: |
| 76 | + """Map native type names to compact codes used in manifests. |
| 77 | +
|
| 78 | + Standard codes: I=integer, S=string/text, N=numeric/float, |
| 79 | + B=boolean, TS=timestamp, D=date, J=json, X=other |
| 80 | + """ |
| 81 | + return { |
| 82 | + "integer": "I", |
| 83 | + "text": "S", |
| 84 | + # ... |
| 85 | + } |
| 86 | + |
| 87 | + def get_compact_tables( |
| 88 | + self, schema_name: str, table_names: Optional[List[str]] = None |
| 89 | + ) -> List[Dict[str, Any]]: |
| 90 | + """Fetch table schemas in compact format for manifest generation. |
| 91 | +
|
| 92 | + Returns a list of dicts: |
| 93 | + { |
| 94 | + 't': 'schema.table_name', |
| 95 | + 'd': 'table description or empty string', |
| 96 | + 'f': [ |
| 97 | + {'n': 'col_name', 't': 'I', 'pk': True}, |
| 98 | + {'n': 'other_col', 't': 'S', 'fk': 'other_table.id'}, |
| 99 | + ] |
| 100 | + } |
| 101 | + """ |
| 102 | + ... |
| 103 | +``` |
| 104 | + |
| 105 | +**2. Register the provider** in `tabletalk/factories.py` — add a branch to `get_db_provider()` that maps your type string to your class. |
| 106 | + |
| 107 | +**3. Add the optional dependency** in `pyproject.toml` under `[project.optional-dependencies]`: |
| 108 | + |
| 109 | +```toml |
| 110 | +mydb = ["mydb-driver>=1.0"] |
| 111 | +``` |
| 112 | + |
| 113 | +And add it to the `all` extras list. |
| 114 | + |
| 115 | +**4. Add a test** in `tabletalk/tests/` following the pattern of the existing provider tests. |
| 116 | + |
| 117 | +--- |
| 118 | + |
| 119 | +## Adding an LLM provider |
| 120 | + |
| 121 | +LLM providers implement the `LLMProvider` ABC in `tabletalk/interfaces.py`. |
| 122 | + |
| 123 | +**1. Create the provider file** at `tabletalk/providers/myllm_provider.py`: |
| 124 | + |
| 125 | +```python |
| 126 | +from typing import Dict, Generator, List |
| 127 | +from tabletalk.interfaces import LLMProvider |
| 128 | + |
| 129 | + |
| 130 | +class MyLLMProvider(LLMProvider): |
| 131 | + def __init__(self, config: Dict): |
| 132 | + # initialize client from config dict |
| 133 | + ... |
| 134 | + |
| 135 | + def generate_response(self, prompt: str) -> str: |
| 136 | + """Single-turn: return the full response as a string.""" |
| 137 | + ... |
| 138 | + |
| 139 | + def generate_response_stream(self, prompt: str) -> Generator[str, None, None]: |
| 140 | + """Single-turn: yield response tokens as they arrive.""" |
| 141 | + ... |
| 142 | + |
| 143 | + def generate_chat_stream( |
| 144 | + self, messages: List[Dict[str, str]] |
| 145 | + ) -> Generator[str, None, None]: |
| 146 | + """Multi-turn: yield tokens given a full messages list (OpenAI format). |
| 147 | +
|
| 148 | + messages is a list of {'role': 'system'|'user'|'assistant', 'content': '...'}. |
| 149 | + """ |
| 150 | + ... |
| 151 | +``` |
| 152 | + |
| 153 | +**2. Register the provider** in `tabletalk/factories.py` in `get_llm_provider()`. |
| 154 | + |
| 155 | +--- |
| 156 | + |
| 157 | +## Project structure |
| 158 | + |
| 159 | +``` |
| 160 | +tabletalk/ |
| 161 | +├── cli.py — Click CLI (all user-facing commands) |
| 162 | +├── app.py — Flask web server + SSE streaming endpoints |
| 163 | +├── interfaces.py — DatabaseProvider, LLMProvider, QuerySession, Parser ABCs |
| 164 | +├── factories.py — Provider instantiation from config dicts |
| 165 | +├── profiles.py — ~/.tabletalk/profiles.yml management + dbt import |
| 166 | +├── utils.py — Project scaffolding helpers |
| 167 | +├── providers/ — One file per database or LLM backend |
| 168 | +└── tests/ — pytest test suite |
| 169 | +``` |
| 170 | + |
| 171 | +The `QuerySession` class in `interfaces.py` is the core orchestrator — it owns SQL generation, execution, history, favorites, and explain/suggest logic. `Parser` handles `tabletalk apply` (schema introspection → manifest files). |
| 172 | + |
| 173 | +--- |
| 174 | + |
| 175 | +## Pull requests |
| 176 | + |
| 177 | +- Open an issue first for non-trivial changes |
| 178 | +- Keep PRs focused — one feature or fix per PR |
| 179 | +- Ensure `ruff` and `mypy` pass with no new errors |
| 180 | +- Add or update tests for changed behavior |
| 181 | +- Update the relevant docs page if your change affects user-visible behavior |
0 commit comments