{"id":17874052,"url":"https://github.com/devkral/timelineomat","last_synced_at":"2025-04-03T00:12:35.711Z","repository":{"id":241101073,"uuid":"804283024","full_name":"devkral/timelineomat","owner":"devkral","description":null,"archived":false,"fork":false,"pushed_at":"2024-05-22T12:15:26.000Z","size":6,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-05-22T12:30:38.971Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devkral.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-22T09:54:31.000Z","updated_at":"2024-05-22T12:30:41.155Z","dependencies_parsed_at":"2024-05-22T12:30:40.818Z","dependency_job_id":"3a4043f5-b7dc-48c9-bb8e-794a8a581899","html_url":"https://github.com/devkral/timelineomat","commit_stats":null,"previous_names":["devkral/timelineomat"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devkral%2Ftimelineomat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devkral%2Ftimelineomat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devkral%2Ftimelineomat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devkral%2Ftimelineomat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devkral","download_url":"https://codeload.github.com/devkral/timelineomat/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246911469,"owners_count":20853657,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-28T11:07:28.460Z","updated_at":"2025-04-03T00:12:35.692Z","avatar_url":"https://github.com/devkral.png","language":"Python","readme":"# TimelineOMat\n\nThis project allows to streamline events in timelines.\nStreamlining is here to fill to adjust the event start and stop times so it fits into the gaps of the timeline and to emit an Exception if this is not possible.\nThe timeline should be filled with the events with higher priority first and then descend in priority.\n\n\n## Installation\n``` sh\npip install timelineomat\n```\n\n## Usage\n\nThere are 5 different functions which also exist as methods of the TimelineOMat class\n\n- streamline_event_times: checks how to short the given event to fit into the timelines. Without a timeline the result can be used for sorting (see section later)\n- streamline_event: uses streamline_event_times plus setters to update the event and returns event\n- transform_events_to_times: transforms timelines to TimeRangeTuple for e.g. databases\n- ordered_insert: insert an event in a timeline so it stays ordered. By default an offset is returned. It can be used in case of ascending inserts to improve the performance\n- streamline_ordered_insert: combined functions of ordered_insert and streamline_event. Works efficient on arrays also in descending order direction\n\nordered_insert also takes the parameters direction and offset (direction can be set on TimelineOMat). This allows performant inserts and collision checks.\n\nWhen ordered_insert is called with offset 0 or unset it is safe to call even when the insertion order is chaotic\n\nThe timeline must be ordered anyway for ordered_insert\n\nThere is a new argument occlusions which must be of type list. It receives the \noccluded time ranges\n\n``` python\nfrom dataclasses import dataclass\nfrom datetime import datetime as dt\nfrom timelineomat import TimelineOMat, streamline_event_times, stream_line_event, TimeRangeTuple\n\n\n@dataclass\nclass Event:\n    start: dt\n    stop: dt\n\ntimeline = [\n    Event(start=dt(2024, 1, 1), stop=dt(2024, 1, 2)),\n    Event(start=dt(2024, 1, 2), stop=dt(2024, 1, 3))\n]\nnew_event = Event(start=dt(2024, 1, 1), stop=dt(2024, 1, 4))\n# one time methods\n# get intermediate result of new times\nstreamline_event_times(new_event, timeline) == TimeRangeTuple(start=dt(2024, 1, 3), stop=dt(2024, 1, 4))\n# update the event\ntimeline.append(streamline_event(new_event, timeline))\n\ntm = TimelineOMat()\n# use method instead\ntm.streamline_event_times(Event(start=dt(2024, 1, 1), stop=dt(2024, 1, )), timeline) == TimeRangeTuple(start=dt(2024, 1, 4), stop=dt(2024, 1, 5))\n\n# now integrate in django or whatever\nfrom django.db.models import Q\n\nq = Q()\n# this is not optimized\nfor timetuple, ev in tm.transform_events_to_times(timeline):\n    # timetuple is actually a 2 element tuple\n    q |= Q(timepoint__range=timetuple) \u0026 ~Q(id=ev.id)\n\n```\n\n\n## Tricks to integrate in different datastructures\n\nTimelineOMat supports out of the box all kind of dicts as well as objects. It determinates\nif an object is a dict and uses otherwise getattr and setattr. It even supports timelines with mixed types.\n\nThe easiest way to integrate Events in TimelineOMat with different names than start and stop is to provide\nthe names for `start_extractor` and `stop_extractor`.\nWhen providing strings the string names are mirrored to the arguments:\n`start_setter` and `stop_setter`. No need to set them explicitly.\n\n### Data types of start, stop\n\nthe output format is always datetime but the input is flexible. Datetimes are nearly passed through\n(naive datetimes can get a timezone set, more info later)\n\nInt as well as float are also supported. In this case datetime.fromtimestamp is used.\n\nIn case of strings fromisodatestring is used.\n\n#### Optional fallback timezones\n\nAll of TimelineOMat, streamline_event_times and streamline_event support an argument:\nfallback_timezone\n\nIf set the timezone is used in case a naive datetime is encountered (in case of int, float, the timezone is always set).\n\nSupported are the regular timezones of python (timezone.utc or ZoneInfo).\n\n### TimelineOMat one-time overwrites\n\n\nGiven the code\n\n\n``` python\nfrom dataclasses import dataclass\nfrom datetime import datetime as dt\nfrom timelineomat import TimelineOMat\n\n\n@dataclass\nclass Event:\n    start: dt\n    stop: dt\n\n\ntimeline = [\n    Event(start=dt(2024, 1, 1), stop=dt(2024, 1, 2)),\n    Event(start=dt(2024, 1, 2), stop=dt(2024, 1, 3))\n]\nnew_event1 = Event(start=dt(2024, 1, 1), stop=dt(2024, 1, 4))\nnew_event2 = dict(start=dt(2024, 1, 1), end=dt(2024, 1, 5))\n\ntm = TimelineOMat()\n```\n\nit is possible to extract the data with\n\n``` python\n\n\ndef one_time_overwrite_end(ev):\n    if isinstance(ev, dict):\n        return ev[\"end\"]\n    else:\n        return ev.stop\n\ntimeline.append(tm.streamline_event(new_event1, timeline))\n#\n\ntimeline.append(Event(**tm.streamline_event_times(new_event2, timeline, stop_extractor=one_time_overwrite_end)._asdict()))\n```\n\n\n## Tricks to improve the performance:\n\n### Using TimelineOMat\n In case of the one time methods the extractors and setters are generated all the time when using string extractors or setters -\u003e bad performance\n\nBuilding an TimelineOMat is more efficient or alternatively provide functions for extractors and setters\n\n### Only the last element (sorted timelines)\n\nFor handling unsorted timelines TimelineOMat iterates through all events all the time.\nIn case of an ordered timeline the performance can be improved by using only the last element:\n\n\n``` python\nfrom dataclasses import dataclass\nfrom datetime import datetime as dt\nfrom timelineomat import TimelineOMat\n\n\n@dataclass\nclass Event:\n    start: dt\n    stop: dt\n\nordered_timeline = [\n    Event(start=dt(2024, 1, 1), stop=dt(2024, 1, 2)),\n    Event(start=dt(2024, 1, 2), stop=dt(2024, 1, 3))\n]\nnew_event = Event(start=dt(2024, 1, 1), stop=dt(2024, 1, 4))\n# here we generate the setters and extractors only onetime\ntm = TimelineOMat()\ntm.streamline_event_times(new_event, ordered_timeline[-1:])\n\n```\n\nIn case the inserts are not completely ordered there is a helper named ordered_insert. It returns and takes (optionally) an offset. As soon as a break in the monotonic ascending or descending is detected, the offset can be set to 0.\n\nNote: position and offset are in ascending orders the same.\n\n\n``` python\nfrom dataclasses import dataclass\nfrom datetime import datetime as dt\nfrom timelineomat import TimelineOMat\n\n\n@dataclass\nclass Event:\n    start: dt\n    stop: dt\n\nordered_timeline = [\n    Event(start=dt(2024, 1, 1), stop=dt(2024, 1, 2)),\n    Event(start=dt(2024, 1, 2), stop=dt(2024, 1, 3))\n]\nnew_event1 = Event(start=dt(2024, 1, 1), stop=dt(2024, 1, 4))\nnew_event2 = Event(start=dt(2023, 1, 1), stop=dt(2023, 1, 4))\nnew_event3 = Event(start=dt(2025, 1, 1), stop=dt(2025, 1, 4))\nnew_event4 = Event(start=dt(2025, 2, 1), stop=dt(2025, 2, 4))\n# here we generate the setters and extractors only onetime\ntm = TimelineOMat(direction=\"desc\")\nposition, offset = tm.ordered_insert(\n    tm.streamline_event(\n        new_event1, ordered_timeline[:len(ordered_timeline)-1-offset]\n    ),\n    ordered_timeline\n)\nposition, offset = tm.streamlined_ordered_insert(new_event2, ordered_timeline, offset=offset)\n# is stable\nposition = tm.streamlined_ordered_insert(new_event2, ordered_timeline, ordered_timeline, offset=offset).position\n# here is a break in the monotic order and we get ascending inserts\noffset = 0\n# ascending is easier, so split streamlined_ordered_insert into their inner commands\n# for descending we need to build a reverse window of the array\nposition, offset = tm.ordered_insert(tm.streamline_event(new_event3, ordered_timeline), ordered_timeline, offset=offset, direction=\"asc\")\nposition, offset = tm.ordered_insert(tm.streamline_event(new_event4, ordered_timeline[-1:]), ordered_timeline, offset=offset, direction=\"asc\")\n\n```\n\n\n## How to integrate in db systems\n\nDB Systems like django support range queries, which receives two element tuples. TimelineOMat can convert the timelines into such tuples (TimeRangeTuple doubles as tuple) with transform_events_to_times.\n\nThe result is an iterator which returns tuples of (TimeRangeTuple, Event)\n\nAn example is in Usage\n\nNote: since 0.7.0 the Event from which the TimeRangeTuple is extracted is provided as second element\n\n\n## How to use for sorting\n\nsimple use the streamline_event_times(...) method of TimelineOMat without any timelines as key function. By using the TimelineOMat class parameters can be preinitialized\n\nThe resulting tuple can be sorted\n\n``` python\n\ntm = TimelineOMat()\ntimeline.sort(key=tm.streamline_event_times)\n\n```\n\nTo compare only the start or stop timestamps\n\n``` python\n\ntm = TimelineOMat()\ntimeline.sort(key=tm.start_extractor)\n\n```\n\nAnother usage of the key function would be together with heapq to implement some kind of merge sort\n\n## Development\n\nFor more speed and efficiency this projects uses uv instead of pip.\nThis means you have to install the uv package manager additionally for development.\n\nSee how to install here:\n\nhttps://pypi.org/project/uv/\n\n## Changes\n\n0.7.0 Breaking Change: transform_events_to_times is now an iterator and returns the event as second element\n0.6.0 add streamlined_ordered_insert\n0.5.0 add occlusions argument\n0.4.0 rename NoCallAllowed to NoCallAllowedError\n0.3.0 rename NewTimesResult to TimeRangeTuple (the old name is still available)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevkral%2Ftimelineomat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevkral%2Ftimelineomat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevkral%2Ftimelineomat/lists"}