pyvikunja 0.6__tar.gz → 0.8__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.
- {pyvikunja-0.6/pyvikunja.egg-info → pyvikunja-0.8}/PKG-INFO +1 -1
- {pyvikunja-0.6 → pyvikunja-0.8}/pyproject.toml +1 -1
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja/api.py +52 -5
- {pyvikunja-0.6 → pyvikunja-0.8/pyvikunja.egg-info}/PKG-INFO +1 -1
- {pyvikunja-0.6 → pyvikunja-0.8}/LICENSE +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/README.md +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja/__init__.py +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja/models/enum/repeat_mode.py +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja/models/enum/task_priority.py +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja/models/label.py +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja/models/models.py +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja/models/project.py +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja/models/task.py +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja/models/team.py +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja/models/user.py +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja.egg-info/SOURCES.txt +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja.egg-info/dependency_links.txt +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja.egg-info/requires.txt +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/pyvikunja.egg-info/top_level.txt +0 -0
- {pyvikunja-0.6 → pyvikunja-0.8}/setup.cfg +0 -0
@@ -1,5 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
from typing import List, Dict, Any, Optional
|
3
|
+
from urllib.parse import urlparse, urlunparse
|
3
4
|
|
4
5
|
import httpx
|
5
6
|
|
@@ -15,6 +16,7 @@ logger = logging.getLogger(__name__)
|
|
15
16
|
|
16
17
|
class APIError(Exception):
|
17
18
|
"""Custom exception for API-related errors."""
|
19
|
+
|
18
20
|
def __init__(self, status_code: int, message: str):
|
19
21
|
super().__init__(f"HTTP {status_code}: {message}")
|
20
22
|
self.status_code = status_code
|
@@ -23,12 +25,43 @@ class APIError(Exception):
|
|
23
25
|
|
24
26
|
class VikunjaAPI:
|
25
27
|
def __init__(self, base_url: str, token: str):
|
26
|
-
self.
|
28
|
+
self.host = self._normalize_host(base_url)
|
29
|
+
self.api_base_url = self._normalize_api_base_url(self.host)
|
27
30
|
self.headers = {"Authorization": f"Bearer {token}"}
|
28
31
|
self.client = httpx.AsyncClient()
|
29
32
|
|
30
|
-
|
31
|
-
|
33
|
+
@property
|
34
|
+
def web_ui_link(self):
|
35
|
+
return self.host
|
36
|
+
|
37
|
+
def _normalize_host(self, url: str) -> str:
|
38
|
+
"""Ensures the host has a valid protocol and retains ports if provided."""
|
39
|
+
if "://" not in url:
|
40
|
+
url = f"https://{url}" # Default to HTTPS if no scheme provided
|
41
|
+
|
42
|
+
parsed = urlparse(url)
|
43
|
+
|
44
|
+
# Default to HTTPS if no scheme is provided
|
45
|
+
scheme = parsed.scheme if parsed.scheme else "https"
|
46
|
+
|
47
|
+
# Ensure netloc is correctly used (handles ports)
|
48
|
+
netloc = parsed.netloc if parsed.netloc else parsed.path # Handles cases where netloc is empty
|
49
|
+
|
50
|
+
# Rebuild the host URL
|
51
|
+
host = urlunparse((scheme, netloc, "", "", "", ""))
|
52
|
+
|
53
|
+
return host.rstrip("/")
|
54
|
+
|
55
|
+
def _normalize_api_base_url(self, host: str) -> str:
|
56
|
+
"""Ensures the API base URL includes /api/v1."""
|
57
|
+
if not host.endswith("/api/v1"):
|
58
|
+
return f"{host}/api/v1"
|
59
|
+
return host
|
60
|
+
|
61
|
+
|
62
|
+
async def _request(self, method: str, endpoint: str, params: Optional[Dict] = None, data: Optional[Dict] = None) -> \
|
63
|
+
Optional[Any]:
|
64
|
+
url = f"{self.api_base_url}{endpoint}"
|
32
65
|
try:
|
33
66
|
response = await self.client.request(method, url, headers=self.headers, params=params, json=data)
|
34
67
|
response.raise_for_status()
|
@@ -41,6 +74,22 @@ class VikunjaAPI:
|
|
41
74
|
logger.error(f"Unexpected error occurred: {e} | URL: {url}")
|
42
75
|
return None
|
43
76
|
|
77
|
+
async def ping(self) -> bool:
|
78
|
+
"""Tests if the API key is valid by calling the /projects endpoint."""
|
79
|
+
"""Not chosen the /user endpoint here because it was always returning 401 with an API Token"""
|
80
|
+
url = f"{self.api_base_url}/projects"
|
81
|
+
|
82
|
+
try:
|
83
|
+
response = await self.client.get(url, headers=self.headers, timeout=10)
|
84
|
+
response.raise_for_status()
|
85
|
+
|
86
|
+
if response.status_code == 200:
|
87
|
+
return True
|
88
|
+
else:
|
89
|
+
raise httpx.HTTPError(f"Non-200 Response from server {response.status_code}")
|
90
|
+
except httpx.HTTPError as e:
|
91
|
+
raise e
|
92
|
+
|
44
93
|
# Projects
|
45
94
|
async def get_projects(self, page: int = 1, per_page: int = 20) -> List[Project]:
|
46
95
|
response = await self._request("GET", "/projects", params={"page": page, "per_page": per_page})
|
@@ -114,5 +163,3 @@ class VikunjaAPI:
|
|
114
163
|
|
115
164
|
async def delete_team(self, team_id: int) -> Optional[Team]:
|
116
165
|
return await self._request("DELETE", f"/teams/{team_id}")
|
117
|
-
|
118
|
-
|
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
|