Skip to content

Commit 3c9aa88

Browse files
committed
Rebase
Signed-off-by: Alina Buzachis <[email protected]>
1 parent 88ae44b commit 3c9aa88

15 files changed

+170
-147
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -88,26 +88,7 @@ The application can be reached in your browser at `https://localhost:8000/`. The
8888

8989
### Development Configuration
9090

91-
The application uses dynaconf to manage configuration. Default development settings are defined in `development_defaults.py`. These include:
92-
93-
#### Database
94-
95-
By default, the application uses a local SQLite database:
96-
97-
```bash
98-
# Default DB path
99-
default_path = BASE_DIR / "db.sqlite3"
100-
101-
# Use environment variable if set, else default
102-
env_path = os.getenv("SQLITE_PATH")
103-
db_path = Path(env_path) if env_path else default_path
104-
```
105-
106-
You can override the path by setting the `SQLITE_PATH` environment variable.
107-
108-
#### Ansible Automation Platform (AAP) Service Configuration
109-
110-
Default configuration values for connecting to the AAP service are defined in `development_defaults.py`:
91+
Default configuration values for connecting to the Ansible Automation Platform (AAP) service are defined in `development_defaults.py`:
11192

11293
```bash
11394
AAP_URL = "http://localhost:44926" # Base URL of your AAP instance

Dockerfile.dev

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Generated by Django 4.2.23 on 2025-08-05 19:21
2+
3+
from django.db import migrations
4+
from django.db import models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("core", "0003_task"),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name="task",
16+
name="updated_at",
17+
field=models.DateTimeField(auto_now=True),
18+
),
19+
migrations.AlterField(
20+
model_name="task",
21+
name="status",
22+
field=models.CharField(
23+
choices=[
24+
("Initiated", "Initiated"),
25+
("Running", "Running"),
26+
("Completed", "Completed"),
27+
("Failed", "Failed"),
28+
],
29+
db_index=True,
30+
default="Initiated",
31+
max_length=20,
32+
),
33+
),
34+
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated by Django 4.2.23 on 2025-08-05 19:41
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("core", "0004_task_updated_at_alter_task_status"),
10+
]
11+
12+
operations = [
13+
migrations.RemoveField(
14+
model_name="task",
15+
name="updated_at",
16+
),
17+
]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 4.2.23 on 2025-08-05 19:42
2+
3+
from django.db import migrations
4+
from django.db import models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("core", "0005_remove_task_updated_at"),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name="task",
16+
name="updated_at",
17+
field=models.DateTimeField(auto_now=True),
18+
),
19+
]

core/models.py

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
from __future__ import annotations
22

3+
from typing import Any
4+
from typing import Dict
5+
from typing import Optional
6+
37
from ansible_base.lib.abstract_models import CommonModel
48
from django.db import models
59

@@ -85,17 +89,57 @@ class Meta:
8589
app_label = "core"
8690
ordering = ["id"]
8791

88-
status_choices = (
89-
("Initiated", "Initiated"),
90-
("Running", "Running"),
91-
("Completed", "Completed"),
92-
("Failed", "Failed"),
93-
)
92+
class Status(models.TextChoices):
93+
INITIATED = "Initiated"
94+
RUNNING = "Running"
95+
COMPLETED = "Completed"
96+
FAILED = "Failed"
9497

95-
status: models.CharField = models.CharField(max_length=20, choices=status_choices)
98+
status: models.CharField = models.CharField(
99+
max_length=20, choices=Status.choices, default=Status.INITIATED, db_index=True
100+
)
96101
details: models.JSONField = models.JSONField(null=True, blank=True)
102+
updated_at: models.DateTimeField = models.DateTimeField(auto_now=True)
103+
104+
def set_status(
105+
self,
106+
new_status: str,
107+
details: Optional[Dict[str, Any]] = None,
108+
save_immediately: bool = True,
109+
) -> None:
110+
"""
111+
Safely update the task's status and optional details.
112+
113+
Args:
114+
new_status (str): The new status (must be one of Status.choices).
115+
details (dict, optional): Additional info about this status update.
116+
save_immediately (bool): If True, saves the instance to the database
117+
immediately.
118+
119+
Raises:
120+
ValueError: If the provided status is invalid.
121+
"""
122+
if new_status not in self.Status.values:
123+
raise ValueError(
124+
f"Invalid status '{new_status}'. Allowed values: {self.Status.values}"
125+
)
126+
127+
self.status = new_status
128+
self.details = details or {}
129+
if save_immediately:
130+
self.save(update_fields=["status", "details", "updated_at"])
131+
132+
def mark_initiated(self, details: Optional[Dict[str, Any]] = None) -> None:
133+
self.set_status(self.Status.INITIATED, details)
134+
135+
def mark_running(self, details: Optional[Dict[str, Any]] = None) -> None:
136+
self.set_status(self.Status.RUNNING, details)
137+
138+
def mark_completed(self, details: Optional[Dict[str, Any]] = None) -> None:
139+
self.set_status(self.Status.COMPLETED, details)
140+
141+
def mark_failed(self, details: Optional[Dict[str, Any]] = None) -> None:
142+
self.set_status(self.Status.FAILED, details)
97143

98-
def update_task_status(self, status_: str, details: dict) -> None:
99-
self.status = status_
100-
self.details = details
101-
self.save()
144+
def __str__(self) -> str:
145+
return f"Task #{self.pk} - {self.status}"

core/tasks.py renamed to core/task_runner.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,20 @@
1313
def run_pattern_task(pattern_id: int, task_id: int) -> None:
1414
"""
1515
Orchestrates downloading a collection and saving a pattern definition.
16+
17+
Args:
18+
pattern_id (int): The ID of the pattern to process.
19+
task_id (int): The ID of the task..
20+
21+
Raises:
22+
FileNotFoundError: If the pattern definition is not found.
23+
Exception: If any other error occurs.
1624
"""
1725
task = Task.objects.get(id=task_id)
26+
task.mark_initiated({"info": "Processing started"})
1827
try:
1928
pattern = Pattern.objects.get(id=pattern_id)
20-
task.update_task_status("Running", {"info": "Processing pattern"})
29+
task.mark_running({"info": "Processing pattern"})
2130
with download_collection(
2231
pattern.collection_name, pattern.collection_version
2332
) as collection_path:
@@ -37,10 +46,10 @@ def run_pattern_task(pattern_id: int, task_id: int) -> None:
3746
pattern.collection_name, pattern.collection_version
3847
)
3948
pattern.save(update_fields=["pattern_definition", "collection_version_uri"])
40-
task.update_task_status("Completed", {"info": "Pattern processed successfully"})
49+
task.mark_completed({"info": "Pattern processed successfully"})
4150
except FileNotFoundError:
4251
logger.error(f"Could not find pattern definition for task {task_id}")
43-
task.update_task_status("Failed", {"error": "Pattern definition not found."})
52+
task.mark_failed({"error": "Pattern definition not found."})
4453
except Exception as e:
4554
logger.error(f"Task {task_id} failed: {e}")
46-
task.update_task_status("Failed", {"error": str(e)})
55+
task.mark_failed({"error": str(e)})

core/tests/test_tasks.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from core.models import Pattern
1313
from core.models import PatternInstance
1414
from core.models import Task
15-
from core.tasks import run_pattern_task
15+
from core.task_runner import run_pattern_task
1616

1717

1818
class SharedDataMixin:
@@ -78,9 +78,11 @@ def tearDown(self):
7878

7979

8080
class PatternTaskTest(SharedDataMixin, TestCase):
81-
@patch("core.models.Task.update_task_status", autospec=True)
82-
@patch("core.tasks.open", new_callable=mock_open, read_data='{"name": "test"}')
83-
@patch("core.tasks.download_collection")
81+
@patch("core.models.Task.set_status", autospec=True)
82+
@patch(
83+
"core.task_runner.open", new_callable=mock_open, read_data='{"name": "test"}'
84+
)
85+
@patch("core.task_runner.download_collection")
8486
def test_run_pattern_task_success(
8587
self, mock_download, mock_open_fn, mock_update_status
8688
):
@@ -118,8 +120,8 @@ def test_run_pattern_task_success(
118120
task, "Completed", {"info": "Pattern processed successfully"}
119121
)
120122

121-
@patch("core.models.Task.update_task_status", autospec=True)
122-
@patch("core.tasks.download_collection", side_effect=FileNotFoundError)
123+
@patch("core.models.Task.set_status", autospec=True)
124+
@patch("core.task_runner.download_collection", side_effect=FileNotFoundError)
123125
def test_run_pattern_task_file_not_found(self, mock_download, mock_update_status):
124126
pattern = Pattern.objects.create(
125127
collection_name="demo.collection",
@@ -134,15 +136,17 @@ def test_run_pattern_task_file_not_found(self, mock_download, mock_update_status
134136
task, "Failed", {"error": "Pattern definition not found."}
135137
)
136138

137-
@patch("core.tasks.download_collection", side_effect=Exception("Download failed"))
139+
@patch(
140+
"core.task_runner.download_collection", side_effect=Exception("Download failed")
141+
)
138142
def test_run_pattern_task_handles_download_failure(self, mock_download):
139143
run_pattern_task(self.pattern.id, self.task.id)
140144
self.task.refresh_from_db()
141145
self.assertEqual(self.task.status, "Failed")
142146
self.assertIn("Download failed", self.task.details.get("error", ""))
143147

144-
@patch("core.models.Task.update_task_status", autospec=True)
145-
@patch("core.tasks.download_collection")
148+
@patch("core.models.Task.set_status", autospec=True)
149+
@patch("core.task_runner.download_collection")
146150
def test_full_status_update_flow(self, mock_download, mock_update_status):
147151
temp_dir_path = self.create_temp_collection_dir()
148152
mock_download.return_value.__enter__.return_value = temp_dir_path

pattern_service/settings/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
except ImportError:
1515
pass
1616

17-
# Ensure default environment mode
1817
os.environ["PATTERN_SERVICE_MODE"] = os.environ.get(
1918
"PATTERN_SERVICE_MODE", "production"
2019
)

pattern_service/settings/defaults.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,3 @@
133133
},
134134
"publish": {"default_control_broker": "socket", "default_broker": "pg_notify"},
135135
}
136-
137-
# Base URL of your AAP service
138-
URL = "http://localhost:44926" # or your default URL
139-
140-
# Whether to verify SSL certificates (True or False)
141-
VALIDATE_CERTS = False
142-
143-
# Default username and password for authentication
144-
USERNAME = "admin"
145-
PASSWORD = "your_default_password_here"

0 commit comments

Comments
 (0)