task-manager-cli-vedaanth 0.1.0__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.
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: task-manager-cli-vedaanth
3
+ Version: 0.1.0
4
+ Summary: Interactive terminal client for Task Manager
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: typer
7
+ Requires-Dist: rich
8
+ Requires-Dist: httpx
9
+ Requires-Dist: pydantic
10
+ Requires-Dist: platformdirs
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "task-manager-cli-vedaanth"
7
+ version = "0.1.0"
8
+ description = "Interactive terminal client for Task Manager"
9
+ requires-python = ">=3.10"
10
+ dependencies = [
11
+ "typer",
12
+ "rich",
13
+ "httpx",
14
+ "pydantic",
15
+ "platformdirs"
16
+ ]
17
+
18
+ [project.scripts]
19
+ task-manager = "task_manager.cli:app"
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .cli import app
2
+
3
+ app()
@@ -0,0 +1,123 @@
1
+ import httpx
2
+
3
+
4
+ BASE_URL = "https://task-manager-cli-eu90.onrender.com"
5
+
6
+
7
+ def register_user(username, email, password):
8
+ response = httpx.post(
9
+ f"{BASE_URL}/api/register/",
10
+ json={
11
+ "username": username,
12
+ "email": email,
13
+ "password": password,
14
+ },
15
+ )
16
+
17
+ return response
18
+
19
+
20
+ """
21
+ This file's job is:
22
+ CLI
23
+
24
+ api_client.py
25
+
26
+ HTTP request
27
+
28
+ Django API"""
29
+
30
+
31
+ def login_user(username, password):
32
+ response = httpx.post(
33
+ f"{BASE_URL}/api/token/",
34
+ json={
35
+ "username": username,
36
+ "password": password,
37
+ },
38
+ )
39
+
40
+ return response
41
+
42
+ def get_tasks(access_token):
43
+ response = httpx.get(
44
+ f"{BASE_URL}/api/tasks/",
45
+ headers={
46
+ "Authorization": f"Bearer {access_token}"
47
+ #That's how your CLI tells Django:
48
+ #"This request belongs to the user represented by this JWT."""
49
+ }
50
+ )
51
+
52
+ return response
53
+ def create_task(access_token, title, description=""):
54
+ response = httpx.post(
55
+ f"{BASE_URL}/api/tasks/",
56
+ headers={
57
+ "Authorization": f"Bearer {access_token}"
58
+ },
59
+ json={
60
+ "title": title,
61
+ "description": description,
62
+ },
63
+ )
64
+
65
+ return response
66
+ """The flow is:
67
+ CLI
68
+
69
+ create_task()
70
+
71
+ POST /api/tasks/
72
+
73
+ JWT → Django identifies request.user
74
+
75
+ serializer.save(owner=request.user)
76
+
77
+ Database"""
78
+ def complete_task(access_token, task_id):
79
+ response = httpx.patch(
80
+ f"{BASE_URL}/api/task/{task_id}/",
81
+ headers={
82
+ "Authorization": f"Bearer {access_token}"
83
+ },
84
+ json={
85
+ "done": True
86
+ },
87
+ )
88
+
89
+ return response
90
+
91
+ def update_task(access_token, task_id, title, description):
92
+ response = httpx.patch(
93
+ f"{BASE_URL}/api/task/{task_id}/",
94
+ headers={
95
+ "Authorization": f"Bearer {access_token}"
96
+ },
97
+ json={
98
+ "title": title,
99
+ "description": description
100
+ },
101
+ )
102
+
103
+ return response
104
+ def get_task(access_token, task_id):
105
+ response = httpx.get(
106
+ f"{BASE_URL}/api/task/{task_id}/",
107
+ headers={
108
+ "Authorization": f"Bearer {access_token}"
109
+ },
110
+ )
111
+
112
+ return response
113
+
114
+
115
+ def delete_task(access_token, task_id):
116
+ response = httpx.delete(
117
+ f"{BASE_URL}/api/task/{task_id}/",
118
+ headers={
119
+ "Authorization": f"Bearer {access_token}"
120
+ },
121
+ )
122
+
123
+ return response
@@ -0,0 +1,23 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+
5
+ TOKEN_FILE = Path.home() / ".task_manager_tokens.json"
6
+
7
+
8
+ def save_tokens(tokens):
9
+ with open(TOKEN_FILE, "w") as file:
10
+ json.dump(tokens, file)
11
+
12
+
13
+ def load_tokens():
14
+ if not TOKEN_FILE.exists():
15
+ return None
16
+
17
+ with open(TOKEN_FILE, "r") as file:
18
+ return json.load(file)
19
+
20
+
21
+ def logout():
22
+ if TOKEN_FILE.exists():
23
+ TOKEN_FILE.unlink()
@@ -0,0 +1,456 @@
1
+ from urllib import response
2
+
3
+ import typer
4
+ from rich.console import Console
5
+
6
+ from .api_client import (
7
+ get_tasks,
8
+ get_task,
9
+ create_task,
10
+ complete_task,
11
+ update_task,
12
+ delete_task,
13
+ login_user,
14
+ register_user,
15
+ )
16
+
17
+ from .auth import (
18
+ save_tokens,
19
+ load_tokens,
20
+ logout,
21
+ )
22
+
23
+ app = typer.Typer()
24
+ console = Console()
25
+
26
+
27
+ @app.callback(invoke_without_command=True)
28
+ def main():
29
+
30
+ console.print(
31
+ "\n[bold cyan]╭──────────────────────────────────────╮[/bold cyan]"
32
+ )
33
+ console.print(
34
+ "[bold cyan]│ TASK MANAGER CLI │[/bold cyan]"
35
+ )
36
+ console.print(
37
+ "[bold cyan]╰──────────────────────────────────────╯[/bold cyan]"
38
+ )
39
+ console.print("\nWelcome to Task Manager CLI!")
40
+ console.print("Type [bold]help[/bold] to see available commands.")
41
+ console.print("Type [bold]exit[/bold] to quit.\n")
42
+
43
+ while True:
44
+
45
+ command = typer.prompt("task")
46
+
47
+ # Exit the CLI
48
+ if command.lower() == "exit":
49
+
50
+ console.print("[yellow]Goodbye![/yellow]")
51
+ break
52
+
53
+ # Show available CLI commands
54
+ elif command.lower() == "help":
55
+
56
+ console.print("\n[bold cyan]AUTH[/bold cyan]")
57
+ console.print(" register - Create a new account")
58
+ console.print(" login - Login to your account")
59
+ console.print(" logout - Logout from your account")
60
+ console.print(" me - Show current user")
61
+
62
+ console.print("\n[bold cyan]TASKS[/bold cyan]")
63
+ console.print(" list - Show your tasks")
64
+ console.print(" get - Get a task by ID")
65
+ console.print(" create - Create a new task")
66
+ console.print(" update - Update a task")
67
+ console.print(" delete - Delete a task")
68
+ console.print(" done - Mark a task as completed")
69
+
70
+ console.print("\n[bold cyan]SYSTEM[/bold cyan]")
71
+ console.print(" help - Show this help")
72
+ console.print(" clear - Clear the terminal")
73
+ console.print(" exit - Exit the CLI\n")
74
+
75
+ # Clear the terminal
76
+ elif command.lower() == "clear":
77
+
78
+ console.clear()
79
+
80
+ # Register a new user account
81
+ elif command.lower() == "register":
82
+
83
+ """
84
+ Flow:
85
+ task: register
86
+
87
+ Ask username
88
+
89
+ Ask email
90
+
91
+ Ask password
92
+
93
+ api_client.py
94
+
95
+ POST /api/register/
96
+
97
+ Django creates the user
98
+ """
99
+
100
+ console.print("\n[bold cyan]Create Account[/bold cyan]")
101
+
102
+ username = typer.prompt("Username")
103
+ email = typer.prompt("Email")
104
+ password = typer.prompt(
105
+ "Password",
106
+ hide_input=True
107
+ )
108
+
109
+ response = register_user(
110
+ username,
111
+ email,
112
+ password
113
+ )
114
+
115
+ if response.status_code == 201:
116
+
117
+ console.print(
118
+ "[green]✓ Account created successfully![/green]"
119
+ )
120
+
121
+ else:
122
+
123
+ console.print(
124
+ f"[red]✗ Registration failed:[/red] "
125
+ f"{response.text}"
126
+ )
127
+
128
+ # Login and save JWT tokens locally
129
+ elif command.lower() == "login":
130
+
131
+ console.print("\n[bold cyan]Login[/bold cyan]")
132
+
133
+ username = typer.prompt("Username")
134
+ password = typer.prompt(
135
+ "Password",
136
+ hide_input=True
137
+ )
138
+
139
+ response = login_user(
140
+ username,
141
+ password
142
+ )
143
+
144
+ if response.status_code == 200:
145
+
146
+ tokens = response.json()
147
+
148
+ # Save access and refresh tokens locally
149
+ save_tokens(tokens)
150
+
151
+ console.print(
152
+ "[green]✓ Login successful![/green]"
153
+ )
154
+
155
+ else:
156
+
157
+ console.print(
158
+ f"[red]✗ Login failed:[/red] "
159
+ f"{response.text}"
160
+ )
161
+
162
+ # Retrieve and display the user's tasks
163
+ elif command.lower() == "list":
164
+
165
+ # Load the saved JWT tokens
166
+ tokens = load_tokens()
167
+
168
+ if not tokens:
169
+
170
+ console.print(
171
+ "[red]Please login first.[/red]"
172
+ )
173
+ continue
174
+
175
+ # Send the access token with the API request
176
+ response = get_tasks(
177
+ tokens["access"]
178
+ )
179
+
180
+ if response.status_code == 200:
181
+
182
+ tasks = response.json()
183
+
184
+ # Check whether the user has any tasks
185
+ if not tasks:
186
+
187
+ console.print(
188
+ "\n[yellow]You don't have any tasks yet.[/yellow]"
189
+ )
190
+ continue
191
+
192
+ console.print(
193
+ "\n[bold cyan]Your Tasks[/bold cyan]"
194
+ )
195
+ console.print(
196
+ "────────────────────────────────────────"
197
+ )
198
+
199
+ # Display every task
200
+ for task in tasks:
201
+
202
+ # Show ✓ for completed tasks
203
+ # Show ☐ for incomplete tasks
204
+ if task["done"]:
205
+ status_icon = "[green]✓[/green]"
206
+ else:
207
+ status_icon = "[yellow]☐[/yellow]"
208
+
209
+ console.print(
210
+ f"{status_icon} "
211
+ f"[bold]ID: {task['id']}[/bold]"
212
+ )
213
+
214
+ # Display the task title
215
+ console.print(
216
+ f" [bold]{task['title']}[/bold]"
217
+ )
218
+
219
+ # Display the task description
220
+ description = task.get(
221
+ "description",
222
+ ""
223
+ )
224
+
225
+ # Break long descriptions into readable lines
226
+ words = description.split()
227
+ lines = []
228
+ current_line = ""
229
+
230
+ for word in words:
231
+
232
+ if len(current_line) + len(word) + 1 <= 60:
233
+
234
+ current_line += word + " "
235
+
236
+ else:
237
+
238
+ lines.append(
239
+ current_line.strip()
240
+ )
241
+ current_line = word + " "
242
+
243
+ if current_line:
244
+
245
+ lines.append(
246
+ current_line.strip()
247
+ )
248
+
249
+ for line in lines:
250
+
251
+ console.print(
252
+ f" {line}"
253
+ )
254
+
255
+ console.print()
256
+
257
+ console.print(
258
+ "────────────────────────────────────────"
259
+ )
260
+
261
+ else:
262
+
263
+ console.print(
264
+ f"[red]Failed to get tasks:[/red] "
265
+ f"{response.text}"
266
+ )
267
+
268
+ # Create a new task
269
+ elif command.lower() == "create":
270
+
271
+ # Load the saved JWT tokens
272
+ tokens = load_tokens()
273
+
274
+ if not tokens:
275
+
276
+ console.print(
277
+ "[red]Please login first.[/red]"
278
+ )
279
+ continue
280
+
281
+ console.print(
282
+ "\n[bold cyan]Create Task[/bold cyan]"
283
+ )
284
+
285
+ title = typer.prompt("Title")
286
+
287
+ description = typer.prompt(
288
+ "Description",
289
+ default=""
290
+ )
291
+
292
+ # Send the new task to the Django API
293
+ response = create_task(
294
+ tokens["access"],
295
+ title,
296
+ description
297
+ )
298
+
299
+ if response.status_code == 201:
300
+
301
+ console.print(
302
+ "[green]✓ Task created successfully![/green]"
303
+ )
304
+
305
+ else:
306
+
307
+ console.print(
308
+ f"[red]✗ Failed to create task:[/red] "
309
+ f"{response.text}"
310
+ )
311
+
312
+ # Mark an existing task as completed
313
+ elif command.lower() == "done":
314
+
315
+ # Load the saved JWT tokens
316
+ tokens = load_tokens()
317
+
318
+ if not tokens:
319
+
320
+ console.print(
321
+ "[red]Please login first.[/red]"
322
+ )
323
+ continue
324
+
325
+ # Ask which task should be marked as completed
326
+ task_id = typer.prompt("Task ID")
327
+
328
+ # Send the completion request to the Django API
329
+ response = complete_task(
330
+ tokens["access"],
331
+ task_id
332
+ )
333
+
334
+ if response.status_code == 200:
335
+
336
+ console.print(
337
+ "[green]✓ Task marked as completed![/green]"
338
+ )
339
+
340
+ else:
341
+
342
+ console.print(
343
+ f"[red]✗ Failed to complete task:[/red] "
344
+ f"{response.text}"
345
+ )
346
+ elif command.lower() == "edit":
347
+ tokens = load_tokens()
348
+
349
+ if not tokens:
350
+ console.print("[red]Please login first.[/red]")
351
+ continue
352
+
353
+ console.print("\n[bold cyan]Edit Task[/bold cyan]")
354
+
355
+ task_id = typer.prompt("Task ID")
356
+ title = typer.prompt("New title")
357
+ description = typer.prompt("New description")
358
+
359
+ response = update_task(
360
+ tokens["access"],
361
+ task_id,
362
+ title,
363
+ description
364
+ )
365
+
366
+ if response.status_code == 200:
367
+ console.print("[green]✓ Task updated successfully![/green]")
368
+ else:
369
+ console.print(
370
+ f"[red]✗ Failed to update task:[/red] "
371
+ f"{response.text}"
372
+ )
373
+
374
+ # Handle commands that are not implemented or recognized
375
+ elif command.lower() == "get":
376
+ tokens = load_tokens()
377
+
378
+ if not tokens:
379
+ console.print("[red]Please login first.[/red]")
380
+ continue
381
+
382
+ task_id = typer.prompt("Task ID")
383
+
384
+ response = get_task(
385
+ tokens["access"],
386
+ task_id
387
+ )
388
+
389
+ if response.status_code == 200:
390
+ task = response.json()
391
+
392
+ status_icon = (
393
+ "[green]✓[/green]"
394
+ if task["done"]
395
+ else "[yellow]☐[/yellow]"
396
+ )
397
+
398
+ console.print(f"\n{status_icon} [bold]ID: {task['id']}[/bold]")
399
+ console.print(f" [bold]{task['title']}[/bold]")
400
+ console.print(f" {task.get('description', '')}")
401
+ else:
402
+ console.print(
403
+ f"[red]✗ Failed to get task:[/red] "
404
+ f"{response.text}"
405
+ )
406
+ elif command.lower() == "delete":
407
+ tokens = load_tokens()
408
+
409
+ if not tokens:
410
+ console.print("[red]Please login first.[/red]")
411
+ continue
412
+
413
+ task_id = typer.prompt("Task ID")
414
+
415
+ confirm = typer.confirm(
416
+ f"Are you sure you want to delete task {task_id}?"
417
+ )
418
+
419
+ if not confirm:
420
+ console.print("[yellow]Delete cancelled.[/yellow]")
421
+ continue
422
+
423
+ response = delete_task(
424
+ tokens["access"],
425
+ task_id
426
+ )
427
+
428
+ if response.status_code == 204:
429
+ console.print(
430
+ "[green]✓ Task deleted successfully![/green]"
431
+ )
432
+ else:
433
+ console.print(
434
+ f"[red]✗ Failed to delete task:[/red] "
435
+ f"{response.text}"
436
+ )
437
+ elif command.lower() == "logout":
438
+ tokens = load_tokens()
439
+
440
+ if not tokens:
441
+ console.print("[yellow]You are not logged in.[/yellow]")
442
+ continue
443
+
444
+ logout()
445
+
446
+ console.print("[green]✓ Logged out successfully![/green]")
447
+
448
+ else:
449
+
450
+ console.print(
451
+ f"[red]Unknown command:[/red] {command}"
452
+ )
453
+
454
+
455
+ if __name__ == "__main__":
456
+ app()
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: task-manager-cli-vedaanth
3
+ Version: 0.1.0
4
+ Summary: Interactive terminal client for Task Manager
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: typer
7
+ Requires-Dist: rich
8
+ Requires-Dist: httpx
9
+ Requires-Dist: pydantic
10
+ Requires-Dist: platformdirs
@@ -0,0 +1,12 @@
1
+ pyproject.toml
2
+ src/task_manager/__init__.py
3
+ src/task_manager/__main__.py
4
+ src/task_manager/api_client.py
5
+ src/task_manager/auth.py
6
+ src/task_manager/cli.py
7
+ src/task_manager_cli_vedaanth.egg-info/PKG-INFO
8
+ src/task_manager_cli_vedaanth.egg-info/SOURCES.txt
9
+ src/task_manager_cli_vedaanth.egg-info/dependency_links.txt
10
+ src/task_manager_cli_vedaanth.egg-info/entry_points.txt
11
+ src/task_manager_cli_vedaanth.egg-info/requires.txt
12
+ src/task_manager_cli_vedaanth.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ task-manager = task_manager.cli:app
@@ -0,0 +1,5 @@
1
+ typer
2
+ rich
3
+ httpx
4
+ pydantic
5
+ platformdirs