Skip to content

Commit c9d3397

Browse files
committed
redis replaced postgres in planning module
1 parent ca0af87 commit c9d3397

14 files changed

Lines changed: 187 additions & 69 deletions

poetry.lock

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ fastapi = {version = "*", extras = ["all"]}
1515
pydantic = "*"
1616
lagom = "*"
1717
psycopg2-binary = "*"
18+
redis = "*"
1819

1920
[tool.poetry.group.dev.dependencies]
2021
ruff = "*"

smartschedule/container.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from lagom import Container
2+
from redis import Redis
23

34
from smartschedule.allocation.cashflow.cashflow_repository import CashflowRepository
45
from smartschedule.allocation.cashflow.sqlalchemy_cashflow_repository import (
@@ -11,8 +12,8 @@
1112
SqlAlchemyProjectAllocationsRepository,
1213
)
1314
from smartschedule.planning.project_repository import ProjectRepository
14-
from smartschedule.planning.sqlalchemy_project_repository import (
15-
SqlAlchemyProjectRepository,
15+
from smartschedule.planning.redis_project_repository import (
16+
RedisProjectRepository,
1617
)
1718
from smartschedule.shared.event_bus import EventBus, SyncExecutor
1819
from smartschedule.shared.events_publisher import EventsPublisher
@@ -24,6 +25,6 @@ def build() -> Container:
2425
container[EventsPublisher] = lambda c: EventBus(c, executor) # type: ignore[type-abstract]
2526
container[EventBus] = lambda c: EventBus(c, executor)
2627
container[CashflowRepository] = SqlAlchemyCashflowRepository # type: ignore[type-abstract]
27-
container[ProjectRepository] = SqlAlchemyProjectRepository # type: ignore[type-abstract]
28+
container[ProjectRepository] = lambda c: RedisProjectRepository(c[Redis]) # type: ignore[type-abstract]
2829
container[ProjectAllocationsRepository] = SqlAlchemyProjectAllocationsRepository # type: ignore[type-abstract]
2930
return container

smartschedule/planning/plan_chosen_resources.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def define_resources_within_dates(
3737
event = NeededResourcesChosen(
3838
project_id, resources, time_boundaries, datetime.now()
3939
)
40+
self._project_repository.save(project)
4041
self._events_publisher.publish(event)
4142

4243
def adjust_stages_to_resource_availability(
@@ -54,6 +55,7 @@ def adjust_stages_to_resource_availability(
5455
needed_resources_calendars, *stages
5556
)
5657
project.add_schedule(schedule)
58+
self._project_repository.save(project)
5759

5860
def _create_schedule_adjusting_to_calendars(
5961
self, needed_resources_calendars: Calendars, *stages: Stage

smartschedule/planning/planning_facade.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,20 @@ def __init__(
4141
def add_new_project(self, name: str, *stages: Stage) -> ProjectId:
4242
parallelized_stages = self._stage_parallelization.of(set(stages))
4343
project = Project(name, parallelized_stages)
44-
self._project_repository.add(project)
44+
self._project_repository.save(project)
4545
return project.id
4646

4747
def add_new_project_with_parallelized_stages(
4848
self, name: str, parallelized_stages: ParallelStagesList
4949
) -> ProjectId:
5050
project = Project(name, parallelized_stages)
51-
self._project_repository.add(project)
51+
self._project_repository.save(project)
5252
return project.id
5353

5454
def add_demands(self, project_id: ProjectId, demands: Demands) -> None:
5555
project = self._project_repository.get(id=project_id)
5656
project.add_demands(demands)
57+
self._project_repository.save(project)
5758
event = CapabilitiesDemanded(project_id, project.all_demands, datetime.now())
5859
self._events_publisher.publish(event)
5960

@@ -62,6 +63,7 @@ def define_demands_per_stage(
6263
) -> None:
6364
project = self._project_repository.get(id=project_id)
6465
project.add_demands_per_stage(demands_per_stage)
66+
self._project_repository.save(project)
6567
event = CapabilitiesDemanded(project_id, project.all_demands, datetime.now())
6668
self._events_publisher.publish(event)
6769

@@ -79,14 +81,17 @@ def define_project_stages(self, project_id: ProjectId, *stages: Stage) -> None:
7981
project = self._project_repository.get(id=project_id)
8082
parallelized_stages = self._stage_parallelization.of(set(stages))
8183
project.parallelized_stages = parallelized_stages
84+
self._project_repository.save(project)
8285

8386
def define_start_date(self, project_id: ProjectId, start_date: date) -> None:
8487
project = self._project_repository.get(id=project_id)
8588
project.add_schedule_by_start_date(start_date)
89+
self._project_repository.save(project)
8690

8791
def define_manual_schedule(self, project_id: ProjectId, schedule: Schedule) -> None:
8892
project = self._project_repository.get(id=project_id)
8993
project.add_schedule(schedule)
94+
self._project_repository.save(project)
9095

9196
def adjust_stages_to_resource_availability(
9297
self, project_id: ProjectId, time_boundaries: TimeSlot, *stages: Stage
@@ -104,6 +109,7 @@ def plan_critical_stage_with_resource(
104109
) -> None:
105110
project = self._project_repository.get(id=project_id)
106111
project.add_schedule_by_critical_stage(critical_stage, stage_time_slot)
112+
self._project_repository.save(project)
107113
event = CriticalStagePlanned(
108114
project_id, stage_time_slot, resource_id, datetime.now()
109115
)
@@ -114,6 +120,7 @@ def plan_critical_stage(
114120
) -> None:
115121
project = self._project_repository.get(id=project_id)
116122
project.add_schedule_by_critical_stage(critical_stage, stage_time_slot)
123+
self._project_repository.save(project)
117124
event = CriticalStagePlanned(project_id, stage_time_slot, None, datetime.now())
118125
self._events_publisher.publish(event)
119126

smartschedule/planning/project.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1+
from dataclasses import dataclass
12
from datetime import date
23

3-
from sqlalchemy.orm import Mapped, mapped_column
4-
54
from smartschedule.planning.chosen_resources import ChosenResources
65
from smartschedule.planning.demands import Demands
76
from smartschedule.planning.demands_per_stage import DemandsPerStage
@@ -11,26 +10,18 @@
1110
from smartschedule.planning.parallelization.stage import Stage
1211
from smartschedule.planning.project_id import ProjectId
1312
from smartschedule.planning.schedule.schedule import Schedule
14-
from smartschedule.shared.sqlalchemy_extensions import AsJSON, EmbeddedUUID, registry
1513
from smartschedule.shared.timeslot.time_slot import TimeSlot
1614

1715

18-
@registry.mapped_as_dataclass(init=False)
16+
@dataclass
1917
class Project:
20-
__tablename__ = "projects"
21-
22-
id: Mapped[ProjectId] = mapped_column(EmbeddedUUID[ProjectId], primary_key=True)
23-
_version: Mapped[int] = mapped_column(name="version")
24-
name: Mapped[str]
25-
parallelized_stages: Mapped[ParallelStagesList] = mapped_column(
26-
AsJSON[ParallelStagesList]
27-
)
28-
demands_per_stage: Mapped[DemandsPerStage] = mapped_column(AsJSON[DemandsPerStage])
29-
all_demands: Mapped[Demands] = mapped_column(AsJSON[Demands])
30-
chosen_resources: Mapped[ChosenResources] = mapped_column(AsJSON[ChosenResources])
31-
schedule: Mapped[Schedule] = mapped_column(AsJSON[Schedule])
32-
33-
__mapper_args__ = {"version_id_col": _version}
18+
id: ProjectId
19+
name: str
20+
parallelized_stages: ParallelStagesList
21+
demands_per_stage: DemandsPerStage
22+
all_demands: Demands
23+
chosen_resources: ChosenResources
24+
schedule: Schedule
3425

3526
def __init__(self, name: str, parallelized_stages: ParallelStagesList) -> None:
3627
self.id = ProjectId.new_one()

smartschedule/planning/project_id.py

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,16 @@
11
from __future__ import annotations
22

3+
from dataclasses import dataclass
34
from uuid import UUID, uuid4
45

56

7+
@dataclass(frozen=True)
68
class ProjectId:
7-
def __init__(self, uuid: UUID) -> None:
8-
self._project_id = uuid
9+
uuid: UUID
910

1011
@property
1112
def id(self) -> UUID:
12-
return self._project_id
13-
14-
def __eq__(self, other: object) -> bool:
15-
if not isinstance(other, ProjectId):
16-
return False
17-
return self._project_id == other._project_id
18-
19-
def __hash__(self) -> int:
20-
return hash(self._project_id)
21-
22-
def __repr__(self) -> str:
23-
return f"ProjectId(UUID(hex='{self._project_id}'))"
24-
25-
def __str__(self) -> str:
26-
return str(self._project_id)
13+
return self.uuid
2714

2815
@staticmethod
2916
def new_one() -> ProjectId:

smartschedule/planning/project_repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ def get_all(self, ids: list[ProjectId] | None = None) -> Sequence[Project]:
1515
pass
1616

1717
@abc.abstractmethod
18-
def add(self, model: Project) -> None:
18+
def save(self, model: Project) -> None:
1919
pass
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import json
2+
from typing import Final, Sequence, cast
3+
4+
from pydantic import TypeAdapter
5+
from redis import Redis
6+
7+
from smartschedule.planning.project import Project
8+
from smartschedule.planning.project_id import ProjectId
9+
from smartschedule.planning.project_repository import ProjectRepository
10+
from smartschedule.shared.repository import NotFound
11+
12+
13+
class RedisProjectRepository(ProjectRepository):
14+
HASH_NAME: Final = "projects"
15+
16+
def __init__(self, client: Redis) -> None:
17+
self._client = client
18+
self._type_adapter = TypeAdapter[Project](Project)
19+
20+
def get(self, id: ProjectId) -> Project:
21+
if data := self._client.hget(self.HASH_NAME, self._id_to_key(id)):
22+
as_json = json.loads(cast(str, data))
23+
return self._type_adapter.validate_python(as_json)
24+
raise NotFound
25+
26+
def get_all(self, ids: list[ProjectId] | None = None) -> Sequence[Project]:
27+
if ids is None:
28+
hash_contents = cast(dict[str, str], self._client.hgetall(self.HASH_NAME))
29+
raw_projects = list(hash_contents.values())
30+
else:
31+
keys = [self._id_to_key(id) for id in ids]
32+
raw_projects = cast(list[str], self._client.hmget(self.HASH_NAME, keys))
33+
return [
34+
self._type_adapter.validate_python(json.loads(data))
35+
for data in raw_projects
36+
if data
37+
]
38+
39+
def save(self, model: Project) -> None:
40+
key = self._id_to_key(model.id)
41+
as_dict = self._type_adapter.dump_python(model, mode="json")
42+
as_json = json.dumps(as_dict)
43+
self._client.hset(self.HASH_NAME, key, as_json)
44+
45+
def _id_to_key(self, id: ProjectId) -> str:
46+
return id.id.hex

smartschedule/planning/sqlalchemy_project_repository.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)