kd-cli 2.1.2__tar.gz → 2.1.4__tar.gz

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.
Files changed (74) hide show
  1. {kd_cli-2.1.2/kd_cli.egg-info → kd_cli-2.1.4}/PKG-INFO +1 -1
  2. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/api/http.py +25 -3
  3. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/connection.py +6 -1
  4. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/resources/CHANGELOG.md +13 -0
  5. {kd_cli-2.1.2 → kd_cli-2.1.4/kd_cli.egg-info}/PKG-INFO +1 -1
  6. {kd_cli-2.1.2 → kd_cli-2.1.4}/pyproject.toml +1 -1
  7. {kd_cli-2.1.2 → kd_cli-2.1.4}/LICENSE +0 -0
  8. {kd_cli-2.1.2 → kd_cli-2.1.4}/MANIFEST.in +0 -0
  9. {kd_cli-2.1.2 → kd_cli-2.1.4}/README.md +0 -0
  10. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/__init__.py +0 -0
  11. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/api/__init__.py +0 -0
  12. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/api/articles.py +0 -0
  13. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/api/hub.py +0 -0
  14. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/api/saved_queries.py +0 -0
  15. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/api/tags.py +0 -0
  16. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/api/youtrack.py +0 -0
  17. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/article_marker.py +0 -0
  18. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/auth.py +0 -0
  19. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/__init__.py +0 -0
  20. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/core/__init__.py +0 -0
  21. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/core/command_base.py +0 -0
  22. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/core/command_parser.py +0 -0
  23. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/core/namespaces.py +0 -0
  24. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/core/option_registry.py +0 -0
  25. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/input.py +0 -0
  26. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/main.py +0 -0
  27. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/__init__.py +0 -0
  28. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/alias/__init__.py +0 -0
  29. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/article/__init__.py +0 -0
  30. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/init.py +0 -0
  31. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/issue/__init__.py +0 -0
  32. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/primer/__init__.py +0 -0
  33. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/project/__init__.py +0 -0
  34. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/query/__init__.py +0 -0
  35. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/release_notes.py +0 -0
  36. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/search/__init__.py +0 -0
  37. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/system/__init__.py +0 -0
  38. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/tag/__init__.py +0 -0
  39. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/time/__init__.py +0 -0
  40. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/namespaces/user/__init__.py +0 -0
  41. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/output.py +0 -0
  42. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/cli/tree.py +0 -0
  43. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/client.py +0 -0
  44. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/config.py +0 -0
  45. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/config_schema.py +0 -0
  46. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/debug.py +0 -0
  47. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/exceptions.py +0 -0
  48. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/history.py +0 -0
  49. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/__init__.py +0 -0
  50. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/article.py +0 -0
  51. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/assist.py +0 -0
  52. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/attachment.py +0 -0
  53. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/comment.py +0 -0
  54. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/common.py +0 -0
  55. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/issue.py +0 -0
  56. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/project.py +0 -0
  57. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/saved_query.py +0 -0
  58. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/search.py +0 -0
  59. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/tag.py +0 -0
  60. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/user.py +0 -0
  61. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/models/work_item.py +0 -0
  62. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/primer.py +0 -0
  63. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/query_parser.py +0 -0
  64. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/release_notes.py +0 -0
  65. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/resources/__init__.py +0 -0
  66. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/resources/command-synonyms.yaml +0 -0
  67. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/resources/config-template.yaml +0 -0
  68. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd/resources/error-messages.yaml +0 -0
  69. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd_cli.egg-info/SOURCES.txt +0 -0
  70. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd_cli.egg-info/dependency_links.txt +0 -0
  71. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd_cli.egg-info/entry_points.txt +0 -0
  72. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd_cli.egg-info/requires.txt +0 -0
  73. {kd_cli-2.1.2 → kd_cli-2.1.4}/kd_cli.egg-info/top_level.txt +0 -0
  74. {kd_cli-2.1.2 → kd_cli-2.1.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kd-cli
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: Knowledge Dispatcher for issue trackers and knowledge bases
5
5
  Author: Sergiy Gladkyy
6
6
  License-Expression: MIT
@@ -61,7 +61,7 @@ class HttpClient:
61
61
  if fields:
62
62
  all_params["fields"] = fields
63
63
  response = self._request("GET", path, params=all_params)
64
- return response.json()
64
+ return self._decode_json(response)
65
65
 
66
66
  def post(
67
67
  self,
@@ -77,7 +77,7 @@ class HttpClient:
77
77
  response = self._request("POST", path, json=json, params=all_params or None)
78
78
  if response.status_code == 204 or not response.content:
79
79
  return {}
80
- return response.json()
80
+ return self._decode_json(response)
81
81
 
82
82
  def post_file(
83
83
  self,
@@ -99,7 +99,7 @@ class HttpClient:
99
99
  )
100
100
  if response.status_code == 204 or not response.content:
101
101
  return {}
102
- return response.json()
102
+ return self._decode_json(response)
103
103
 
104
104
  def get_bytes(
105
105
  self,
@@ -168,6 +168,28 @@ class HttpClient:
168
168
  self._raise_for_status(last_response)
169
169
  raise AssertionError("unreachable") # pragma: no cover
170
170
 
171
+ @staticmethod
172
+ def _decode_json(response: httpx.Response) -> Any:
173
+ """Decode a 2xx response as JSON, guarding against non-JSON bodies.
174
+
175
+ A misconfigured base URL (e.g. trailing slash producing ``//api/...``)
176
+ can yield a 200 HTML page from YouTrack's web UI. Surface that as a
177
+ clear APIError instead of letting ``JSONDecodeError`` bubble up.
178
+ """
179
+ content_type = response.headers.get("content-type", "")
180
+ if "json" not in content_type.lower():
181
+ url = str(response.request.url) if response.request else "<unknown>"
182
+ raise APIError(
183
+ response.status_code,
184
+ (
185
+ f"Expected JSON response but got '{content_type or 'unknown'}' "
186
+ f"from {url}. Check the 'url' in your connection config — "
187
+ "a trailing slash or a non-YouTrack host can produce this."
188
+ ),
189
+ response.text[:500],
190
+ )
191
+ return response.json()
192
+
171
193
  @staticmethod
172
194
  def _extract_message(body: str) -> str:
173
195
  """Extract a human-readable message from a YouTrack error response.
@@ -13,7 +13,7 @@ from __future__ import annotations
13
13
  from dataclasses import dataclass
14
14
  from typing import TYPE_CHECKING, Any, Literal
15
15
 
16
- from pydantic import BaseModel, Field, model_validator
16
+ from pydantic import BaseModel, Field, field_validator, model_validator
17
17
 
18
18
  from kd.auth import resolve_connection_token
19
19
  from kd.exceptions import ConfigError
@@ -82,6 +82,11 @@ class ConnectionConfig(BaseModel):
82
82
  description="Version targeting (target + mode)",
83
83
  )
84
84
 
85
+ @field_validator("url", mode="after")
86
+ @classmethod
87
+ def _normalize_url(cls, value: str) -> str:
88
+ return value.rstrip("/")
89
+
85
90
  @model_validator(mode="after")
86
91
  def _require_auth_source(self) -> ConnectionConfig:
87
92
  if not self.token_cmd and not self.token_env:
@@ -4,6 +4,19 @@ All notable changes to kd-cli are recorded here. The project follows [Semantic V
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.1.4] — 2026-04-15
8
+
9
+ ### Fixed
10
+ - Connection `url` with a trailing slash no longer produces opaque `JSONDecodeError`. URLs are now normalized on load (`https://host/` → `https://host`), and the HTTP layer guards against non-JSON 2xx responses — a misconfigured URL that lands on YouTrack's HTML web UI now raises a clear `APIError` naming the URL and content type (KDCLI-67).
11
+
12
+ ### Documentation
13
+ - `docs/how-to/012-install-how-to.md` now walks through obtaining a YouTrack API token (Profile → Account Security → New token) and clarifies that the `token_env` name is user-configurable. The configuration how-to cross-links to it (KDCLI-68).
14
+
15
+ ## [2.1.3] — 2026-04-15
16
+
17
+ ### Fixed
18
+ - `sync-changelog` CI job now correctly publishes `CHANGELOG.md` on its first run against `kd-cli`. The 2.1.2 release exposed a guard that ignored untracked files, so the initial sync was a no-op and the PyPI Changelog link 404'd until this release (KDCLI-65).
19
+
7
20
  ## [2.1.2] — 2026-04-15
8
21
 
9
22
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kd-cli
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: Knowledge Dispatcher for issue trackers and knowledge bases
5
5
  Author: Sergiy Gladkyy
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "kd-cli"
7
- version = "2.1.2"
7
+ version = "2.1.4"
8
8
  description = "Knowledge Dispatcher for issue trackers and knowledge bases"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes