pyvikunja 0.16__py3-none-any.whl → 0.18__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.
pyvikunja/api.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from functools import cached_property
2
3
  from typing import List, Dict, Any, Optional
3
4
  from urllib.parse import urlparse, urlunparse
4
5
 
@@ -29,7 +30,11 @@ class VikunjaAPI:
29
30
  self.api_base_url = self._normalize_api_base_url(self.host)
30
31
  self.headers = {"Authorization": f"Bearer {token}"}
31
32
  self.strict_ssl = strict_ssl
32
- self.client = httpx.AsyncClient(verify=strict_ssl)
33
+
34
+ @cached_property
35
+ def client(self) -> httpx.AsyncClient:
36
+ """Lazily instantiate the HTTP client when first accessed."""
37
+ return httpx.AsyncClient(verify=self.strict_ssl)
33
38
 
34
39
  @property
35
40
  def web_ui_link(self):
@@ -59,14 +64,18 @@ class VikunjaAPI:
59
64
  return f"{host}/api/v1"
60
65
  return host
61
66
 
62
-
63
67
  async def _request(self, method: str, endpoint: str, params: Optional[Dict] = None, data: Optional[Dict] = None) -> \
64
- Optional[Any]:
68
+ Optional[Dict[str, Any]]:
65
69
  url = f"{self.api_base_url}{endpoint}"
66
70
  try:
67
71
  response = await self.client.request(method, url, headers=self.headers, params=params, json=data)
68
72
  response.raise_for_status()
69
- return response.json()
73
+
74
+ # Return JSON data and headers
75
+ return {
76
+ "data": response.json(),
77
+ "headers": response.headers
78
+ }
70
79
  except httpx.HTTPStatusError as e:
71
80
  logger.error(f"HTTP error occurred: {e.response.status_code} | {e.response.text} | URL: {url}")
72
81
  except httpx.RequestError as e:
@@ -91,76 +100,105 @@ class VikunjaAPI:
91
100
  except httpx.HTTPError as e:
92
101
  raise e
93
102
 
103
+ async def get_paginated_data(self, endpoint: str) -> List[Dict[str, Any]]:
104
+ all_data = []
105
+ page = 1
106
+ per_page = 20
107
+
108
+ while True:
109
+ response = await self._request("GET", endpoint, params={"page": page, "per_page": per_page})
110
+ if not response:
111
+ break
112
+
113
+ all_data.extend(response["data"])
114
+ total_pages = int(response["headers"].get("x-pagination-total-pages", 1))
115
+ if page >= total_pages:
116
+ break
117
+
118
+ page += 1
119
+
120
+ return all_data
121
+
94
122
  # Projects
95
- async def get_projects(self, page: int = 1, per_page: int = 20) -> List[Project]:
96
- response = await self._request("GET", "/projects", params={"page": page, "per_page": per_page})
97
- return [Project(self, project_data) for project_data in response or []]
123
+ async def get_projects(self) -> List[Project]:
124
+ data = await self.get_paginated_data("/projects")
125
+ return [Project(self, project) for project in data]
98
126
 
99
127
  async def get_project(self, project_id: int) -> Optional[Project]:
100
128
  response = await self._request("GET", f"/projects/{project_id}")
101
- return Project(self, response)
129
+ return Project(self, response['data'])
102
130
 
103
131
  async def create_project(self, project: Dict) -> Optional[Dict]:
104
- return await self._request("PUT", "/projects", data=project)
132
+ result = await self._request("PUT", "/projects", data=project)
133
+ return result['data']
105
134
 
106
135
  async def update_project(self, project_id: int, project: Dict) -> Optional[Dict]:
107
- return await self._request("POST", f"/projects/{project_id}", data=project)
136
+ result = await self._request("POST", f"/projects/{project_id}", data=project)
137
+ return result['data']
108
138
 
109
139
  async def delete_project(self, project_id: int) -> Optional[Dict]:
110
- return await self._request("DELETE", f"/projects/{project_id}")
140
+ result = await self._request("DELETE", f"/projects/{project_id}")
141
+ return result['data']
111
142
 
112
143
  # Tasks
113
- async def get_tasks(self, project_id: int, page: int = 1, per_page: int = 20) -> List[Task]:
114
- response = await self._request("GET", f"/projects/{project_id}/tasks",
115
- params={"page": page, "per_page": per_page})
144
+ async def get_tasks(self, project_id: int) -> List[Task]:
145
+ response = await self.get_paginated_data(f"/projects/{project_id}/tasks")
116
146
  return [Task(self, task_data) for task_data in response or []]
117
147
 
118
148
  async def get_task(self, task_id: int) -> Task:
119
149
  data = await self._request("GET", f"/tasks/{task_id}")
120
- return Task(self, data)
150
+ return Task(self, data['data'])
121
151
 
122
152
  async def create_task(self, project_id: int, task: Dict) -> Optional[Dict]:
123
- return await self._request("PUT", f"/projects/{project_id}/tasks", data=task)
153
+ result = await self._request("PUT", f"/projects/{project_id}/tasks", data=task)
154
+ return result['data']
124
155
 
125
156
  async def update_task(self, task_id: int, task: Dict) -> Optional[Dict]:
126
- return await self._request("POST", f"/tasks/{task_id}", data=task)
157
+ result = await self._request("POST", f"/tasks/{task_id}", data=task)
158
+ return result['data']
127
159
 
128
160
  async def delete_task(self, task_id: int) -> Optional[Dict]:
129
- return await self._request("DELETE", f"/tasks/{task_id}")
161
+ result = await self._request("DELETE", f"/tasks/{task_id}")
162
+ return result['data']
130
163
 
131
164
  # Labels
132
- async def get_labels(self, page: int = 1, per_page: int = 20) -> List[Label]:
133
- response = await self._request("GET", "/labels", params={"page": page, "per_page": per_page})
165
+ async def get_labels(self) -> List[Label]:
166
+ response = await self.get_paginated_data("/labels")
134
167
  return [Label(label_data) for label_data in response or []]
135
168
 
136
169
  async def get_label(self, label_id: int) -> Optional[Dict]:
137
- return await self._request("GET", f"/labels/{label_id}")
170
+ result = await self._request("GET", f"/labels/{label_id}")
171
+ return result['data']
138
172
 
139
173
  async def create_label(self, label: Dict) -> Optional[Dict]:
140
- return await self._request("PUT", "/labels", data=label)
174
+ result = await self._request("PUT", "/labels", data=label)
175
+ return result['data']
141
176
 
142
177
  async def update_label(self, label_id: int, label: Dict) -> Optional[Dict]:
143
- return await self._request("PUT", f"/labels/{label_id}", data=label)
178
+ result = await self._request("PUT", f"/labels/{label_id}", data=label)
179
+ return result['data']
144
180
 
145
181
  async def delete_label(self, label_id: int) -> Optional[Dict]:
146
- return await self._request("DELETE", f"/labels/{label_id}")
182
+ result = await self._request("DELETE", f"/labels/{label_id}")
183
+ return result['data']
147
184
 
148
185
  # Teams
149
- async def get_teams(self, page: int = 1, per_page: int = 20) -> List[Team]:
150
- response = await self._request("GET", "/teams", params={"page": page, "per_page": per_page})
186
+ async def get_teams(self) -> List[Team]:
187
+ response = await self.get_paginated_data("/teams")
151
188
  return [Team(self, team_data) for team_data in response or []]
152
189
 
153
190
  async def get_team(self, team_id: int) -> Optional[Team]:
154
191
  response = await self._request("GET", f"/teams/{team_id}")
155
- return Team(self, response)
192
+ return Team(self, response['data'])
156
193
 
157
194
  async def create_team(self, team: Dict) -> Optional[Team]:
158
195
  response = await self._request("PUT", "/teams", data=team)
159
- return Team(self, response)
196
+ return Team(self, response['data'])
160
197
 
161
198
  async def update_team(self, team_id: int, team: Dict) -> Optional[Team]:
162
199
  response = await self._request("POST", f"/teams/{team_id}", data=team)
163
- return Team(self, response)
200
+ return Team(self, response['data'])
164
201
 
165
202
  async def delete_team(self, team_id: int) -> Optional[Team]:
166
- return await self._request("DELETE", f"/teams/{team_id}")
203
+ result = await self._request("DELETE", f"/teams/{team_id}")
204
+ return result['data']
@@ -15,8 +15,8 @@ class Project(BaseModel):
15
15
  self.hex_color: Optional[str] = data.get('hex_color')
16
16
  self.owner: 'User' = User(data.get('owner') or {})
17
17
 
18
- async def get_tasks(self, page: int = 1, per_page: int = 20) -> List['Task']:
19
- return await self.api.get_tasks(self.id, page, per_page)
18
+ async def get_tasks(self) -> List['Task']:
19
+ return await self.api.get_tasks(self.id)
20
20
 
21
21
  async def create_task(self, task: Dict) -> 'Task':
22
22
  from pyvikunja.models.task import Task
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: pyvikunja
3
- Version: 0.16
3
+ Version: 0.18
4
4
  Summary: A Python wrapper for Vikunja API
5
5
  Author: Joseph Shufflebotham
6
6
  License: AGPL
7
7
  License-File: LICENSE
8
8
  Requires-Dist: httpx==0.28.1
9
+ Dynamic: license-file
@@ -1,15 +1,15 @@
1
1
  pyvikunja/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- pyvikunja/api.py,sha256=P5yQBHmnWaM2OORwkk0Q-1K2eLIqFzwdtnjiEK8BD6o,6999
2
+ pyvikunja/api.py,sha256=30sosl6wNWT6nTa7swLFSv9q9G9AlQ6Rq_FGDH7mLBs,7887
3
3
  pyvikunja/models/label.py,sha256=cOOuVYDVDMmK96Ev22EDgWwXljkAonQNL4XsTMay4K4,467
4
4
  pyvikunja/models/models.py,sha256=VmnBKs_DhcF1CNE3b2MhH4harwZo1697cMhTXetLGWk,822
5
- pyvikunja/models/project.py,sha256=viVIe3lVgbh7k05ADDds1cwzvC1vf4tT2vSNjbB7KMk,1179
5
+ pyvikunja/models/project.py,sha256=wFtnyuLTteDUQrUWeImR7DXTI0XYhCSiQGlmwaIzdwM,1128
6
6
  pyvikunja/models/task.py,sha256=8KUoN0SuTRkJq_cHdsuIeQHUiNgOiWCLcWFVN9NJH5c,7806
7
7
  pyvikunja/models/team.py,sha256=0Z3828Cm3nNuNr1z2on63fLYAVW325_SQKi5RmqXf-I,559
8
8
  pyvikunja/models/user.py,sha256=36duFNyGXKzlwqO0d6FA-C1KTCci2sIMU1IBIF-N_LM,310
9
9
  pyvikunja/models/enum/repeat_mode.py,sha256=xkDPfYeRz342ovqro1UVBAoegpPgpZQYTnyo2sf6R7I,112
10
10
  pyvikunja/models/enum/task_priority.py,sha256=aVz1HEofIqwUkXG0vYm58n_vLNbgqtbvHuQ5K-YQqA0,119
11
- pyvikunja-0.16.dist-info/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
12
- pyvikunja-0.16.dist-info/METADATA,sha256=3euyTCR3vuG3-bv1QDux2x68eGDiZKyYkb90I1HeNnI,188
13
- pyvikunja-0.16.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
14
- pyvikunja-0.16.dist-info/top_level.txt,sha256=WVV9zgxUBuWOkUY1t_U7zI0paWWTVelKYB4vjsiKsks,10
15
- pyvikunja-0.16.dist-info/RECORD,,
11
+ pyvikunja-0.18.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
12
+ pyvikunja-0.18.dist-info/METADATA,sha256=SvUsJ9e0YkFFDAcrVBM37n9ivnciqJdOjTQo5mtWy3s,210
13
+ pyvikunja-0.18.dist-info/WHEEL,sha256=L0N565qmK-3nM2eBoMNFszYJ_MTx03_tQ0CQu1bHLYo,91
14
+ pyvikunja-0.18.dist-info/top_level.txt,sha256=WVV9zgxUBuWOkUY1t_U7zI0paWWTVelKYB4vjsiKsks,10
15
+ pyvikunja-0.18.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (78.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5