codegraph-py 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codegraph/__init__.py +12 -0
- codegraph/__main__.py +6 -0
- codegraph/cli.py +911 -0
- codegraph/codegraph.py +618 -0
- codegraph/context/__init__.py +216 -0
- codegraph/db/__init__.py +11 -0
- codegraph/db/connection.py +163 -0
- codegraph/db/queries.py +752 -0
- codegraph/db/schema.sql +151 -0
- codegraph/directory.py +160 -0
- codegraph/errors.py +101 -0
- codegraph/extraction/__init__.py +956 -0
- codegraph/extraction/languages/__init__.py +64 -0
- codegraph/extraction/languages/base.py +132 -0
- codegraph/extraction/languages/c_cfg.py +53 -0
- codegraph/extraction/languages/cpp_cfg.py +60 -0
- codegraph/extraction/languages/dart_cfg.py +53 -0
- codegraph/extraction/languages/go_cfg.py +70 -0
- codegraph/extraction/languages/java_cfg.py +62 -0
- codegraph/extraction/languages/javascript_cfg.py +84 -0
- codegraph/extraction/languages/kotlin_cfg.py +52 -0
- codegraph/extraction/languages/lua_cfg.py +65 -0
- codegraph/extraction/languages/python_cfg.py +75 -0
- codegraph/extraction/languages/ruby_cfg.py +48 -0
- codegraph/extraction/languages/rust_cfg.py +68 -0
- codegraph/extraction/languages/scala_cfg.py +59 -0
- codegraph/extraction/languages/swift_cfg.py +64 -0
- codegraph/extraction/languages/typescript_cfg.py +89 -0
- codegraph/extraction/tree_sitter_extractor.py +689 -0
- codegraph/graph/__init__.py +685 -0
- codegraph/installer/__init__.py +6 -0
- codegraph/mcp/__init__.py +668 -0
- codegraph/project_config.py +191 -0
- codegraph/resolution/__init__.py +337 -0
- codegraph/search/__init__.py +653 -0
- codegraph/sync/__init__.py +204 -0
- codegraph/types.py +334 -0
- codegraph/ui/__init__.py +11 -0
- codegraph/utils.py +218 -0
- codegraph_py-1.0.0.dist-info/METADATA +238 -0
- codegraph_py-1.0.0.dist-info/RECORD +44 -0
- codegraph_py-1.0.0.dist-info/WHEEL +5 -0
- codegraph_py-1.0.0.dist-info/entry_points.txt +2 -0
- codegraph_py-1.0.0.dist-info/top_level.txt +1 -0
codegraph/db/schema.sql
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
-- CodeGraph SQLite Schema
|
|
2
|
+
-- Version 1
|
|
3
|
+
|
|
4
|
+
-- Schema version tracking
|
|
5
|
+
CREATE TABLE IF NOT EXISTS schema_versions (
|
|
6
|
+
version INTEGER PRIMARY KEY,
|
|
7
|
+
applied_at INTEGER NOT NULL,
|
|
8
|
+
description TEXT
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
-- Core Tables
|
|
12
|
+
|
|
13
|
+
-- Nodes: Code symbols (functions, classes, variables, etc.)
|
|
14
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
15
|
+
id TEXT PRIMARY KEY,
|
|
16
|
+
kind TEXT NOT NULL,
|
|
17
|
+
name TEXT NOT NULL,
|
|
18
|
+
qualified_name TEXT NOT NULL,
|
|
19
|
+
file_path TEXT NOT NULL,
|
|
20
|
+
language TEXT NOT NULL,
|
|
21
|
+
start_line INTEGER NOT NULL,
|
|
22
|
+
end_line INTEGER NOT NULL,
|
|
23
|
+
start_column INTEGER NOT NULL,
|
|
24
|
+
end_column INTEGER NOT NULL,
|
|
25
|
+
docstring TEXT,
|
|
26
|
+
signature TEXT,
|
|
27
|
+
visibility TEXT,
|
|
28
|
+
is_exported INTEGER DEFAULT 0,
|
|
29
|
+
is_async INTEGER DEFAULT 0,
|
|
30
|
+
is_static INTEGER DEFAULT 0,
|
|
31
|
+
is_abstract INTEGER DEFAULT 0,
|
|
32
|
+
decorators TEXT,
|
|
33
|
+
type_parameters TEXT,
|
|
34
|
+
return_type TEXT,
|
|
35
|
+
updated_at INTEGER NOT NULL
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
-- Edges: Relationships between nodes
|
|
39
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
40
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
41
|
+
source TEXT NOT NULL,
|
|
42
|
+
target TEXT NOT NULL,
|
|
43
|
+
kind TEXT NOT NULL,
|
|
44
|
+
metadata TEXT,
|
|
45
|
+
line INTEGER,
|
|
46
|
+
col INTEGER,
|
|
47
|
+
provenance TEXT DEFAULT NULL,
|
|
48
|
+
FOREIGN KEY (source) REFERENCES nodes(id) ON DELETE CASCADE,
|
|
49
|
+
FOREIGN KEY (target) REFERENCES nodes(id) ON DELETE CASCADE
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
-- Files: Tracked source files
|
|
53
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
54
|
+
path TEXT PRIMARY KEY,
|
|
55
|
+
content_hash TEXT NOT NULL,
|
|
56
|
+
language TEXT NOT NULL,
|
|
57
|
+
size INTEGER NOT NULL,
|
|
58
|
+
modified_at INTEGER NOT NULL,
|
|
59
|
+
indexed_at INTEGER NOT NULL,
|
|
60
|
+
node_count INTEGER DEFAULT 0,
|
|
61
|
+
errors TEXT
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
-- Unresolved References: References that need resolution after full indexing
|
|
65
|
+
CREATE TABLE IF NOT EXISTS unresolved_refs (
|
|
66
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
67
|
+
from_node_id TEXT NOT NULL,
|
|
68
|
+
reference_name TEXT NOT NULL,
|
|
69
|
+
reference_kind TEXT NOT NULL,
|
|
70
|
+
line INTEGER NOT NULL,
|
|
71
|
+
col INTEGER NOT NULL,
|
|
72
|
+
candidates TEXT,
|
|
73
|
+
file_path TEXT NOT NULL DEFAULT '',
|
|
74
|
+
language TEXT NOT NULL DEFAULT 'unknown',
|
|
75
|
+
FOREIGN KEY (from_node_id) REFERENCES nodes(id) ON DELETE CASCADE
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
-- =============================================================================
|
|
79
|
+
-- Indexes for Query Performance
|
|
80
|
+
-- =============================================================================
|
|
81
|
+
|
|
82
|
+
-- Node indexes
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_qualified_name ON nodes(qualified_name);
|
|
86
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_file_path ON nodes(file_path);
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_language ON nodes(language);
|
|
88
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_file_line ON nodes(file_path, start_line);
|
|
89
|
+
|
|
90
|
+
-- Full-text search index on node names, docstrings, and signatures
|
|
91
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
|
|
92
|
+
id UNINDEXED,
|
|
93
|
+
name,
|
|
94
|
+
qualified_name,
|
|
95
|
+
docstring,
|
|
96
|
+
signature,
|
|
97
|
+
content='nodes',
|
|
98
|
+
content_rowid='rowid'
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
-- Triggers to keep FTS index in sync
|
|
102
|
+
CREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN
|
|
103
|
+
INSERT INTO nodes_fts(rowid, id, name, qualified_name, docstring, signature)
|
|
104
|
+
VALUES (new.rowid, new.id, new.name, new.qualified_name, new.docstring, new.signature);
|
|
105
|
+
END;
|
|
106
|
+
|
|
107
|
+
CREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN
|
|
108
|
+
INSERT INTO nodes_fts(nodes_fts, rowid, id, name, qualified_name, docstring, signature)
|
|
109
|
+
VALUES ('delete', old.rowid, old.id, old.name, old.qualified_name, old.docstring, old.signature);
|
|
110
|
+
END;
|
|
111
|
+
|
|
112
|
+
CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN
|
|
113
|
+
INSERT INTO nodes_fts(nodes_fts, rowid, id, name, qualified_name, docstring, signature)
|
|
114
|
+
VALUES ('delete', old.rowid, old.id, old.name, old.qualified_name, old.docstring, old.signature);
|
|
115
|
+
INSERT INTO nodes_fts(rowid, id, name, qualified_name, docstring, signature)
|
|
116
|
+
VALUES (new.rowid, new.id, new.name, new.qualified_name, new.docstring, new.signature);
|
|
117
|
+
END;
|
|
118
|
+
|
|
119
|
+
-- Name segment vocabulary for natural language matching
|
|
120
|
+
CREATE TABLE IF NOT EXISTS name_segment_vocab (
|
|
121
|
+
segment TEXT NOT NULL,
|
|
122
|
+
name TEXT NOT NULL,
|
|
123
|
+
PRIMARY KEY (segment, name)
|
|
124
|
+
) WITHOUT ROWID;
|
|
125
|
+
|
|
126
|
+
-- Edge indexes
|
|
127
|
+
CREATE INDEX IF NOT EXISTS idx_edges_kind ON edges(kind);
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source_kind ON edges(source, kind);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target_kind ON edges(target, kind);
|
|
130
|
+
|
|
131
|
+
-- Edge identity uniqueness
|
|
132
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_edges_identity
|
|
133
|
+
ON edges(source, target, kind, IFNULL(line, -1), IFNULL(col, -1));
|
|
134
|
+
|
|
135
|
+
-- File indexes
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_files_language ON files(language);
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_files_modified_at ON files(modified_at);
|
|
138
|
+
|
|
139
|
+
-- Unresolved refs indexes
|
|
140
|
+
CREATE INDEX IF NOT EXISTS idx_unresolved_from_node ON unresolved_refs(from_node_id);
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_unresolved_name ON unresolved_refs(reference_name);
|
|
142
|
+
CREATE INDEX IF NOT EXISTS idx_unresolved_file_path ON unresolved_refs(file_path);
|
|
143
|
+
CREATE INDEX IF NOT EXISTS idx_unresolved_from_name ON unresolved_refs(from_node_id, reference_name);
|
|
144
|
+
CREATE INDEX IF NOT EXISTS idx_edges_provenance ON edges(provenance);
|
|
145
|
+
|
|
146
|
+
-- Project metadata for version/provenance tracking
|
|
147
|
+
CREATE TABLE IF NOT EXISTS project_metadata (
|
|
148
|
+
key TEXT PRIMARY KEY,
|
|
149
|
+
value TEXT NOT NULL,
|
|
150
|
+
updated_at INTEGER NOT NULL
|
|
151
|
+
);
|
codegraph/directory.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CodeGraph Directory Management
|
|
3
|
+
|
|
4
|
+
Manages the .codegraph/ directory structure.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
import shutil
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, List, Tuple
|
|
14
|
+
|
|
15
|
+
import codegraph.errors as err
|
|
16
|
+
|
|
17
|
+
CODEGRAPH_DIR = '.codegraph'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_codegraph_dir(project_root: str) -> str:
|
|
21
|
+
"""Get the path to the .codegraph directory."""
|
|
22
|
+
return os.path.join(project_root, CODEGRAPH_DIR)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_database_path(project_root: str) -> str:
|
|
26
|
+
"""Get the path to the SQLite database."""
|
|
27
|
+
return os.path.join(get_codegraph_dir(project_root), 'codegraph.db')
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_initialized(project_root: str) -> bool:
|
|
31
|
+
"""Check if a directory has been initialized as a CodeGraph project."""
|
|
32
|
+
cg_dir = get_codegraph_dir(project_root)
|
|
33
|
+
db_path = os.path.join(cg_dir, 'codegraph.db')
|
|
34
|
+
return os.path.isdir(cg_dir) and os.path.isfile(db_path)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def create_directory(project_root: str) -> None:
|
|
38
|
+
"""Create the .codegraph directory structure."""
|
|
39
|
+
cg_dir = get_codegraph_dir(project_root)
|
|
40
|
+
os.makedirs(cg_dir, exist_ok=True)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def remove_directory(project_root: str) -> None:
|
|
44
|
+
"""Remove the .codegraph directory structure."""
|
|
45
|
+
cg_dir = get_codegraph_dir(project_root)
|
|
46
|
+
if os.path.isdir(cg_dir):
|
|
47
|
+
shutil.rmtree(cg_dir)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def validate_directory(project_root: str) -> Tuple[bool, List[str]]:
|
|
51
|
+
"""Validate the .codegraph directory structure."""
|
|
52
|
+
errors_list = []
|
|
53
|
+
cg_dir = get_codegraph_dir(project_root)
|
|
54
|
+
|
|
55
|
+
if not os.path.isdir(cg_dir):
|
|
56
|
+
errors_list.append(f'{CODEGRAPH_DIR}/ directory not found')
|
|
57
|
+
|
|
58
|
+
db_path = os.path.join(cg_dir, 'codegraph.db')
|
|
59
|
+
if not os.path.isfile(db_path):
|
|
60
|
+
errors_list.append(f'{CODEGRAPH_DIR}/codegraph.db not found')
|
|
61
|
+
|
|
62
|
+
return (len(errors_list) == 0, errors_list)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def find_nearest_codegraph_root(path: str) -> Optional[str]:
|
|
66
|
+
"""Walk up from path to find the nearest .codegraph directory."""
|
|
67
|
+
path = os.path.normpath(os.path.abspath(path))
|
|
68
|
+
root = Path(path).anchor
|
|
69
|
+
|
|
70
|
+
while path and path != root:
|
|
71
|
+
if is_initialized(path):
|
|
72
|
+
return path
|
|
73
|
+
parent = os.path.dirname(path)
|
|
74
|
+
if parent == path:
|
|
75
|
+
break
|
|
76
|
+
path = parent
|
|
77
|
+
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def is_codegraph_data_dir(name: str) -> bool:
|
|
82
|
+
"""Check if a directory name is the CodeGraph data directory."""
|
|
83
|
+
return name == CODEGRAPH_DIR
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def unsafe_index_root_reason(path: str) -> Optional[str]:
|
|
87
|
+
"""Check if a path is unsafe to index (home dir, root, etc)."""
|
|
88
|
+
home = os.path.expanduser('~')
|
|
89
|
+
path = os.path.normpath(os.path.abspath(path))
|
|
90
|
+
|
|
91
|
+
if path == home:
|
|
92
|
+
return 'your home directory'
|
|
93
|
+
if path == Path(path).anchor:
|
|
94
|
+
return 'a filesystem root'
|
|
95
|
+
|
|
96
|
+
# Check common dangerous paths
|
|
97
|
+
dangerous = [
|
|
98
|
+
'/etc', '/var', '/sys', '/proc', '/dev', '/bin', '/sbin',
|
|
99
|
+
'/usr/bin', '/usr/lib', '/usr/sbin',
|
|
100
|
+
]
|
|
101
|
+
if os.name == 'posix':
|
|
102
|
+
for d in dangerous:
|
|
103
|
+
if path == d:
|
|
104
|
+
return 'a system directory'
|
|
105
|
+
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def derive_project_name_tokens(project_root: str) -> List[str]:
|
|
110
|
+
"""Derive project name tokens from project configuration files."""
|
|
111
|
+
tokens = []
|
|
112
|
+
|
|
113
|
+
# Try pyproject.toml
|
|
114
|
+
pyproject = os.path.join(project_root, 'pyproject.toml')
|
|
115
|
+
if os.path.isfile(pyproject):
|
|
116
|
+
try:
|
|
117
|
+
# Minimal TOML parsing for project name
|
|
118
|
+
with open(pyproject, 'r') as f:
|
|
119
|
+
for line in f:
|
|
120
|
+
if line.strip().startswith('name'):
|
|
121
|
+
import re
|
|
122
|
+
m = re.search(r'name\s*=\s*["\'](.+?)["\']', line)
|
|
123
|
+
if m:
|
|
124
|
+
tokens.extend(m.group(1).replace('-', ' ').replace('_', ' ').split())
|
|
125
|
+
break
|
|
126
|
+
except Exception:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
# Try package.json
|
|
130
|
+
pkg_json = os.path.join(project_root, 'package.json')
|
|
131
|
+
if os.path.isfile(pkg_json):
|
|
132
|
+
try:
|
|
133
|
+
with open(pkg_json, 'r') as f:
|
|
134
|
+
data = json.load(f)
|
|
135
|
+
if 'name' in data:
|
|
136
|
+
tokens.extend(data['name'].replace('-', ' ').replace('_', ' ').split())
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
# Try setup.cfg
|
|
141
|
+
setup_cfg = os.path.join(project_root, 'setup.cfg')
|
|
142
|
+
if os.path.isfile(setup_cfg):
|
|
143
|
+
try:
|
|
144
|
+
with open(setup_cfg, 'r') as f:
|
|
145
|
+
for line in f:
|
|
146
|
+
if line.strip().startswith('name'):
|
|
147
|
+
import re
|
|
148
|
+
m = re.search(r'name\s*=\s*(.+?)$', line)
|
|
149
|
+
if m:
|
|
150
|
+
tokens.extend(m.group(1).strip().replace('-', ' ').replace('_', ' ').split())
|
|
151
|
+
break
|
|
152
|
+
except Exception:
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
# Fallback: use directory name
|
|
156
|
+
if not tokens:
|
|
157
|
+
dir_name = os.path.basename(project_root)
|
|
158
|
+
tokens = dir_name.replace('-', ' ').replace('_', ' ').split()
|
|
159
|
+
|
|
160
|
+
return [t.lower() for t in tokens if len(t) > 1]
|
codegraph/errors.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CodeGraph Error Types
|
|
3
|
+
|
|
4
|
+
Custom exceptions and logging utilities.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CodeGraphError(Exception):
|
|
15
|
+
"""Base error for all CodeGraph errors."""
|
|
16
|
+
def __init__(self, message: str, cause: Optional[Exception] = None):
|
|
17
|
+
self.message = message
|
|
18
|
+
self.cause = cause
|
|
19
|
+
super().__init__(message)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FileError(CodeGraphError):
|
|
23
|
+
"""Error related to file operations."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ParseError(CodeGraphError):
|
|
28
|
+
"""Error during code parsing."""
|
|
29
|
+
def __init__(self, message: str, file_path: Optional[str] = None,
|
|
30
|
+
line: Optional[int] = None, column: Optional[int] = None):
|
|
31
|
+
self.file_path = file_path
|
|
32
|
+
self.line = line
|
|
33
|
+
self.column = column
|
|
34
|
+
super().__init__(message)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DatabaseError(CodeGraphError):
|
|
38
|
+
"""Error related to database operations."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SearchError(CodeGraphError):
|
|
43
|
+
"""Error during search operations."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ConfigError(CodeGraphError):
|
|
48
|
+
"""Error related to configuration."""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# =============================================================================
|
|
53
|
+
# Logger
|
|
54
|
+
# =============================================================================
|
|
55
|
+
|
|
56
|
+
_logger: Optional[logging.Logger] = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_logger() -> logging.Logger:
|
|
60
|
+
"""Get the global CodeGraph logger."""
|
|
61
|
+
global _logger
|
|
62
|
+
if _logger is None:
|
|
63
|
+
_logger = logging.getLogger('codegraph')
|
|
64
|
+
if not _logger.handlers:
|
|
65
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
66
|
+
handler.setFormatter(logging.Formatter(
|
|
67
|
+
'[%(levelname)s] %(message)s'
|
|
68
|
+
))
|
|
69
|
+
_logger.addHandler(handler)
|
|
70
|
+
_logger.setLevel(logging.WARNING)
|
|
71
|
+
return _logger
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def set_logger(logger: logging.Logger) -> None:
|
|
75
|
+
"""Set a custom logger."""
|
|
76
|
+
global _logger
|
|
77
|
+
_logger = logger
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def log_debug(message: str, **kwargs) -> None:
|
|
81
|
+
"""Log a debug message."""
|
|
82
|
+
extra = f' {" ".join(f"{k}={v}" for k, v in kwargs.items())}' if kwargs else ''
|
|
83
|
+
get_logger().debug(f'{message}{extra}')
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def log_info(message: str, **kwargs) -> None:
|
|
87
|
+
"""Log an info message."""
|
|
88
|
+
extra = f' {" ".join(f"{k}={v}" for k, v in kwargs.items())}' if kwargs else ''
|
|
89
|
+
get_logger().info(f'{message}{extra}')
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def log_warn(message: str, **kwargs) -> None:
|
|
93
|
+
"""Log a warning message."""
|
|
94
|
+
extra = f' {" ".join(f"{k}={v}" for k, v in kwargs.items())}' if kwargs else ''
|
|
95
|
+
get_logger().warning(f'{message}{extra}')
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def log_error(message: str, **kwargs) -> None:
|
|
99
|
+
"""Log an error message."""
|
|
100
|
+
extra = f' {" ".join(f"{k}={v}" for k, v in kwargs.items())}' if kwargs else ''
|
|
101
|
+
get_logger().error(f'{message}{extra}')
|