Skip to content

Commit 90a1b71

Browse files
committed
Improve code organization: docstrings, type hints, and imports
- Add module docstrings to cli.py, admin.py, logging.py, and hash.py - Add `from __future__ import annotations` for PEP 563 deferred evaluation - Modernize type hints using Python 3.10+ syntax (X | None instead of Optional[X]) - Use TYPE_CHECKING imports to avoid circular dependencies - Improve function docstrings to Google style with Args/Returns sections - Add type annotations to: - admin.py: set_password(), kill(), and kill_quick() functions - cli.py: cli() function - hash.py: all hashing functions - logging.py: excepthook() function - utils.py: all utility functions and ClassProperty
1 parent 63ebc38 commit 90a1b71

File tree

5 files changed

+277
-69
lines changed

5 files changed

+277
-69
lines changed

datajoint/admin.py

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,39 @@
1+
"""
2+
Administrative utilities for managing database connections and passwords.
3+
4+
This module provides functions for viewing and terminating database connections
5+
through the MySQL processlist interface, as well as password management.
6+
"""
7+
8+
from __future__ import annotations
9+
110
import logging
211
from getpass import getpass
312

413
import pymysql
514
from packaging import version
615

7-
from .connection import conn
16+
from .connection import Connection, conn
817
from .settings import config
918
from .utils import user_choice
1019

1120
logger = logging.getLogger(__name__.split(".")[0])
1221

1322

14-
def set_password(new_password=None, connection=None, update_config=None):
23+
def set_password(
24+
new_password: str | None = None,
25+
connection: Connection | None = None,
26+
update_config: bool | None = None,
27+
) -> None:
28+
"""
29+
Change the database password for the current user.
30+
31+
Args:
32+
new_password: The new password. If None, prompts for input.
33+
connection: A datajoint.Connection object. If None, uses datajoint.conn().
34+
update_config: If True, save the new password to local config.
35+
If None, prompts the user.
36+
"""
1537
connection = conn() if connection is None else connection
1638
if new_password is None:
1739
new_password = getpass("New password: ")
@@ -36,20 +58,27 @@ def set_password(new_password=None, connection=None, update_config=None):
3658
config.save_local(verbose=True)
3759

3860

39-
def kill(restriction=None, connection=None, order_by=None):
61+
def kill(
62+
restriction: str | None = None,
63+
connection: Connection | None = None,
64+
order_by: str | list[str] | None = None,
65+
) -> None:
4066
"""
41-
view and kill database connections.
67+
View and interactively kill database connections.
4268
43-
:param restriction: restriction to be applied to processlist
44-
:param connection: a datajoint.Connection object. Default calls datajoint.conn()
45-
:param order_by: order by a single attribute or the list of attributes. defaults to 'id'.
69+
Displays active database connections matching the optional restriction and
70+
prompts the user to select connections to terminate.
4671
47-
Restrictions are specified as strings and can involve any of the attributes of
48-
information_schema.processlist: ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO.
72+
Args:
73+
restriction: SQL WHERE clause condition to filter the processlist.
74+
Can reference any column from information_schema.processlist:
75+
ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO.
76+
connection: A datajoint.Connection object. If None, uses datajoint.conn().
77+
order_by: Column name(s) to sort results by. Defaults to 'id'.
4978
5079
Examples:
51-
dj.kill('HOST LIKE "%compute%"') lists only connections from hosts containing "compute".
52-
dj.kill('TIME > 600') lists only connections in their current state for more than 10 minutes
80+
>>> dj.kill('HOST LIKE "%compute%"') # connections from hosts containing "compute"
81+
>>> dj.kill('TIME > 600') # connections idle for more than 10 minutes
5382
"""
5483

5584
if connection is None:
@@ -95,18 +124,28 @@ def kill(restriction=None, connection=None, order_by=None):
95124
logger.warn("Process not found")
96125

97126

