arize-phoenix 10.0.4__py3-none-any.whl → 12.28.1__py3-none-any.whl
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.
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/METADATA +124 -72
- arize_phoenix-12.28.1.dist-info/RECORD +499 -0
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/WHEEL +1 -1
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/IP_NOTICE +1 -1
- phoenix/__generated__/__init__.py +0 -0
- phoenix/__generated__/classification_evaluator_configs/__init__.py +20 -0
- phoenix/__generated__/classification_evaluator_configs/_document_relevance_classification_evaluator_config.py +17 -0
- phoenix/__generated__/classification_evaluator_configs/_hallucination_classification_evaluator_config.py +17 -0
- phoenix/__generated__/classification_evaluator_configs/_models.py +18 -0
- phoenix/__generated__/classification_evaluator_configs/_tool_selection_classification_evaluator_config.py +17 -0
- phoenix/__init__.py +5 -4
- phoenix/auth.py +39 -2
- phoenix/config.py +1763 -91
- phoenix/datetime_utils.py +120 -2
- phoenix/db/README.md +595 -25
- phoenix/db/bulk_inserter.py +145 -103
- phoenix/db/engines.py +140 -33
- phoenix/db/enums.py +3 -12
- phoenix/db/facilitator.py +302 -35
- phoenix/db/helpers.py +1000 -65
- phoenix/db/iam_auth.py +64 -0
- phoenix/db/insertion/dataset.py +135 -2
- phoenix/db/insertion/document_annotation.py +9 -6
- phoenix/db/insertion/evaluation.py +2 -3
- phoenix/db/insertion/helpers.py +17 -2
- phoenix/db/insertion/session_annotation.py +176 -0
- phoenix/db/insertion/span.py +15 -11
- phoenix/db/insertion/span_annotation.py +3 -4
- phoenix/db/insertion/trace_annotation.py +3 -4
- phoenix/db/insertion/types.py +50 -20
- phoenix/db/migrations/versions/01a8342c9cdf_add_user_id_on_datasets.py +40 -0
- phoenix/db/migrations/versions/0df286449799_add_session_annotations_table.py +105 -0
- phoenix/db/migrations/versions/272b66ff50f8_drop_single_indices.py +119 -0
- phoenix/db/migrations/versions/58228d933c91_dataset_labels.py +67 -0
- phoenix/db/migrations/versions/699f655af132_experiment_tags.py +57 -0
- phoenix/db/migrations/versions/735d3d93c33e_add_composite_indices.py +41 -0
- phoenix/db/migrations/versions/a20694b15f82_cost.py +196 -0
- phoenix/db/migrations/versions/ab513d89518b_add_user_id_on_dataset_versions.py +40 -0
- phoenix/db/migrations/versions/d0690a79ea51_users_on_experiments.py +40 -0
- phoenix/db/migrations/versions/deb2c81c0bb2_dataset_splits.py +139 -0
- phoenix/db/migrations/versions/e76cbd66ffc3_add_experiments_dataset_examples.py +87 -0
- phoenix/db/models.py +669 -56
- phoenix/db/pg_config.py +10 -0
- phoenix/db/types/model_provider.py +4 -0
- phoenix/db/types/token_price_customization.py +29 -0
- phoenix/db/types/trace_retention.py +23 -15
- phoenix/experiments/evaluators/utils.py +3 -3
- phoenix/experiments/functions.py +160 -52
- phoenix/experiments/tracing.py +2 -2
- phoenix/experiments/types.py +1 -1
- phoenix/inferences/inferences.py +1 -2
- phoenix/server/api/auth.py +38 -7
- phoenix/server/api/auth_messages.py +46 -0
- phoenix/server/api/context.py +100 -4
- phoenix/server/api/dataloaders/__init__.py +79 -5
- phoenix/server/api/dataloaders/annotation_configs_by_project.py +31 -0
- phoenix/server/api/dataloaders/annotation_summaries.py +60 -8
- phoenix/server/api/dataloaders/average_experiment_repeated_run_group_latency.py +50 -0
- phoenix/server/api/dataloaders/average_experiment_run_latency.py +17 -24
- phoenix/server/api/dataloaders/cache/two_tier_cache.py +1 -2
- phoenix/server/api/dataloaders/dataset_dataset_splits.py +52 -0
- phoenix/server/api/dataloaders/dataset_example_revisions.py +0 -1
- phoenix/server/api/dataloaders/dataset_example_splits.py +40 -0
- phoenix/server/api/dataloaders/dataset_examples_and_versions_by_experiment_run.py +47 -0
- phoenix/server/api/dataloaders/dataset_labels.py +36 -0
- phoenix/server/api/dataloaders/document_evaluation_summaries.py +2 -2
- phoenix/server/api/dataloaders/document_evaluations.py +6 -9
- phoenix/server/api/dataloaders/experiment_annotation_summaries.py +88 -34
- phoenix/server/api/dataloaders/experiment_dataset_splits.py +43 -0
- phoenix/server/api/dataloaders/experiment_error_rates.py +21 -28
- phoenix/server/api/dataloaders/experiment_repeated_run_group_annotation_summaries.py +77 -0
- phoenix/server/api/dataloaders/experiment_repeated_run_groups.py +57 -0
- phoenix/server/api/dataloaders/experiment_runs_by_experiment_and_example.py +44 -0
- phoenix/server/api/dataloaders/last_used_times_by_generative_model_id.py +35 -0
- phoenix/server/api/dataloaders/latency_ms_quantile.py +40 -8
- phoenix/server/api/dataloaders/record_counts.py +37 -10
- phoenix/server/api/dataloaders/session_annotations_by_session.py +29 -0
- phoenix/server/api/dataloaders/span_cost_by_span.py +24 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_generative_model.py +56 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_project_session.py +57 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_span.py +43 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_trace.py +56 -0
- phoenix/server/api/dataloaders/span_cost_details_by_span_cost.py +27 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment.py +57 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment_repeated_run_group.py +64 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment_run.py +58 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_generative_model.py +55 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project.py +152 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project_session.py +56 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_trace.py +55 -0
- phoenix/server/api/dataloaders/span_costs.py +29 -0
- phoenix/server/api/dataloaders/table_fields.py +2 -2
- phoenix/server/api/dataloaders/token_prices_by_model.py +30 -0
- phoenix/server/api/dataloaders/trace_annotations_by_trace.py +27 -0
- phoenix/server/api/dataloaders/types.py +29 -0
- phoenix/server/api/exceptions.py +11 -1
- phoenix/server/api/helpers/dataset_helpers.py +5 -1
- phoenix/server/api/helpers/playground_clients.py +1243 -292
- phoenix/server/api/helpers/playground_registry.py +2 -2
- phoenix/server/api/helpers/playground_spans.py +8 -4
- phoenix/server/api/helpers/playground_users.py +26 -0
- phoenix/server/api/helpers/prompts/conversions/aws.py +83 -0
- phoenix/server/api/helpers/prompts/conversions/google.py +103 -0
- phoenix/server/api/helpers/prompts/models.py +205 -22
- phoenix/server/api/input_types/{SpanAnnotationFilter.py → AnnotationFilter.py} +22 -14
- phoenix/server/api/input_types/ChatCompletionInput.py +6 -2
- phoenix/server/api/input_types/CreateProjectInput.py +27 -0
- phoenix/server/api/input_types/CreateProjectSessionAnnotationInput.py +37 -0
- phoenix/server/api/input_types/DatasetFilter.py +17 -0
- phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
- phoenix/server/api/input_types/GenerativeCredentialInput.py +9 -0
- phoenix/server/api/input_types/GenerativeModelInput.py +5 -0
- phoenix/server/api/input_types/ProjectSessionSort.py +161 -1
- phoenix/server/api/input_types/PromptFilter.py +14 -0
- phoenix/server/api/input_types/PromptVersionInput.py +52 -1
- phoenix/server/api/input_types/SpanSort.py +44 -7
- phoenix/server/api/input_types/TimeBinConfig.py +23 -0
- phoenix/server/api/input_types/UpdateAnnotationInput.py +34 -0
- phoenix/server/api/input_types/UserRoleInput.py +1 -0
- phoenix/server/api/mutations/__init__.py +10 -0
- phoenix/server/api/mutations/annotation_config_mutations.py +8 -8
- phoenix/server/api/mutations/api_key_mutations.py +19 -23
- phoenix/server/api/mutations/chat_mutations.py +154 -47
- phoenix/server/api/mutations/dataset_label_mutations.py +243 -0
- phoenix/server/api/mutations/dataset_mutations.py +21 -16
- phoenix/server/api/mutations/dataset_split_mutations.py +351 -0
- phoenix/server/api/mutations/experiment_mutations.py +2 -2
- phoenix/server/api/mutations/export_events_mutations.py +3 -3
- phoenix/server/api/mutations/model_mutations.py +210 -0
- phoenix/server/api/mutations/project_mutations.py +49 -10
- phoenix/server/api/mutations/project_session_annotations_mutations.py +158 -0
- phoenix/server/api/mutations/project_trace_retention_policy_mutations.py +8 -4
- phoenix/server/api/mutations/prompt_label_mutations.py +74 -65
- phoenix/server/api/mutations/prompt_mutations.py +65 -129
- phoenix/server/api/mutations/prompt_version_tag_mutations.py +11 -8
- phoenix/server/api/mutations/span_annotations_mutations.py +15 -10
- phoenix/server/api/mutations/trace_annotations_mutations.py +14 -10
- phoenix/server/api/mutations/trace_mutations.py +47 -3
- phoenix/server/api/mutations/user_mutations.py +66 -41
- phoenix/server/api/queries.py +768 -293
- phoenix/server/api/routers/__init__.py +2 -2
- phoenix/server/api/routers/auth.py +154 -88
- phoenix/server/api/routers/ldap.py +229 -0
- phoenix/server/api/routers/oauth2.py +369 -106
- phoenix/server/api/routers/v1/__init__.py +24 -4
- phoenix/server/api/routers/v1/annotation_configs.py +23 -31
- phoenix/server/api/routers/v1/annotations.py +481 -17
- phoenix/server/api/routers/v1/datasets.py +395 -81
- phoenix/server/api/routers/v1/documents.py +142 -0
- phoenix/server/api/routers/v1/evaluations.py +24 -31
- phoenix/server/api/routers/v1/experiment_evaluations.py +19 -8
- phoenix/server/api/routers/v1/experiment_runs.py +337 -59
- phoenix/server/api/routers/v1/experiments.py +479 -48
- phoenix/server/api/routers/v1/models.py +7 -0
- phoenix/server/api/routers/v1/projects.py +18 -49
- phoenix/server/api/routers/v1/prompts.py +54 -40
- phoenix/server/api/routers/v1/sessions.py +108 -0
- phoenix/server/api/routers/v1/spans.py +1091 -81
- phoenix/server/api/routers/v1/traces.py +132 -78
- phoenix/server/api/routers/v1/users.py +389 -0
- phoenix/server/api/routers/v1/utils.py +3 -7
- phoenix/server/api/subscriptions.py +305 -88
- phoenix/server/api/types/Annotation.py +90 -23
- phoenix/server/api/types/ApiKey.py +13 -17
- phoenix/server/api/types/AuthMethod.py +1 -0
- phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +1 -0
- phoenix/server/api/types/CostBreakdown.py +12 -0
- phoenix/server/api/types/Dataset.py +226 -72
- phoenix/server/api/types/DatasetExample.py +88 -18
- phoenix/server/api/types/DatasetExperimentAnnotationSummary.py +10 -0
- phoenix/server/api/types/DatasetLabel.py +57 -0
- phoenix/server/api/types/DatasetSplit.py +98 -0
- phoenix/server/api/types/DatasetVersion.py +49 -4
- phoenix/server/api/types/DocumentAnnotation.py +212 -0
- phoenix/server/api/types/Experiment.py +264 -59
- phoenix/server/api/types/ExperimentComparison.py +5 -10
- phoenix/server/api/types/ExperimentRepeatedRunGroup.py +155 -0
- phoenix/server/api/types/ExperimentRepeatedRunGroupAnnotationSummary.py +9 -0
- phoenix/server/api/types/ExperimentRun.py +169 -65
- phoenix/server/api/types/ExperimentRunAnnotation.py +158 -39
- phoenix/server/api/types/GenerativeModel.py +245 -3
- phoenix/server/api/types/GenerativeProvider.py +70 -11
- phoenix/server/api/types/{Model.py → InferenceModel.py} +1 -1
- phoenix/server/api/types/ModelInterface.py +16 -0
- phoenix/server/api/types/PlaygroundModel.py +20 -0
- phoenix/server/api/types/Project.py +1278 -216
- phoenix/server/api/types/ProjectSession.py +188 -28
- phoenix/server/api/types/ProjectSessionAnnotation.py +187 -0
- phoenix/server/api/types/ProjectTraceRetentionPolicy.py +1 -1
- phoenix/server/api/types/Prompt.py +119 -39
- phoenix/server/api/types/PromptLabel.py +42 -25
- phoenix/server/api/types/PromptVersion.py +11 -8
- phoenix/server/api/types/PromptVersionTag.py +65 -25
- phoenix/server/api/types/ServerStatus.py +6 -0
- phoenix/server/api/types/Span.py +167 -123
- phoenix/server/api/types/SpanAnnotation.py +189 -42
- phoenix/server/api/types/SpanCostDetailSummaryEntry.py +10 -0
- phoenix/server/api/types/SpanCostSummary.py +10 -0
- phoenix/server/api/types/SystemApiKey.py +65 -1
- phoenix/server/api/types/TokenPrice.py +16 -0
- phoenix/server/api/types/TokenUsage.py +3 -3
- phoenix/server/api/types/Trace.py +223 -51
- phoenix/server/api/types/TraceAnnotation.py +149 -50
- phoenix/server/api/types/User.py +137 -32
- phoenix/server/api/types/UserApiKey.py +73 -26
- phoenix/server/api/types/node.py +10 -0
- phoenix/server/api/types/pagination.py +11 -2
- phoenix/server/app.py +290 -45
- phoenix/server/authorization.py +38 -3
- phoenix/server/bearer_auth.py +34 -24
- phoenix/server/cost_tracking/cost_details_calculator.py +196 -0
- phoenix/server/cost_tracking/cost_model_lookup.py +179 -0
- phoenix/server/cost_tracking/helpers.py +68 -0
- phoenix/server/cost_tracking/model_cost_manifest.json +3657 -830
- phoenix/server/cost_tracking/regex_specificity.py +397 -0
- phoenix/server/cost_tracking/token_cost_calculator.py +57 -0
- phoenix/server/daemons/__init__.py +0 -0
- phoenix/server/daemons/db_disk_usage_monitor.py +214 -0
- phoenix/server/daemons/generative_model_store.py +103 -0
- phoenix/server/daemons/span_cost_calculator.py +99 -0
- phoenix/server/dml_event.py +17 -0
- phoenix/server/dml_event_handler.py +5 -0
- phoenix/server/email/sender.py +56 -3
- phoenix/server/email/templates/db_disk_usage_notification.html +19 -0
- phoenix/server/email/types.py +11 -0
- phoenix/server/experiments/__init__.py +0 -0
- phoenix/server/experiments/utils.py +14 -0
- phoenix/server/grpc_server.py +11 -11
- phoenix/server/jwt_store.py +17 -15
- phoenix/server/ldap.py +1449 -0
- phoenix/server/main.py +26 -10
- phoenix/server/oauth2.py +330 -12
- phoenix/server/prometheus.py +66 -6
- phoenix/server/rate_limiters.py +4 -9
- phoenix/server/retention.py +33 -20
- phoenix/server/session_filters.py +49 -0
- phoenix/server/static/.vite/manifest.json +55 -51
- phoenix/server/static/assets/components-BreFUQQa.js +6702 -0
- phoenix/server/static/assets/{index-E0M82BdE.js → index-CTQoemZv.js} +140 -56
- phoenix/server/static/assets/pages-DBE5iYM3.js +9524 -0
- phoenix/server/static/assets/vendor-BGzfc4EU.css +1 -0
- phoenix/server/static/assets/vendor-DCE4v-Ot.js +920 -0
- phoenix/server/static/assets/vendor-codemirror-D5f205eT.js +25 -0
- phoenix/server/static/assets/vendor-recharts-V9cwpXsm.js +37 -0
- phoenix/server/static/assets/vendor-shiki-Do--csgv.js +5 -0
- phoenix/server/static/assets/vendor-three-CmB8bl_y.js +3840 -0
- phoenix/server/templates/index.html +40 -6
- phoenix/server/thread_server.py +1 -2
- phoenix/server/types.py +14 -4
- phoenix/server/utils.py +74 -0
- phoenix/session/client.py +56 -3
- phoenix/session/data_extractor.py +5 -0
- phoenix/session/evaluation.py +14 -5
- phoenix/session/session.py +45 -9
- phoenix/settings.py +5 -0
- phoenix/trace/attributes.py +80 -13
- phoenix/trace/dsl/helpers.py +90 -1
- phoenix/trace/dsl/query.py +8 -6
- phoenix/trace/projects.py +5 -0
- phoenix/utilities/template_formatters.py +1 -1
- phoenix/version.py +1 -1
- arize_phoenix-10.0.4.dist-info/RECORD +0 -405
- phoenix/server/api/types/Evaluation.py +0 -39
- phoenix/server/cost_tracking/cost_lookup.py +0 -255
- phoenix/server/static/assets/components-DULKeDfL.js +0 -4365
- phoenix/server/static/assets/pages-Cl0A-0U2.js +0 -7430
- phoenix/server/static/assets/vendor-WIZid84E.css +0 -1
- phoenix/server/static/assets/vendor-arizeai-Dy-0mSNw.js +0 -649
- phoenix/server/static/assets/vendor-codemirror-DBtifKNr.js +0 -33
- phoenix/server/static/assets/vendor-oB4u9zuV.js +0 -905
- phoenix/server/static/assets/vendor-recharts-D-T4KPz2.js +0 -59
- phoenix/server/static/assets/vendor-shiki-BMn4O_9F.js +0 -5
- phoenix/server/static/assets/vendor-three-C5WAXd5r.js +0 -2998
- phoenix/utilities/deprecation.py +0 -31
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/LICENSE +0 -0
phoenix/datetime_utils.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from datetime import datetime, timedelta, timezone, tzinfo
|
|
2
|
-
from typing import Any, Optional, cast
|
|
2
|
+
from typing import Any, Iterator, Literal, Optional, cast
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
5
|
import pytz
|
|
@@ -10,6 +10,7 @@ from pandas.core.dtypes.common import (
|
|
|
10
10
|
is_numeric_dtype,
|
|
11
11
|
is_object_dtype,
|
|
12
12
|
)
|
|
13
|
+
from typing_extensions import assert_never
|
|
13
14
|
|
|
14
15
|
_LOCAL_TIMEZONE = datetime.now(timezone.utc).astimezone().tzinfo
|
|
15
16
|
|
|
@@ -28,7 +29,7 @@ def normalize_datetime(
|
|
|
28
29
|
"""
|
|
29
30
|
if not isinstance(dt, datetime):
|
|
30
31
|
return None
|
|
31
|
-
if
|
|
32
|
+
if not is_timezone_aware(dt):
|
|
32
33
|
dt = dt.replace(tzinfo=tz if tz else _LOCAL_TIMEZONE)
|
|
33
34
|
return dt.astimezone(timezone.utc)
|
|
34
35
|
|
|
@@ -106,3 +107,120 @@ def right_open_time_range(
|
|
|
106
107
|
floor_to_minute(min_time) if min_time else None,
|
|
107
108
|
floor_to_minute(max_time + timedelta(minutes=1)) if max_time else None,
|
|
108
109
|
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def is_timezone_aware(dt: datetime) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Returns True if the datetime is timezone-aware, False otherwise.
|
|
115
|
+
"""
|
|
116
|
+
return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_timestamp_range(
|
|
120
|
+
start_time: datetime,
|
|
121
|
+
end_time: datetime,
|
|
122
|
+
stride: Literal["minute", "hour", "day", "week", "month", "year"] = "minute",
|
|
123
|
+
utc_offset_minutes: int = 0,
|
|
124
|
+
) -> Iterator[datetime]:
|
|
125
|
+
"""
|
|
126
|
+
Generate a sequence of datetime objects at regular intervals between start and end times.
|
|
127
|
+
|
|
128
|
+
This function creates time intervals by rounding down the start time to the nearest
|
|
129
|
+
stride boundary in the specified timezone, then yielding timestamps at regular
|
|
130
|
+
intervals until reaching the end time. All returned timestamps are in UTC.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
start_time: The starting datetime (inclusive after rounding down to stride boundary).
|
|
134
|
+
Must be timezone-aware.
|
|
135
|
+
end_time: The ending datetime (exclusive). Must be timezone-aware.
|
|
136
|
+
stride: The interval between generated timestamps. Options:
|
|
137
|
+
- "minute": Generate timestamps every minute
|
|
138
|
+
- "hour": Generate timestamps every hour
|
|
139
|
+
- "day": Generate timestamps every day at midnight
|
|
140
|
+
- "week": Generate timestamps every week at Monday midnight
|
|
141
|
+
- "month": Generate timestamps on the 1st of each month at midnight
|
|
142
|
+
- "year": Generate timestamps on January 1st of each year at midnight
|
|
143
|
+
utc_offset_minutes: Timezone offset in minutes from UTC. Used to determine
|
|
144
|
+
the correct stride boundaries in local time. Positive values
|
|
145
|
+
are east of UTC, negative values are west of UTC.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Iterator of datetime objects in UTC timezone, spaced at the specified stride
|
|
149
|
+
interval. The first timestamp is rounded down to the nearest stride boundary
|
|
150
|
+
in the local timezone (considering utc_offset_minutes).
|
|
151
|
+
|
|
152
|
+
Examples:
|
|
153
|
+
>>> from datetime import datetime, timezone
|
|
154
|
+
>>> start = datetime(2024, 1, 1, 12, 30, 45, tzinfo=timezone.utc)
|
|
155
|
+
>>> end = datetime(2024, 1, 1, 12, 33, 0, tzinfo=timezone.utc)
|
|
156
|
+
>>> list(get_timestamp_range(start, end, "minute"))
|
|
157
|
+
[datetime(2024, 1, 1, 12, 30, tzinfo=timezone.utc),
|
|
158
|
+
datetime(2024, 1, 1, 12, 31, tzinfo=timezone.utc),
|
|
159
|
+
datetime(2024, 1, 1, 12, 32, tzinfo=timezone.utc)]
|
|
160
|
+
|
|
161
|
+
>>> # Week stride rounds down to Monday
|
|
162
|
+
>>> start = datetime(2024, 1, 10, 12, 0, tzinfo=timezone.utc) # Wednesday
|
|
163
|
+
>>> end = datetime(2024, 1, 22, 0, 0, tzinfo=timezone.utc)
|
|
164
|
+
>>> list(get_timestamp_range(start, end, "week"))
|
|
165
|
+
[datetime(2024, 1, 8, 0, 0, tzinfo=timezone.utc), # Monday
|
|
166
|
+
datetime(2024, 1, 15, 0, 0, tzinfo=timezone.utc)] # Next Monday
|
|
167
|
+
|
|
168
|
+
Note:
|
|
169
|
+
- If end_time <= start_time (after rounding), returns an empty iterator
|
|
170
|
+
- Week intervals always start on Monday (weekday 0)
|
|
171
|
+
- Month intervals handle variable month lengths correctly including leap years
|
|
172
|
+
- The function works in local timezone for stride calculations but returns UTC
|
|
173
|
+
"""
|
|
174
|
+
if not is_timezone_aware(start_time) or not is_timezone_aware(end_time):
|
|
175
|
+
raise ValueError("start_time and end_time must be timezone-aware")
|
|
176
|
+
|
|
177
|
+
# Apply UTC offset to work in local timezone
|
|
178
|
+
offset_delta = timedelta(minutes=utc_offset_minutes)
|
|
179
|
+
local_start_time = start_time + offset_delta
|
|
180
|
+
|
|
181
|
+
# round down start_time to the nearest stride in local timezone
|
|
182
|
+
if stride == "minute":
|
|
183
|
+
t = local_start_time.replace(second=0, microsecond=0)
|
|
184
|
+
elif stride == "hour":
|
|
185
|
+
t = local_start_time.replace(minute=0, second=0, microsecond=0)
|
|
186
|
+
elif stride == "day":
|
|
187
|
+
t = local_start_time.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
188
|
+
elif stride == "week":
|
|
189
|
+
# Round down to the beginning of the week (Monday)
|
|
190
|
+
days_since_monday = local_start_time.weekday()
|
|
191
|
+
t = local_start_time.replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(
|
|
192
|
+
days=days_since_monday
|
|
193
|
+
)
|
|
194
|
+
elif stride == "month":
|
|
195
|
+
t = local_start_time.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
|
196
|
+
elif stride == "year":
|
|
197
|
+
t = local_start_time.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
|
|
198
|
+
else:
|
|
199
|
+
assert_never(stride)
|
|
200
|
+
|
|
201
|
+
# Convert back to UTC for comparisons and yielding
|
|
202
|
+
local_end_time = end_time + offset_delta
|
|
203
|
+
|
|
204
|
+
while t < local_end_time:
|
|
205
|
+
# Yield timestamp converted back to UTC
|
|
206
|
+
yield t - offset_delta
|
|
207
|
+
if stride == "minute":
|
|
208
|
+
t += timedelta(minutes=1)
|
|
209
|
+
elif stride == "hour":
|
|
210
|
+
t += timedelta(hours=1)
|
|
211
|
+
elif stride == "day":
|
|
212
|
+
t += timedelta(days=1)
|
|
213
|
+
elif stride == "week":
|
|
214
|
+
t += timedelta(weeks=1)
|
|
215
|
+
elif stride == "month":
|
|
216
|
+
next_month = t.month % 12 + 1
|
|
217
|
+
next_year = t.year + (t.month // 12)
|
|
218
|
+
t = t.replace(
|
|
219
|
+
year=next_year, month=next_month, day=1, hour=0, minute=0, second=0, microsecond=0
|
|
220
|
+
)
|
|
221
|
+
elif stride == "year":
|
|
222
|
+
t = t.replace(
|
|
223
|
+
year=t.year + 1, month=1, day=1, hour=0, minute=0, second=0, microsecond=0
|
|
224
|
+
)
|
|
225
|
+
else:
|
|
226
|
+
assert_never(stride)
|