|
7 | 7 | from typing import Any, Dict, List, Literal, Optional, Union |
8 | 8 |
|
9 | 9 | import dpath |
10 | | -from pydantic.v1 import AnyUrl, BaseModel, Field |
| 10 | +from pydantic.v1 import AnyUrl, BaseModel, Field, validator |
11 | 11 |
|
12 | 12 | from airbyte_cdk import OneOfOptionConfig |
13 | 13 | from airbyte_cdk.sources.file_based.config.file_based_stream_config import FileBasedStreamConfig |
14 | 14 | from airbyte_cdk.sources.specs.transfer_modes import DeliverPermissions |
15 | 15 | from airbyte_cdk.sources.utils import schema_helpers |
| 16 | +from airbyte_cdk.utils.datetime_helpers import ab_datetime_try_parse |
16 | 17 |
|
17 | 18 |
|
18 | 19 | class DeliverRecords(BaseModel): |
@@ -53,13 +54,39 @@ class AbstractFileBasedSpec(BaseModel): |
53 | 54 | start_date: Optional[str] = Field( |
54 | 55 | title="Start Date", |
55 | 56 | description="UTC date and time in the format 2017-01-25T00:00:00.000000Z. Any file modified before this date will not be replicated.", |
56 | | - examples=["2021-01-01T00:00:00.000000Z"], |
| 57 | + examples=[ |
| 58 | + "2021-01-01", |
| 59 | + "2021-01-01T00:00:00Z", |
| 60 | + "2021-01-01T00:00:00.000Z", |
| 61 | + "2021-01-01T00:00:00.000000Z", |
| 62 | + ], |
57 | 63 | format="date-time", |
58 | | - pattern="^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{6}Z$", |
59 | | - pattern_descriptor="YYYY-MM-DDTHH:mm:ss.SSSSSSZ", |
| 64 | + pattern=r"^[0-9]{4}-[0-9]{2}-[0-9]{2}(T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})?)?$", |
| 65 | + pattern_descriptor="YYYY-MM-DD, YYYY-MM-DDTHH:mm:ssZ, or YYYY-MM-DDTHH:mm:ss.SSSSSSZ", |
60 | 66 | order=1, |
61 | 67 | ) |
62 | 68 |
|
| 69 | + @validator("start_date", pre=True) |
| 70 | + def validate_start_date( |
| 71 | + cls, # noqa: N805 # Pydantic validators use cls, not self |
| 72 | + v: Optional[str], |
| 73 | + ) -> Optional[str]: |
| 74 | + """Validate that start_date is a parseable datetime string. |
| 75 | +
|
| 76 | + Uses ab_datetime_try_parse which accepts any common ISO8601/RFC3339 format, |
| 77 | + including formats with or without microseconds (e.g., both |
| 78 | + '2021-01-01T00:00:00Z' and '2021-01-01T00:00:00.000000Z' are valid). |
| 79 | + """ |
| 80 | + if v is None: |
| 81 | + return v |
| 82 | + parsed = ab_datetime_try_parse(v) |
| 83 | + if parsed is None: |
| 84 | + raise ValueError( |
| 85 | + f"'{v}' is not a valid datetime string. " |
| 86 | + "Please use a format like '2021-01-01T00:00:00Z' or '2021-01-01T00:00:00.000000Z'." |
| 87 | + ) |
| 88 | + return v |
| 89 | + |
63 | 90 | streams: List[FileBasedStreamConfig] = Field( |
64 | 91 | title="The list of streams to sync", |
65 | 92 | description='Each instance of this configuration defines a <a href="https://docs.airbyte.com/cloud/core-concepts#stream">stream</a>. Use this to define which files belong in the stream, their format, and how they should be parsed and validated. When sending data to warehouse destination such as Snowflake or BigQuery, each stream is a separate table.', |
|
0 commit comments