-
Notifications
You must be signed in to change notification settings - Fork 90
Type checking #233
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
Type checking #233
Changes from 28 commits
a864d36
618bebf
ce4c693
bfbe8d4
d62f0f5
603f92f
ad1d5bf
80c6873
4205f70
7988113
3630c12
0c1165e
17b6e2a
a99c644
89eaba5
2337398
8a340b8
fce2300
a88aa4a
09f74d9
dbfa16b
624e6d5
1615778
90eb1d8
a5350f0
821f62b
058a84f
39660bc
c5e1a61
31dea66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| name: Typecheck | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths-ignore: | ||
| - '**/*.md' | ||
| - 'AUTHORS' | ||
|
|
||
| jobs: | ||
| typecheck: | ||
| name: Typecheck | ||
| runs-on: ubuntu-latest | ||
|
|
||
| strategy: | ||
| matrix: | ||
| python-version: ["3.13"] | ||
|
|
||
| steps: | ||
| - name: Check out Git repository | ||
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 | ||
| with: | ||
| python-version: ${{ matrix.python-version }} | ||
|
|
||
| - uses: astral-sh/setup-uv@d9e0f98d3fc6adb07d1e3d37f3043649ddad06a1 # v6.5.0 | ||
| with: | ||
| version: 'latest' | ||
|
|
||
| - name: Install dependencies | ||
| run: uv sync --all-extras | ||
|
|
||
| - name: Run mypy | ||
| run: | | ||
| cd litecli | ||
| uv run --no-sync --frozen -- python -m ensurepip | ||
| uv run --no-sync --frozen -- python -m mypy --no-pretty --install-types --non-interactive . |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,6 @@ | ||
| # type: ignore | ||
| from __future__ import annotations | ||
|
|
||
| import importlib.metadata | ||
|
|
||
| __version__ = importlib.metadata.version("litecli") | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,18 @@ | ||
| from __future__ import unicode_literals | ||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
|
|
||
| from prompt_toolkit.enums import DEFAULT_BUFFER | ||
| from prompt_toolkit.filters import Condition | ||
| from prompt_toolkit.filters import Condition, Filter | ||
| from prompt_toolkit.application import get_app | ||
|
|
||
|
|
||
| def cli_is_multiline(cli): | ||
| def cli_is_multiline(cli: Any) -> Filter: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand sometimes types can be complicated to express so
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll have to come back to the use of Any and fix them up. |
||
| @Condition | ||
| def cond(): | ||
| doc = get_app().layout.get_buffer_by_name(DEFAULT_BUFFER).document | ||
| def cond() -> bool: | ||
| buf = get_app().layout.get_buffer_by_name(DEFAULT_BUFFER) | ||
| assert buf is not None | ||
| doc = buf.document | ||
|
|
||
| if not cli.multi_line: | ||
| return False | ||
|
|
@@ -18,7 +22,7 @@ def cond(): | |
| return cond | ||
|
|
||
|
|
||
| def _multiline_exception(text): | ||
| def _multiline_exception(text: str) -> bool: | ||
| orig = text | ||
| text = text.strip() | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,8 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import threading | ||
| from typing import Callable | ||
|
|
||
| from .packages.special.main import COMMANDS | ||
| from collections import OrderedDict | ||
|
|
||
|
|
@@ -7,13 +11,18 @@ | |
|
|
||
|
|
||
| class CompletionRefresher(object): | ||
| refreshers = OrderedDict() | ||
| refreshers: dict[str, Callable] = OrderedDict() | ||
|
|
||
| def __init__(self): | ||
| self._completer_thread = None | ||
| def __init__(self) -> None: | ||
| self._completer_thread: threading.Thread | None = None | ||
| self._restart_refresh = threading.Event() | ||
|
|
||
| def refresh(self, executor, callbacks, completer_options=None): | ||
| def refresh( | ||
| self, | ||
| executor: SQLExecute, | ||
| callbacks: Callable | list[Callable], | ||
| completer_options: dict | None = None, | ||
| ) -> list[tuple]: | ||
| """Creates a SQLCompleter object and populates it with the relevant | ||
| completion suggestions in a background thread. | ||
|
|
||
|
|
@@ -32,10 +41,11 @@ def refresh(self, executor, callbacks, completer_options=None): | |
| self._restart_refresh.set() | ||
| return [(None, None, None, "Auto-completion refresh restarted.")] | ||
| else: | ||
| if executor.dbname == ":memory:": | ||
| if executor.dbname == ":memory": | ||
|
||
| # if DB is memory, needed to use same connection | ||
| # So can't use same connection with different thread | ||
| self._bg_refresh(executor, callbacks, completer_options) | ||
| return [(None, None, None, "Auto-completion refresh started in the background.")] | ||
| else: | ||
| self._completer_thread = threading.Thread( | ||
| target=self._bg_refresh, | ||
|
|
@@ -44,19 +54,17 @@ def refresh(self, executor, callbacks, completer_options=None): | |
| ) | ||
| self._completer_thread.daemon = True | ||
| self._completer_thread.start() | ||
| return [ | ||
| ( | ||
| None, | ||
| None, | ||
| None, | ||
| "Auto-completion refresh started in the background.", | ||
| ) | ||
| ] | ||
|
|
||
| def is_refreshing(self): | ||
| return self._completer_thread and self._completer_thread.is_alive() | ||
|
|
||
| def _bg_refresh(self, sqlexecute, callbacks, completer_options): | ||
| return [(None, None, None, "Auto-completion refresh started in the background.")] | ||
|
|
||
| def is_refreshing(self) -> bool: | ||
| return bool(self._completer_thread and self._completer_thread.is_alive()) | ||
|
|
||
| def _bg_refresh( | ||
| self, | ||
| sqlexecute: SQLExecute, | ||
| callbacks: Callable | list[Callable], | ||
| completer_options: dict, | ||
| ) -> None: | ||
| completer = SQLCompleter(**completer_options) | ||
|
|
||
| e = sqlexecute | ||
|
|
@@ -90,41 +98,42 @@ def _bg_refresh(self, sqlexecute, callbacks, completer_options): | |
| callback(completer) | ||
|
|
||
|
|
||
| def refresher(name, refreshers=CompletionRefresher.refreshers): | ||
| def refresher(name: str, refreshers: dict[str, Callable] = CompletionRefresher.refreshers) -> Callable: | ||
| """Decorator to add the decorated function to the dictionary of | ||
| refreshers. Any function decorated with a @refresher will be executed as | ||
| part of the completion refresh routine.""" | ||
|
|
||
| def wrapper(wrapped): | ||
| def wrapper(wrapped: Callable) -> Callable: | ||
| refreshers[name] = wrapped | ||
| return wrapped | ||
|
|
||
| return wrapper | ||
|
|
||
|
|
||
| @refresher("databases") | ||
| def refresh_databases(completer, executor): | ||
| def refresh_databases(completer: SQLCompleter, executor: SQLExecute) -> None: | ||
| completer.extend_database_names(executor.databases()) | ||
|
|
||
|
|
||
| @refresher("schemata") | ||
| def refresh_schemata(completer, executor): | ||
| def refresh_schemata(completer: SQLCompleter, executor: SQLExecute) -> None: | ||
| # name of the current database. | ||
| completer.extend_schemata(executor.dbname) | ||
| completer.set_dbname(executor.dbname) | ||
|
|
||
|
|
||
| @refresher("tables") | ||
| def refresh_tables(completer, executor): | ||
| completer.extend_relations(executor.tables(), kind="tables") | ||
| completer.extend_columns(executor.table_columns(), kind="tables") | ||
| def refresh_tables(completer: SQLCompleter, executor: SQLExecute) -> None: | ||
| table_cols = list(executor.table_columns()) | ||
| completer.extend_relations(table_cols, kind="tables") | ||
| completer.extend_columns(table_cols, kind="tables") | ||
|
|
||
|
|
||
| @refresher("functions") | ||
| def refresh_functions(completer, executor): | ||
| def refresh_functions(completer: SQLCompleter, executor: SQLExecute) -> None: | ||
| completer.extend_functions(executor.functions()) | ||
|
|
||
|
|
||
| @refresher("special_commands") | ||
| def refresh_special(completer, executor): | ||
| completer.extend_special_commands(COMMANDS.keys()) | ||
| def refresh_special(completer: SQLCompleter, executor: SQLExecute) -> None: | ||
| completer.extend_special_commands(list(COMMANDS.keys())) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this
#type: ignoreneeded?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had added mypy: ignore-errors to all the files before I started. Looks like I left them in by mistake.