mcp-ticketer 0.4.11__py3-none-any.whl → 0.12.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.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- mcp_ticketer/__version__.py +3 -3
- mcp_ticketer/adapters/__init__.py +2 -0
- mcp_ticketer/adapters/aitrackdown.py +9 -3
- mcp_ticketer/adapters/asana/__init__.py +15 -0
- mcp_ticketer/adapters/asana/adapter.py +1308 -0
- mcp_ticketer/adapters/asana/client.py +292 -0
- mcp_ticketer/adapters/asana/mappers.py +334 -0
- mcp_ticketer/adapters/asana/types.py +146 -0
- mcp_ticketer/adapters/github.py +313 -96
- mcp_ticketer/adapters/jira.py +251 -1
- mcp_ticketer/adapters/linear/adapter.py +524 -22
- mcp_ticketer/adapters/linear/client.py +61 -9
- mcp_ticketer/adapters/linear/mappers.py +9 -3
- mcp_ticketer/cache/memory.py +3 -3
- mcp_ticketer/cli/adapter_diagnostics.py +1 -1
- mcp_ticketer/cli/auggie_configure.py +1 -1
- mcp_ticketer/cli/codex_configure.py +80 -1
- mcp_ticketer/cli/configure.py +33 -43
- mcp_ticketer/cli/diagnostics.py +18 -16
- mcp_ticketer/cli/discover.py +288 -21
- mcp_ticketer/cli/gemini_configure.py +1 -1
- mcp_ticketer/cli/instruction_commands.py +429 -0
- mcp_ticketer/cli/linear_commands.py +99 -15
- mcp_ticketer/cli/main.py +1199 -227
- mcp_ticketer/cli/mcp_configure.py +1 -1
- mcp_ticketer/cli/migrate_config.py +12 -8
- mcp_ticketer/cli/platform_commands.py +6 -6
- mcp_ticketer/cli/platform_detection.py +412 -0
- mcp_ticketer/cli/queue_commands.py +15 -15
- mcp_ticketer/cli/simple_health.py +1 -1
- mcp_ticketer/cli/ticket_commands.py +14 -13
- mcp_ticketer/cli/update_checker.py +313 -0
- mcp_ticketer/cli/utils.py +45 -41
- mcp_ticketer/core/__init__.py +12 -0
- mcp_ticketer/core/adapter.py +4 -4
- mcp_ticketer/core/config.py +17 -10
- mcp_ticketer/core/env_discovery.py +33 -3
- mcp_ticketer/core/env_loader.py +7 -6
- mcp_ticketer/core/exceptions.py +3 -3
- mcp_ticketer/core/http_client.py +10 -10
- mcp_ticketer/core/instructions.py +405 -0
- mcp_ticketer/core/mappers.py +1 -1
- mcp_ticketer/core/models.py +1 -1
- mcp_ticketer/core/onepassword_secrets.py +379 -0
- mcp_ticketer/core/project_config.py +17 -1
- mcp_ticketer/core/registry.py +1 -1
- mcp_ticketer/defaults/ticket_instructions.md +644 -0
- mcp_ticketer/mcp/__init__.py +2 -2
- mcp_ticketer/mcp/server/__init__.py +2 -2
- mcp_ticketer/mcp/server/main.py +82 -69
- mcp_ticketer/mcp/server/tools/__init__.py +9 -0
- mcp_ticketer/mcp/server/tools/attachment_tools.py +63 -16
- mcp_ticketer/mcp/server/tools/config_tools.py +381 -0
- mcp_ticketer/mcp/server/tools/hierarchy_tools.py +154 -5
- mcp_ticketer/mcp/server/tools/instruction_tools.py +293 -0
- mcp_ticketer/mcp/server/tools/ticket_tools.py +157 -4
- mcp_ticketer/mcp/server/tools/user_ticket_tools.py +382 -0
- mcp_ticketer/queue/health_monitor.py +1 -0
- mcp_ticketer/queue/manager.py +4 -4
- mcp_ticketer/queue/queue.py +3 -3
- mcp_ticketer/queue/run_worker.py +1 -1
- mcp_ticketer/queue/ticket_registry.py +2 -2
- mcp_ticketer/queue/worker.py +14 -12
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/METADATA +106 -52
- mcp_ticketer-0.12.0.dist-info/RECORD +91 -0
- mcp_ticketer-0.4.11.dist-info/RECORD +0 -77
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/WHEEL +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/entry_points.txt +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_ticketer-0.4.11.dist-info → mcp_ticketer-0.12.0.dist-info}/top_level.txt +0 -0
mcp_ticketer/core/adapter.py
CHANGED
|
@@ -230,7 +230,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
230
230
|
# Epic/Issue/Task Hierarchy Methods
|
|
231
231
|
|
|
232
232
|
async def create_epic(
|
|
233
|
-
self, title: str, description: str | None = None, **kwargs
|
|
233
|
+
self, title: str, description: str | None = None, **kwargs: Any
|
|
234
234
|
) -> Epic | None:
|
|
235
235
|
"""Create epic (top-level grouping).
|
|
236
236
|
|
|
@@ -270,7 +270,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
270
270
|
return result
|
|
271
271
|
return None
|
|
272
272
|
|
|
273
|
-
async def list_epics(self, **kwargs) -> builtins.list[Epic]:
|
|
273
|
+
async def list_epics(self, **kwargs: Any) -> builtins.list[Epic]:
|
|
274
274
|
"""List all epics.
|
|
275
275
|
|
|
276
276
|
Args:
|
|
@@ -291,7 +291,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
291
291
|
title: str,
|
|
292
292
|
description: str | None = None,
|
|
293
293
|
epic_id: str | None = None,
|
|
294
|
-
**kwargs,
|
|
294
|
+
**kwargs: Any,
|
|
295
295
|
) -> Task | None:
|
|
296
296
|
"""Create issue, optionally linked to epic.
|
|
297
297
|
|
|
@@ -330,7 +330,7 @@ class BaseAdapter(ABC, Generic[T]):
|
|
|
330
330
|
return [r for r in results if isinstance(r, Task) and r.is_issue()]
|
|
331
331
|
|
|
332
332
|
async def create_task(
|
|
333
|
-
self, title: str, parent_id: str, description: str | None = None, **kwargs
|
|
333
|
+
self, title: str, parent_id: str, description: str | None = None, **kwargs: Any
|
|
334
334
|
) -> Task | None:
|
|
335
335
|
"""Create task as sub-ticket of parent issue.
|
|
336
336
|
|
mcp_ticketer/core/config.py
CHANGED
|
@@ -47,7 +47,8 @@ class GitHubConfig(BaseAdapterConfig):
|
|
|
47
47
|
|
|
48
48
|
@field_validator("token", mode="before")
|
|
49
49
|
@classmethod
|
|
50
|
-
def validate_token(cls, v):
|
|
50
|
+
def validate_token(cls, v: Any) -> str:
|
|
51
|
+
"""Validate GitHub token from config or environment."""
|
|
51
52
|
if not v:
|
|
52
53
|
v = os.getenv("GITHUB_TOKEN")
|
|
53
54
|
if not v:
|
|
@@ -56,7 +57,8 @@ class GitHubConfig(BaseAdapterConfig):
|
|
|
56
57
|
|
|
57
58
|
@field_validator("owner", mode="before")
|
|
58
59
|
@classmethod
|
|
59
|
-
def validate_owner(cls, v):
|
|
60
|
+
def validate_owner(cls, v: Any) -> str:
|
|
61
|
+
"""Validate GitHub repository owner from config or environment."""
|
|
60
62
|
if not v:
|
|
61
63
|
v = os.getenv("GITHUB_OWNER")
|
|
62
64
|
if not v:
|
|
@@ -65,7 +67,8 @@ class GitHubConfig(BaseAdapterConfig):
|
|
|
65
67
|
|
|
66
68
|
@field_validator("repo", mode="before")
|
|
67
69
|
@classmethod
|
|
68
|
-
def validate_repo(cls, v):
|
|
70
|
+
def validate_repo(cls, v: Any) -> str:
|
|
71
|
+
"""Validate GitHub repository name from config or environment."""
|
|
69
72
|
if not v:
|
|
70
73
|
v = os.getenv("GITHUB_REPO")
|
|
71
74
|
if not v:
|
|
@@ -86,7 +89,8 @@ class JiraConfig(BaseAdapterConfig):
|
|
|
86
89
|
|
|
87
90
|
@field_validator("server", mode="before")
|
|
88
91
|
@classmethod
|
|
89
|
-
def validate_server(cls, v):
|
|
92
|
+
def validate_server(cls, v: Any) -> str:
|
|
93
|
+
"""Validate JIRA server URL from config or environment."""
|
|
90
94
|
if not v:
|
|
91
95
|
v = os.getenv("JIRA_SERVER")
|
|
92
96
|
if not v:
|
|
@@ -95,7 +99,8 @@ class JiraConfig(BaseAdapterConfig):
|
|
|
95
99
|
|
|
96
100
|
@field_validator("email", mode="before")
|
|
97
101
|
@classmethod
|
|
98
|
-
def validate_email(cls, v):
|
|
102
|
+
def validate_email(cls, v: Any) -> str:
|
|
103
|
+
"""Validate JIRA user email from config or environment."""
|
|
99
104
|
if not v:
|
|
100
105
|
v = os.getenv("JIRA_EMAIL")
|
|
101
106
|
if not v:
|
|
@@ -104,7 +109,8 @@ class JiraConfig(BaseAdapterConfig):
|
|
|
104
109
|
|
|
105
110
|
@field_validator("api_token", mode="before")
|
|
106
111
|
@classmethod
|
|
107
|
-
def validate_api_token(cls, v):
|
|
112
|
+
def validate_api_token(cls, v: Any) -> str:
|
|
113
|
+
"""Validate JIRA API token from config or environment."""
|
|
108
114
|
if not v:
|
|
109
115
|
v = os.getenv("JIRA_API_TOKEN")
|
|
110
116
|
if not v:
|
|
@@ -123,7 +129,7 @@ class LinearConfig(BaseAdapterConfig):
|
|
|
123
129
|
api_url: str = "https://api.linear.app/graphql"
|
|
124
130
|
|
|
125
131
|
@model_validator(mode="after")
|
|
126
|
-
def validate_team_identifier(self):
|
|
132
|
+
def validate_team_identifier(self) -> "LinearConfig":
|
|
127
133
|
"""Ensure either team_key or team_id is provided."""
|
|
128
134
|
if not self.team_key and not self.team_id:
|
|
129
135
|
raise ValueError("Either team_key or team_id is required")
|
|
@@ -131,7 +137,8 @@ class LinearConfig(BaseAdapterConfig):
|
|
|
131
137
|
|
|
132
138
|
@field_validator("api_key", mode="before")
|
|
133
139
|
@classmethod
|
|
134
|
-
def validate_api_key(cls, v):
|
|
140
|
+
def validate_api_key(cls, v: Any) -> str:
|
|
141
|
+
"""Validate Linear API key from config or environment."""
|
|
135
142
|
if not v:
|
|
136
143
|
v = os.getenv("LINEAR_API_KEY")
|
|
137
144
|
if not v:
|
|
@@ -179,7 +186,7 @@ class AppConfig(BaseModel):
|
|
|
179
186
|
default_adapter: str | None = None
|
|
180
187
|
|
|
181
188
|
@model_validator(mode="after")
|
|
182
|
-
def validate_adapters(self):
|
|
189
|
+
def validate_adapters(self) -> "AppConfig":
|
|
183
190
|
"""Validate adapter configurations."""
|
|
184
191
|
adapters = self.adapters
|
|
185
192
|
|
|
@@ -220,7 +227,7 @@ class ConfigurationManager:
|
|
|
220
227
|
cls._instance = super().__new__(cls)
|
|
221
228
|
return cls._instance
|
|
222
229
|
|
|
223
|
-
def __init__(self):
|
|
230
|
+
def __init__(self) -> None:
|
|
224
231
|
"""Initialize configuration manager."""
|
|
225
232
|
if not hasattr(self, "_initialized"):
|
|
226
233
|
self._initialized = True
|
|
@@ -6,6 +6,7 @@ environment files, including:
|
|
|
6
6
|
- Support for multiple naming conventions
|
|
7
7
|
- Project information extraction
|
|
8
8
|
- Security validation
|
|
9
|
+
- 1Password CLI integration for secret references
|
|
9
10
|
"""
|
|
10
11
|
|
|
11
12
|
import logging
|
|
@@ -15,6 +16,7 @@ from typing import Any
|
|
|
15
16
|
|
|
16
17
|
from dotenv import dotenv_values
|
|
17
18
|
|
|
19
|
+
from .onepassword_secrets import OnePasswordConfig, OnePasswordSecretsLoader
|
|
18
20
|
from .project_config import AdapterType
|
|
19
21
|
|
|
20
22
|
logger = logging.getLogger(__name__)
|
|
@@ -155,14 +157,27 @@ class EnvDiscovery:
|
|
|
155
157
|
".env.development",
|
|
156
158
|
]
|
|
157
159
|
|
|
158
|
-
def __init__(
|
|
160
|
+
def __init__(
|
|
161
|
+
self,
|
|
162
|
+
project_path: Path | None = None,
|
|
163
|
+
enable_1password: bool = True,
|
|
164
|
+
onepassword_config: OnePasswordConfig | None = None,
|
|
165
|
+
):
|
|
159
166
|
"""Initialize discovery.
|
|
160
167
|
|
|
161
168
|
Args:
|
|
162
169
|
project_path: Path to project root (defaults to cwd)
|
|
170
|
+
enable_1password: Enable 1Password CLI integration for secret resolution
|
|
171
|
+
onepassword_config: Configuration for 1Password integration
|
|
163
172
|
|
|
164
173
|
"""
|
|
165
174
|
self.project_path = project_path or Path.cwd()
|
|
175
|
+
self.enable_1password = enable_1password
|
|
176
|
+
self.op_loader = (
|
|
177
|
+
OnePasswordSecretsLoader(onepassword_config or OnePasswordConfig())
|
|
178
|
+
if enable_1password
|
|
179
|
+
else None
|
|
180
|
+
)
|
|
166
181
|
|
|
167
182
|
def discover(self) -> DiscoveryResult:
|
|
168
183
|
"""Discover adapter configurations from environment files.
|
|
@@ -241,7 +256,22 @@ class EnvDiscovery:
|
|
|
241
256
|
file_path = self.project_path / env_file
|
|
242
257
|
if file_path.exists():
|
|
243
258
|
try:
|
|
244
|
-
|
|
259
|
+
# Check if file contains 1Password references and use op loader if available
|
|
260
|
+
if self.op_loader and self.enable_1password:
|
|
261
|
+
content = file_path.read_text(encoding="utf-8")
|
|
262
|
+
if "op://" in content:
|
|
263
|
+
logger.info(
|
|
264
|
+
f"Detected 1Password references in {env_file}, "
|
|
265
|
+
"attempting to resolve..."
|
|
266
|
+
)
|
|
267
|
+
env_vars = self.op_loader.load_secrets_from_env_file(
|
|
268
|
+
file_path
|
|
269
|
+
)
|
|
270
|
+
else:
|
|
271
|
+
env_vars = dotenv_values(file_path)
|
|
272
|
+
else:
|
|
273
|
+
env_vars = dotenv_values(file_path)
|
|
274
|
+
|
|
245
275
|
# Filter out None values
|
|
246
276
|
env_vars = {k: v for k, v in env_vars.items() if v is not None}
|
|
247
277
|
merged_env.update(env_vars)
|
|
@@ -623,7 +653,7 @@ class EnvDiscovery:
|
|
|
623
653
|
|
|
624
654
|
|
|
625
655
|
def discover_config(project_path: Path | None = None) -> DiscoveryResult:
|
|
626
|
-
"""
|
|
656
|
+
"""Discover configuration from environment files.
|
|
627
657
|
|
|
628
658
|
Args:
|
|
629
659
|
project_path: Path to project root (defaults to cwd)
|
mcp_ticketer/core/env_loader.py
CHANGED
|
@@ -29,8 +29,9 @@ class EnvKeyConfig:
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class UnifiedEnvLoader:
|
|
32
|
-
"""Unified environment loader that handles multiple naming conventions
|
|
33
|
-
|
|
32
|
+
"""Unified environment loader that handles multiple naming conventions.
|
|
33
|
+
|
|
34
|
+
Provides consistent environment loading across all contexts.
|
|
34
35
|
"""
|
|
35
36
|
|
|
36
37
|
# Define key aliases for all adapters
|
|
@@ -131,7 +132,7 @@ class UnifiedEnvLoader:
|
|
|
131
132
|
# Fallback to current directory
|
|
132
133
|
return Path.cwd()
|
|
133
134
|
|
|
134
|
-
def _load_env_files(self):
|
|
135
|
+
def _load_env_files(self) -> None:
|
|
135
136
|
"""Load environment variables from .env files."""
|
|
136
137
|
env_files = [
|
|
137
138
|
self.project_root / ".env.local",
|
|
@@ -144,7 +145,7 @@ class UnifiedEnvLoader:
|
|
|
144
145
|
logger.debug(f"Loading environment from: {env_file}")
|
|
145
146
|
self._load_env_file(env_file)
|
|
146
147
|
|
|
147
|
-
def _load_env_file(self, env_file: Path):
|
|
148
|
+
def _load_env_file(self, env_file: Path) -> None:
|
|
148
149
|
"""Load variables from a single .env file."""
|
|
149
150
|
try:
|
|
150
151
|
with open(env_file) as f:
|
|
@@ -321,7 +322,7 @@ def get_env_loader() -> UnifiedEnvLoader:
|
|
|
321
322
|
def load_adapter_config(
|
|
322
323
|
adapter_name: str, base_config: dict[str, Any] | None = None
|
|
323
324
|
) -> dict[str, Any]:
|
|
324
|
-
"""
|
|
325
|
+
"""Load adapter configuration with environment variables.
|
|
325
326
|
|
|
326
327
|
Args:
|
|
327
328
|
adapter_name: Name of the adapter ('linear', 'jira', 'github')
|
|
@@ -335,7 +336,7 @@ def load_adapter_config(
|
|
|
335
336
|
|
|
336
337
|
|
|
337
338
|
def validate_adapter_config(adapter_name: str, config: dict[str, Any]) -> list[str]:
|
|
338
|
-
"""
|
|
339
|
+
"""Validate adapter configuration.
|
|
339
340
|
|
|
340
341
|
Args:
|
|
341
342
|
adapter_name: Name of the adapter
|
mcp_ticketer/core/exceptions.py
CHANGED
|
@@ -35,7 +35,7 @@ class AdapterError(MCPTicketerError):
|
|
|
35
35
|
self.original_error = original_error
|
|
36
36
|
|
|
37
37
|
def __str__(self) -> str:
|
|
38
|
-
"""
|
|
38
|
+
"""Return string representation of the error."""
|
|
39
39
|
base_msg = f"[{self.adapter_name}] {super().__str__()}"
|
|
40
40
|
if self.original_error:
|
|
41
41
|
base_msg += f" (caused by: {self.original_error})"
|
|
@@ -88,7 +88,7 @@ class ValidationError(MCPTicketerError):
|
|
|
88
88
|
self.value = value
|
|
89
89
|
|
|
90
90
|
def __str__(self) -> str:
|
|
91
|
-
"""
|
|
91
|
+
"""Return string representation of the error."""
|
|
92
92
|
base_msg = super().__str__()
|
|
93
93
|
if self.field:
|
|
94
94
|
base_msg += f" (field: {self.field})"
|
|
@@ -126,7 +126,7 @@ class StateTransitionError(MCPTicketerError):
|
|
|
126
126
|
self.to_state = to_state
|
|
127
127
|
|
|
128
128
|
def __str__(self) -> str:
|
|
129
|
-
"""
|
|
129
|
+
"""Return string representation of the error."""
|
|
130
130
|
return f"{super().__str__()} ({self.from_state} -> {self.to_state})"
|
|
131
131
|
|
|
132
132
|
|
mcp_ticketer/core/http_client.py
CHANGED
|
@@ -206,7 +206,7 @@ class BaseHTTPClient:
|
|
|
206
206
|
headers: dict[str, str] | None = None,
|
|
207
207
|
timeout: float | None = None,
|
|
208
208
|
retry_count: int = 0,
|
|
209
|
-
**kwargs,
|
|
209
|
+
**kwargs: Any,
|
|
210
210
|
) -> httpx.Response:
|
|
211
211
|
"""Make HTTP request with retry and rate limiting.
|
|
212
212
|
|
|
@@ -293,27 +293,27 @@ class BaseHTTPClient:
|
|
|
293
293
|
# No more retries, re-raise the exception
|
|
294
294
|
raise
|
|
295
295
|
|
|
296
|
-
async def get(self, endpoint: str, **kwargs) -> httpx.Response:
|
|
296
|
+
async def get(self, endpoint: str, **kwargs: Any) -> httpx.Response:
|
|
297
297
|
"""Make GET request."""
|
|
298
298
|
return await self.request(HTTPMethod.GET, endpoint, **kwargs)
|
|
299
299
|
|
|
300
|
-
async def post(self, endpoint: str, **kwargs) -> httpx.Response:
|
|
300
|
+
async def post(self, endpoint: str, **kwargs: Any) -> httpx.Response:
|
|
301
301
|
"""Make POST request."""
|
|
302
302
|
return await self.request(HTTPMethod.POST, endpoint, **kwargs)
|
|
303
303
|
|
|
304
|
-
async def put(self, endpoint: str, **kwargs) -> httpx.Response:
|
|
304
|
+
async def put(self, endpoint: str, **kwargs: Any) -> httpx.Response:
|
|
305
305
|
"""Make PUT request."""
|
|
306
306
|
return await self.request(HTTPMethod.PUT, endpoint, **kwargs)
|
|
307
307
|
|
|
308
|
-
async def patch(self, endpoint: str, **kwargs) -> httpx.Response:
|
|
308
|
+
async def patch(self, endpoint: str, **kwargs: Any) -> httpx.Response:
|
|
309
309
|
"""Make PATCH request."""
|
|
310
310
|
return await self.request(HTTPMethod.PATCH, endpoint, **kwargs)
|
|
311
311
|
|
|
312
|
-
async def delete(self, endpoint: str, **kwargs) -> httpx.Response:
|
|
312
|
+
async def delete(self, endpoint: str, **kwargs: Any) -> httpx.Response:
|
|
313
313
|
"""Make DELETE request."""
|
|
314
314
|
return await self.request(HTTPMethod.DELETE, endpoint, **kwargs)
|
|
315
315
|
|
|
316
|
-
async def get_json(self, endpoint: str, **kwargs) -> dict[str, Any]:
|
|
316
|
+
async def get_json(self, endpoint: str, **kwargs: Any) -> dict[str, Any]:
|
|
317
317
|
"""Make GET request and return JSON response."""
|
|
318
318
|
response = await self.get(endpoint, **kwargs)
|
|
319
319
|
|
|
@@ -323,7 +323,7 @@ class BaseHTTPClient:
|
|
|
323
323
|
|
|
324
324
|
return response.json()
|
|
325
325
|
|
|
326
|
-
async def post_json(self, endpoint: str, **kwargs) -> dict[str, Any]:
|
|
326
|
+
async def post_json(self, endpoint: str, **kwargs: Any) -> dict[str, Any]:
|
|
327
327
|
"""Make POST request and return JSON response."""
|
|
328
328
|
response = await self.post(endpoint, **kwargs)
|
|
329
329
|
|
|
@@ -333,7 +333,7 @@ class BaseHTTPClient:
|
|
|
333
333
|
|
|
334
334
|
return response.json()
|
|
335
335
|
|
|
336
|
-
async def put_json(self, endpoint: str, **kwargs) -> dict[str, Any]:
|
|
336
|
+
async def put_json(self, endpoint: str, **kwargs: Any) -> dict[str, Any]:
|
|
337
337
|
"""Make PUT request and return JSON response."""
|
|
338
338
|
response = await self.put(endpoint, **kwargs)
|
|
339
339
|
|
|
@@ -343,7 +343,7 @@ class BaseHTTPClient:
|
|
|
343
343
|
|
|
344
344
|
return response.json()
|
|
345
345
|
|
|
346
|
-
async def patch_json(self, endpoint: str, **kwargs) -> dict[str, Any]:
|
|
346
|
+
async def patch_json(self, endpoint: str, **kwargs: Any) -> dict[str, Any]:
|
|
347
347
|
"""Make PATCH request and return JSON response."""
|
|
348
348
|
response = await self.patch(endpoint, **kwargs)
|
|
349
349
|
|