98-
def kill_quick(restriction=None, connection=None):
127+
def kill_quick(
128+
restriction: str | None = None,
129+
connection: Connection | None = None,
130+
) -> int:
99131
"""
100-
Kill database connections without prompting. Returns number of terminated connections.
132+
Kill database connections without prompting.
133+
134+
Terminates all database connections matching the optional restriction
135+
without user confirmation.
101136
102-
:param restriction: restriction to be applied to processlist
103-
:param connection: a datajoint.Connection object. Default calls datajoint.conn()
137+
Args:
138+
restriction: SQL WHERE clause condition to filter the processlist.
139+
Can reference any column from information_schema.processlist:
140+
ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO.
141+
connection: A datajoint.Connection object. If None, uses datajoint.conn().
104142
105-
Restrictions are specified as strings and can involve any of the attributes of
106-
information_schema.processlist: ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO.
143+
Returns:
144+
Number of connections terminated.
107145
108146
Examples:
109-
dj.kill('HOST LIKE "%compute%"') terminates connections from hosts containing "compute".
147+
>>> dj.kill_quick('HOST LIKE "%compute%"') # kill connections from "compute" hosts
148+
>>> dj.kill_quick('TIME > 600') # kill connections idle for more than 10 minutes
110149
"""
111150
if connection is None:
112151
connection = conn()

datajoint/cli.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,41 @@
1+
"""
2+
Command-line interface for DataJoint Python.
3+
4+
This module provides a console interface for interacting with DataJoint databases,
5+
allowing users to connect to servers and work with virtual modules from the command line.
6+
7+
Usage:
8+
datajoint [-u USER] [-p PASSWORD] [-h HOST] [-s SCHEMA:MODULE ...]
9+
10+
Example:
11+
datajoint -u root -h localhost -s mydb:experiment mydb:subject
12+
"""
13+
14+
from __future__ import annotations
15+
116
import argparse
217
from code import interact
318
from collections import ChainMap
19+
from typing import TYPE_CHECKING
20+
21+
if TYPE_CHECKING:
22+
from collections.abc import Sequence
423

524
import datajoint as dj
625

726

