Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/3875-serialize-new-otconfig #509

Merged
merged 14 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions OTAnalytics/application/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@
OTFLOW_FILE_TYPE = "otflow"


# OTConfig Default Values
DEFAULT_DO_EVENTS = True
DEFAULT_DO_COUNTING = True
DEFAULT_SAVE_NAME = ""
DEFAULT_SAVE_SUFFIX = ""
DEFAULT_EVENT_FORMATS = {"csv"}
DEFAULT_LOG_FILE = Path("logs")


OS: str = platform.system()
"""OS OTAnalytics is currently running on"""

Expand Down
3 changes: 3 additions & 0 deletions OTAnalytics/application/parser/config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def serialize(
self,
project: Project,
video_files: Iterable[Video],
track_files: Iterable[Path],
sections: Iterable[Section],
flows: Iterable[Flow],
file: Path,
Expand All @@ -84,6 +85,7 @@ def serialize(
Args:
project (Project): description of the project
video_files (Iterable[Video]): video files to reference
track_files (Iterable[Path]): track files to reference
sections (Iterable[Section]): sections to store
flows (Iterable[Flow]): flows to store
file (Path): output file
Expand All @@ -109,6 +111,7 @@ def convert(
self,
project: Project,
video_files: Iterable[Video],
track_files: Iterable[Path],
sections: Iterable[Section],
flows: Iterable[Flow],
file: Path,
Expand Down
5 changes: 3 additions & 2 deletions OTAnalytics/application/run_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,9 @@ def num_processes(self) -> int:
def log_file(self) -> Path:
if self._cli_args.log_file:
return Path(self._cli_args.log_file)
if self._otconfig:
return self._otconfig.analysis.logfile
if self._otconfig and self._cli_args.config_file:
base_dir = Path(self._cli_args.config_file).parent
return base_dir / self._otconfig.analysis.logfile
return DEFAULT_LOG_FILE

@property
Expand Down
4 changes: 3 additions & 1 deletion OTAnalytics/application/use_cases/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ def __call__(self, file: Path) -> None:
if self._datastore.project.start_date:
project = self._datastore.project
video_files = self._datastore.get_all_videos()
track_files = self._datastore._track_file_repository.get_all()
sections = self._datastore.get_all_sections()
flows = self._datastore.get_all_flows()

self._config_parser.serialize(
project=project,
video_files=video_files,
track_files=track_files,
sections=sections,
flows=flows,
file=file,
Expand All @@ -38,7 +40,7 @@ def __call__(self, file: Path) -> None:
ConfigurationFile(
file,
self._config_parser.convert(
project, video_files, sections, flows, file
project, video_files, track_files, sections, flows, file
),
)
)
Expand Down
4 changes: 4 additions & 0 deletions OTAnalytics/application/use_cases/config_has_changed.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from OTAnalytics.application.use_cases.flow_repository import GetAllFlows
from OTAnalytics.application.use_cases.get_current_project import GetCurrentProject
from OTAnalytics.application.use_cases.section_repository import GetAllSections
from OTAnalytics.application.use_cases.track_repository import GetAllTrackFiles
from OTAnalytics.application.use_cases.video_repository import GetAllVideos


Expand All @@ -15,12 +16,14 @@ def __init__(
get_flows: GetAllFlows,
get_current_project: GetCurrentProject,
get_videos: GetAllVideos,
get_track_files: GetAllTrackFiles,
):
self._config_parser = config_parser
self._get_sections = get_sections
self._get_flows = get_flows
self._get_current_project = get_current_project
self._get_videos = get_videos
self._get_track_files = get_track_files

def has_changed(self, prev_config: ConfigurationFile) -> bool:
"""
Expand All @@ -35,6 +38,7 @@ def has_changed(self, prev_config: ConfigurationFile) -> bool:
current_content = self._config_parser.convert(
self._get_current_project.get(),
self._get_videos.get(),
self._get_track_files(),
self._get_sections(),
self._get_flows.get(),
prev_config.file,
Expand Down
4 changes: 4 additions & 0 deletions OTAnalytics/application/use_cases/load_otconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
AddAllFlows,
FlowAlreadyExists,
)
from OTAnalytics.application.use_cases.load_track_files import LoadTrackFiles
from OTAnalytics.application.use_cases.section_repository import (
AddAllSections,
SectionAlreadyExists,
Expand All @@ -26,6 +27,7 @@ def __init__(
add_videos: AddAllVideos,
add_sections: AddAllSections,
add_flows: AddAllFlows,
load_track_files: LoadTrackFiles,
deserialize: Deserializer,
) -> None:

Expand All @@ -35,6 +37,7 @@ def __init__(
self._add_videos = add_videos
self._add_sections = add_sections
self._add_flows = add_flows
self._load_track_files = load_track_files
self._deserialize = deserialize
self._subject = Subject[ConfigurationFile]()

Expand All @@ -48,6 +51,7 @@ def load(self, file: Path) -> None:
self._add_videos.add(config.videos)
self._add_sections.add(config.sections)
self._add_flows.add(config.flows)
self._load_track_files(list(config.analysis.track_files))
self._subject.notify(
ConfigurationFile(
file,
Expand Down
18 changes: 18 additions & 0 deletions OTAnalytics/domain/files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from os import path
from os.path import normcase, splitdrive
from pathlib import Path
from typing import Callable


class DifferentDrivesException(Exception):
pass


def build_relative_path(
actual: Path, relative_to: Path, exception_msg_provider: Callable[[str, str], str]
) -> str:
self_drive, _ = splitdrive(actual)
other_drive, _ = splitdrive(relative_to)
if normcase(self_drive) != normcase(other_drive):
raise DifferentDrivesException(exception_msg_provider(self_drive, other_drive))
return path.relpath(actual, relative_to)
18 changes: 16 additions & 2 deletions OTAnalytics/domain/track_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import Iterable, Optional

from OTAnalytics.application.logger import logger
from OTAnalytics.domain.observer import Subject
from OTAnalytics.domain.observer import OBSERVER, Subject
from OTAnalytics.domain.track import Track, TrackId
from OTAnalytics.domain.track_dataset import TrackDataset

Expand Down Expand Up @@ -225,11 +225,21 @@ def __len__(self) -> int:
class TrackFileRepository:
def __init__(self) -> None:
self._files: set[Path] = set()
self._subject = Subject[Iterable[Path]]()

def add(self, file: Path) -> None:
"""
Add a single track file the repository.

Args:
file (Path): track file to be added.
"""
self.__add(file)
self._subject.notify([file])

def __add(self, file: Path) -> None:
"""Add a single track file the repository without notifying observers.

Args:
file (Path): track file to be added.
"""
Expand All @@ -243,7 +253,8 @@ def add_all(self, files: Iterable[Path]) -> None:
files (Iterable[Path]): the files to be added.
"""
for file in files:
self.add(file)
self.__add(file)
self._subject.notify(files)

def get_all(self) -> set[Path]:
"""
Expand All @@ -253,3 +264,6 @@ def get_all(self) -> set[Path]:
set[Path]: all tracks within the repository.
"""
return self._files.copy()

def register(self, observer: OBSERVER[Iterable[Path]]) -> None:
self._subject.register(observer)
28 changes: 11 additions & 17 deletions OTAnalytics/domain/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
from dataclasses import dataclass
from datetime import datetime, timedelta
from math import floor
from os import path
from os.path import normcase, splitdrive
from pathlib import Path
from typing import Iterable, Optional

from OTAnalytics.domain.files import build_relative_path
from OTAnalytics.domain.track import TrackImage

VIDEOS: str = "videos"
Expand Down Expand Up @@ -83,10 +82,6 @@ def to_dict(
pass


class DifferentDrivesException(Exception):
pass


@dataclass(frozen=True)
class VideoMetadata:
path: str
Expand Down Expand Up @@ -196,18 +191,17 @@ def to_dict(
self,
relative_to: Path,
) -> dict:
return {PATH: self.__build_relative_path(relative_to)}

def __build_relative_path(self, relative_to: Path) -> str:
self_drive, _ = splitdrive(self.path)
other_drive, _ = splitdrive(relative_to)
if normcase(self_drive) != normcase(other_drive):
raise DifferentDrivesException(
"Video and config files are stored on different drives. "
f"Video file is stored on {self_drive}."
f"Configuration is stored on {other_drive}"
return {
PATH: build_relative_path(
self.path,
relative_to,
lambda actual, other: (
"Video and config files are stored on different drives. "
f"Video file is stored on {actual}."
f"Configuration is stored on {other}"
),
)
return path.relpath(self.path, relative_to)
}

def contains(self, date: datetime) -> bool:
if self.metadata:
Expand Down
6 changes: 3 additions & 3 deletions OTAnalytics/plugin_parser/argparse_cli_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ def _setup(self) -> None:
required=False,
)
self._parser.add_argument(
"--track-export",
"--no-track-export",
action="store_true",
help="Export tracks as csv",
help="Do not export tracks as csv",
required=False,
)
self._parser.add_argument(
Expand Down Expand Up @@ -149,7 +149,7 @@ def parse(self) -> CliArguments:
save_suffix=args.save_suffix,
event_formats=args.event_formats,
count_intervals=args.count_intervals,
track_export=args.track_export,
track_export=not args.no_track_export,
num_processes=args.num_processes,
log_file=args.logfile,
include_classes=args.include_classes,
Expand Down
53 changes: 50 additions & 3 deletions OTAnalytics/plugin_parser/otconfig_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
from typing import Iterable

from OTAnalytics.application import project
from OTAnalytics.application.config import (
DEFAULT_COUNTING_INTERVAL_IN_MINUTES,
DEFAULT_DO_COUNTING,
DEFAULT_DO_EVENTS,
DEFAULT_EVENT_FORMATS,
DEFAULT_LOG_FILE,
DEFAULT_SAVE_NAME,
DEFAULT_SAVE_SUFFIX,
)
from OTAnalytics.application.config_specification import OtConfigDefaultValueProvider
from OTAnalytics.application.datastore import VideoParser
from OTAnalytics.application.parser.config_parser import (
Expand Down Expand Up @@ -33,6 +42,7 @@
WeatherType,
)
from OTAnalytics.domain import flow, section, video
from OTAnalytics.domain.files import build_relative_path
from OTAnalytics.domain.flow import Flow
from OTAnalytics.domain.section import Section
from OTAnalytics.domain.video import Video
Expand Down Expand Up @@ -181,7 +191,6 @@ def _parse_analysis(self, data: dict, base_folder: Path) -> AnalysisConfig:
EXPORT,
NUM_PROCESSES,
LOGFILE,
DEBUG,
],
)
export_config = self._parse_export(data[EXPORT])
Expand Down Expand Up @@ -214,17 +223,23 @@ def serialize(
self,
project: Project,
video_files: Iterable[Video],
track_files: Iterable[Path],
sections: Iterable[Section],
flows: Iterable[Flow],
file: Path,
) -> None:
self._validate_data(project)
content = self.convert(project, video_files, sections, flows, file)
content = self.convert(project, video_files, track_files, sections, flows, file)
write_json(data=content, path=file)

def serialize_from_config(self, config: OtConfig, file: Path) -> None:
self.serialize(
config.project, config.videos, config.sections, config.flows, file
config.project,
config.videos,
config.analysis.track_files,
config.sections,
config.flows,
file,
)

@staticmethod
Expand All @@ -236,6 +251,7 @@ def convert(
self,
project: Project,
video_files: Iterable[Video],
track_files: Iterable[Path],
sections: Iterable[Section],
flows: Iterable[Flow],
file: Path,
Expand All @@ -247,7 +263,38 @@ def convert(
relative_to=parent_folder,
)
section_content = self._flow_parser.convert(sections, flows)
analysis_content: dict = {
ANALYSIS: {
DO_EVENTS: DEFAULT_DO_EVENTS,
DO_COUNTING: DEFAULT_DO_COUNTING,
TRACKS: sorted(
[
build_relative_path(
file,
parent_folder,
lambda actual, other: (
"Track and config files are stored on "
"different drives. "
f"Track file is stored on {actual}."
f"Configuration is stored on {other}"
),
)
for file in track_files
]
),
EXPORT: {
SAVE_NAME: DEFAULT_SAVE_NAME,
SAVE_SUFFIX: DEFAULT_SAVE_SUFFIX,
EVENT_FORMATS: list(DEFAULT_EVENT_FORMATS),
COUNT_INTERVALS: [DEFAULT_COUNTING_INTERVAL_IN_MINUTES],
},
NUM_PROCESSES: 1,
LOGFILE: str(DEFAULT_LOG_FILE),
}
}
content: dict[str, list[dict] | dict] = {PROJECT: project_content}
content |= video_content
content |= analysis_content
content |= section_content

return content
Loading