Skip to content

Commit c85d81a

Browse files
committed
typehinting for clibuffer.py, several special .py
Add typehints for files: * mycli/clibuffer.py * mycli/packages/special/__init__.py * mycli/packages/special/dbcommands.py * mycli/packages/special/favoritequeries.py * mycli/packages/special/iocommands.py * mycli/packages/special/main.py Changes * Import "iocommands" instead of toplevel "special" * create ArgType Enum for NO_QUERY and friends * convert special-command aliases argument to a list, since it is of variable length * rewrite open_external_editor to respect two different modes for invoking click.echo(): with and without filename * recast "log" as "logger" for consistency across the project * bugfix: always test whether fetchone() returns an unpackable value * don't set FavoriteQueries.instance to None * add some explicit returns * update docstrings * convert some format strings to f-strings * convert unassigned strings not in docstring position to comments * remove a Python 2 compatibility conditional * return a tuple from parseargfile() rather than a dict * reformat some long lines * remove "type: ignore"s * precede unused variables with underscores * don't rewrite variables with different-type values * add --non-interactive to CI when installing type stubs
1 parent 80d38df commit c85d81a

File tree

10 files changed

+237
-157
lines changed

10 files changed

+237
-157
lines changed

.github/workflows/typecheck.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ jobs:
2929

3030
- name: Run mypy
3131
run: |
32-
uv run --no-sync --frozen -- python -m mypy --no-pretty --install-types .
32+
uv run --no-sync --frozen -- python -m ensurepip
33+
uv run --no-sync --frozen -- python -m mypy --no-pretty --install-types --non-interactive .

mycli/clibuffer.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
# type: ignore
1+
from typing import Callable
22

33
from prompt_toolkit.application import get_app
44
from prompt_toolkit.enums import DEFAULT_BUFFER
55
from prompt_toolkit.filters import Condition
66

7-
from mycli.packages import special
7+
from mycli.packages.special import iocommands
88

99

10-
def cli_is_multiline(mycli):
10+
def cli_is_multiline(mycli) -> Callable:
1111
@Condition
1212
def cond():
1313
doc = get_app().layout.get_buffer_by_name(DEFAULT_BUFFER).document
@@ -20,7 +20,7 @@ def cond():
2020
return cond
2121

2222

23-
def _multiline_exception(text):
23+
def _multiline_exception(text: str) -> bool:
2424
orig = text
2525
text = text.strip()
2626