8-
def cli(args: list = None):
27+
def cli(args: Sequence[str] | None = None) -> None:
928
"""
10-
Console interface for DataJoint Python
29+
Console interface for DataJoint Python.
30+
31+
Launches an interactive Python shell with DataJoint configured and optional
32+
virtual modules loaded for database schemas.
33+
34+
Args:
35+
args: List of command-line arguments. If None, reads from sys.argv.
1136
12-
:param args: List of arguments to be passed in, defaults to reading stdin
13-
:type args: list, optional
37+
Raises:
38+
SystemExit: Always raised when the interactive session ends.
1439
"""
1540
parser = argparse.ArgumentParser(
1641
prog="datajoint",

datajoint/hash.py

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,56 @@
1+
"""
2+
Hashing utilities for DataJoint.
3+
4+
This module provides functions for computing hashes of data, streams, and files.
5+
These are used for checksums, content-addressable storage, and primary key hashing.
6+
"""
7+
8+
from __future__ import annotations
9+
110
import hashlib
211
import io
312
import uuid
13+
from collections.abc import Mapping
414
from pathlib import Path
15+
from typing import IO
516

617

7-
def key_hash(mapping):
18+
def key_hash(mapping: Mapping) -> str:
819
"""
9-
32-byte hash of the mapping's key values sorted by the key name.
10-
This is often used to convert a long primary key value into a shorter hash.
11-
For example, the JobTable in datajoint.jobs uses this function to hash the primary key of autopopulated tables.
20+
Compute a 32-character hex hash of a mapping's values, sorted by key name.
21+
22+
This is commonly used to convert long primary key values into shorter hashes.
23+
For example, the JobTable in datajoint.jobs uses this function to hash the
24+
primary keys of autopopulated tables.
25+
26+
Args:
27+
mapping: A dict-like object whose values will be hashed.
28+
29+
Returns:
30+
A 32-character hexadecimal MD5 hash string.
31+
32+
Example:
33+
>>> key_hash({'subject_id': 1, 'session': 5})
34+
'a1b2c3d4e5f6...'
1235
"""
1336
hashed = hashlib.md5()
1437
for k, v in sorted(mapping.items()):
1538
hashed.update(str(v).encode())
1639
return hashed.hexdigest()
1740

1841

19-
def uuid_from_stream(stream, *, init_string=""):
42+
def uuid_from_stream(stream: IO[bytes], *, init_string: str = "") -> uuid.UUID:
2043
"""
21-
:return: 16-byte digest of stream data
22-
:stream: stream object or open file handle
23-
:init_string: string to initialize the checksum
44+
Compute a UUID from the contents of a binary stream.
45+
46+
Reads the stream in chunks and computes an MD5 hash, returning it as a UUID.
47+
48+
Args:
49+
stream: A binary stream object (file handle opened in 'rb' mode or BytesIO).
50+
init_string: Optional string to initialize the hash (acts as a salt).
51+
52+
Returns:
53+
A UUID object derived from the MD5 hash of the stream contents.
2454
"""
2555
hashed = hashlib.md5(init_string.encode())
2656
chunk = True
@@ -31,9 +61,29 @@ def uuid_from_stream(stream, *, init_string=""):
3161
return uuid.UUID(bytes=hashed.digest())
3262

3363

34-
def uuid_from_buffer(buffer=b"", *, init_string=""):
64+
def uuid_from_buffer(buffer: bytes = b"", *, init_string: str = "") -> uuid.UUID:
65+
"""
66+
Compute a UUID from a bytes buffer.
67+
68+
Args:
69+
buffer: The binary data to hash.
70+
init_string: Optional string to initialize the hash (acts as a salt).
71+
72+
Returns:
73+
A UUID object derived from the MD5 hash of the buffer.
74+
"""
3575
return uuid_from_stream(io.BytesIO(buffer), init_string=init_string)
3676

3777

38-
def uuid_from_file(filepath, *, init_string=""):
78+
def uuid_from_file(filepath: str | Path, *, init_string: str = "") -> uuid.UUID:
79+
"""
80+
Compute a UUID from the contents of a file.
81+
82+
Args:
83+
filepath: Path to the file to hash.
84+
init_string: Optional string to initialize the hash (acts as a salt).
85+
86+
Returns:
87+
A UUID object derived from the MD5 hash of the file contents.
88+
"""
3989
return uuid_from_stream(Path(filepath).open("rb"), init_string=init_string)

datajoint/logging.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
1+
"""
2+
Logging configuration for the DataJoint package.
3+
4+
This module sets up the default logging handler and format for DataJoint,
5+
and provides a custom exception hook to log uncaught exceptions.
6+
7+
The log level can be configured via the DJ_LOG_LEVEL environment variable.
8+
"""
9+
10+
from __future__ import annotations
11+
112
import logging
213
import os
314
import sys
15+
from types import TracebackType
416

517
logger = logging.getLogger(__name__.split(".")[0])
618

@@ -15,7 +27,22 @@
1527
logger.handlers = [stream_handler]
1628

1729

18-
def excepthook(exc_type, exc_value, exc_traceback):
30+
def excepthook(
31+
exc_type: type[BaseException],
32+
exc_value: BaseException,
33+
exc_traceback: TracebackType | None,
34+
) -> None:
35+
"""
36+
Custom exception hook that logs uncaught exceptions.
37+
38+
Keyboard interrupts are passed to the default handler; all other exceptions
39+
are logged as errors with full traceback information.
40+
41+
Args:
42+
exc_type: The exception class.
43+
exc_value: The exception instance.
44+
exc_traceback: The traceback object.
45+
"""
1946
if issubclass(exc_type, KeyboardInterrupt):
2047
sys.__excepthook__(exc_type, exc_value, exc_traceback)
2148
return

0 commit comments

Comments
 (0)