@@ -39,7 +39,7 @@ def _multiline_exception(text):
3939
or
4040
# Ended with the current delimiter (usually a semi-column)
4141
text.endswith((
42-
special.get_current_delimiter(),
42+
iocommands.get_current_delimiter(),
4343
"\\g",
4444
"\\G",
4545
r"\e",

mycli/main.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
from mycli.packages.parseutils import is_destructive, is_dropping_database
5353
from mycli.packages.prompt_utils import confirm, confirm_destructive_query
5454
from mycli.packages.special.favoritequeries import FavoriteQueries
55-
from mycli.packages.special.main import NO_QUERY
55+
from mycli.packages.special.main import ArgType
5656
from mycli.packages.tabular_output import sql_format
5757
from mycli.packages.toolkit.history import FileHistoryWithTimestamp
5858
from mycli.sqlcompleter import SQLCompleter
@@ -198,37 +198,37 @@ def __init__(
198198
self.prompt_app = None
199199

200200
def register_special_commands(self):
201-
special.register_special_command(self.change_db, "use", "\\u", "Change to a new database.", aliases=("\\u",))
201+
special.register_special_command(self.change_db, "use", "\\u", "Change to a new database.", aliases=["\\u"])
202202
special.register_special_command(
203203
self.change_db,
204204
"connect",
205205
"\\r",
206206
"Reconnect to the database. Optional database argument.",
207-
aliases=("\\r",),
207+
aliases=["\\r"],
208208
case_sensitive=True,
209209
)
210210
special.register_special_command(
211-
self.refresh_completions, "rehash", "\\#", "Refresh auto-completions.", arg_type=NO_QUERY, aliases=("\\#",)
211+
self.refresh_completions, "rehash", "\\#", "Refresh auto-completions.", arg_type=ArgType.NO_QUERY, aliases=["\\#"]
212212
)
213213
special.register_special_command(
214214
self.change_table_format,
215215
"tableformat",
216216
"\\T",
217217
"Change the table format used to output results.",
218-
aliases=("\\T",),
218+
aliases=["\\T"],
219219
case_sensitive=True,
220220
)
221221
special.register_special_command(
222222
self.change_redirect_format,
223223
"redirectformat",
224224
"\\Tr",
225225
"Change the table format used to output redirected results.",
226-
aliases=("\\Tr",),
226+
aliases=["\\Tr"],
227227
case_sensitive=True,
228228
)
229-
special.register_special_command(self.execute_from_file, "source", "\\. filename", "Execute commands from file.", aliases=("\\.",))
229+
special.register_special_command(self.execute_from_file, "source", "\\. filename", "Execute commands from file.", aliases=["\\."])
230230
special.register_special_command(
231-
self.change_prompt_format, "prompt", "\\R", "Change prompt format.", aliases=("\\R",), case_sensitive=True
231+
self.change_prompt_format, "prompt", "\\R", "Change prompt format.", aliases=["\\R"], case_sensitive=True
232232
)
233233

234234
def change_table_format(self, arg, **_):
@@ -574,7 +574,7 @@ def handle_editor_command(self, text):
574574
while special.editor_command(text):
575575
filename = special.get_filename(text)
576576
query = special.get_editor_query(text) or self.get_last_query()
577-
sql, message = special.open_external_editor(filename, sql=query)
577+
sql, message = special.open_external_editor(filename=filename, sql=query)
578578
if message:
579579
# Something went wrong. Raise an exception and bail.
580580
raise RuntimeError(message)

mycli/packages/parseutils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def extract_from_part(parsed: TokenList, stop_at_punctuation: bool = True) -> Ge
123123
break
124124

125125

126-
def extract_table_identifiers(token_stream: TokenList) -> Generator[tuple[str | None, str, str]]:
126+
def extract_table_identifiers(token_stream: TokenList) -> Generator[tuple[str | None, str, str], None, None]:
127127
"""yields tuples of (schema_name, table_name, table_alias)"""
128128

129129
for item in token_stream:

mycli/packages/special/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
# type: ignore
1+
from __future__ import annotations
22

3-
__all__ = []
3+
from typing import Callable
44

5+
__all__: list[str] = []
56

6-
def export(defn):
7+
8+
def export(defn: Callable):
79
"""Decorator to explicitly mark functions that are exposed in a lib."""
810
globals()[defn.__name__] = defn
911
__all__.append(defn.__name__)

mycli/packages/special/dbcommands.py

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
1-
# type: ignore
1+
from __future__ import annotations
22

33
import logging
44
import os
55
import platform
66

77
from pymysql import ProgrammingError
8+
from pymysql.cursors import Cursor
89

910
from mycli import __version__
1011
from mycli.packages.special import iocommands
11-
from mycli.packages.special.main import PARSED_QUERY, RAW_QUERY, special_command
12+
from mycli.packages.special.main import ArgType, special_command
1213
from mycli.packages.special.utils import format_uptime
1314

14-
log = logging.getLogger(__name__)
15+
logger = logging.getLogger(__name__)
1516

1617

17-
@special_command("\\dt", "\\dt[+] [table]", "List or describe tables.", arg_type=PARSED_QUERY, case_sensitive=True)
18-
def list_tables(cur, arg=None, arg_type=PARSED_QUERY, verbose=False):
18+
@special_command("\\dt", "\\dt[+] [table]", "List or describe tables.", arg_type=ArgType.PARSED_QUERY, case_sensitive=True)
19+
def list_tables(
20+
cur: Cursor,
21+
arg: str | None = None,
22+
_arg_type: ArgType = ArgType.PARSED_QUERY,
23+
verbose: bool = False,
24+
) -> list[tuple]:
1925
if arg:
2026
query = "SHOW FIELDS FROM {0}".format(arg)
2127
else:
2228
query = "SHOW TABLES"
23-
log.debug(query)
29+
logger.debug(query)
2430
cur.execute(query)
2531
tables = cur.fetchall()
2632
status = ""
@@ -31,17 +37,18 @@ def list_tables(cur, arg=None, arg_type=PARSED_QUERY, verbose=False):
3137

3238
if verbose and arg:
3339
query = "SHOW CREATE TABLE {0}".format(arg)
34-
log.debug(query)
40+
logger.debug(query)
3541
cur.execute(query)
36-
status = cur.fetchone()[1]
42+
if one := cur.fetchone():
43+
status = one[1]
3744

3845
return [(None, tables, headers, status)]
3946

4047

41-
@special_command("\\l", "\\l", "List databases.", arg_type=RAW_QUERY, case_sensitive=True)
42-
def list_databases(cur, **_):
48+
@special_command("\\l", "\\l", "List databases.", arg_type=ArgType.RAW_QUERY, case_sensitive=True)
49+
def list_databases(cur: Cursor, **_) -> list[tuple]:
4350
query = "SHOW DATABASES"
44-
log.debug(query)
51+
logger.debug(query)
4552
cur.execute(query)
4653
if cur.description:
4754
headers = [x[0] for x in cur.description]
@@ -50,21 +57,23 @@ def list_databases(cur, **_):
5057
return [(None, None, None, "")]
5158

5259

53-
@special_command("status", "\\s", "Get status information from the server.", arg_type=RAW_QUERY, aliases=("\\s",), case_sensitive=True)
54-
def status(cur, **_):
60+
@special_command(
61+
"status", "\\s", "Get status information from the server.", arg_type=ArgType.RAW_QUERY, aliases=["\\s"], case_sensitive=True
62+
)
63+
def status(cur: Cursor, **_) -> list[tuple]:
5564
query = "SHOW GLOBAL STATUS;"
56-
log.debug(query)
65+
logger.debug(query)
5766
try:
5867
cur.execute(query)
5968
except ProgrammingError:
6069
# Fallback in case query fail, as it does with Mysql 4
6170
query = "SHOW STATUS;"
62-
log.debug(query)
71+
logger.debug(query)
6372
cur.execute(query)
6473
status = dict(cur.fetchall())
6574

6675
query = "SHOW GLOBAL VARIABLES;"
67-
log.debug(query)
76+
logger.debug(query)
6877
cur.execute(query)
6978
variables = dict(cur.fetchall())
7079

@@ -92,11 +101,13 @@ def status(cur, **_):
92101
output.append(("Connection id:", cur.connection.thread_id()))
93102

94103
query = "SELECT DATABASE(), USER();"
95-
log.debug(query)
104+
logger.debug(query)
96105
cur.execute(query)
97-
db, user = cur.fetchone()
98-
if db is None:
106+
if one := cur.fetchone():
107+
db, user = one
108+
else:
99109
db = ""
110+
user = ""
100111

101112
output.append(("Current database:", db))
102113
output.append(("Current user:", user))
@@ -121,9 +132,12 @@ def status(cur, **_):
121132
output.append(("Connection:", host_info))
122133

123134
query = "SELECT @@character_set_server, @@character_set_database, @@character_set_client, @@character_set_connection LIMIT 1;"
124-
log.debug(query)
135+
logger.debug(query)
125136
cur.execute(query)
126-
charset = cur.fetchone()
137+
if one := cur.fetchone():
138+
charset = one
139+
else:
140+
charset = ("", "", "", "")
127141
output.append(("Server characterset:", charset[0]))
128142
output.append(("Db characterset:", charset[1]))
129143
output.append(("Client characterset:", charset[2]))
@@ -151,8 +165,8 @@ def status(cur, **_):
151165
if "Queries" in status:
152166
queries_per_second = int(status["Queries"]) / int(status["Uptime"])
153167
stats.append("Queries per second avg: {:.3f}".format(queries_per_second))
154-
stats = " ".join(stats)
155-
footer.append("\n" + stats)
168+
stats_str = " ".join(stats)
169+
footer.append("\n" + stats_str)
156170

157171
footer.append("--------------")
158172
return [("\n".join(title), output, "", "\n".join(footer))]

mycli/packages/special/favoritequeries.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class FavoriteQueries:
3434
"""
3535

3636
# Class-level variable, for convenience to use as a singleton.
37-
instance = None
37+
instance: FavoriteQueries
3838

3939
def __init__(self, config) -> None:
4040
self.config = config

0 commit comments

Comments
 (0)