|
4 | 4 | import sqlalchemy |
5 | 5 | from sqlmodel import Session, SQLModel |
6 | 6 |
|
7 | | -from timescaledb.hypertables import sql_statements as sql |
8 | | -from timescaledb.hypertables import validators |
| 7 | +from timescaledb.hypertables.extractors import extract_model_hypertable_params |
| 8 | +from timescaledb.hypertables.schemas import HypertableCreateSchema |
9 | 9 |
|
10 | | -HYPERTABLE_INTERVAL_TYPE_SQL = { |
11 | | - "INTERVAL": sql.CREATE_HYPERTABLE_SQL_VIA_INTERVAL, |
12 | | - "TIMESTAMP": sql.CREATE_HYPERTABLE_SQL_VIA_TIMESTAMP, |
13 | | -} |
| 10 | +# from timescaledb.hypertables.schemas import HypertableParams |
14 | 11 |
|
15 | 12 |
|
16 | 13 | def create_hypertable( |
17 | 14 | session: Session, |
18 | | - model: Type[SQLModel], |
19 | | - if_not_exists: bool = True, |
20 | | - migrate_data: bool = True, |
21 | 15 | commit: bool = True, |
| 16 | + model: Type[SQLModel] = None, |
| 17 | + table_name: str = None, |
| 18 | + hypertable_options: dict = { |
| 19 | + "if_not_exists": True, |
| 20 | + "migrate_data": True, |
| 21 | + }, |
| 22 | + overwrite_model_params: bool = False, |
22 | 23 | ) -> None: |
23 | | - """ |
24 | | - Create a hypertable from a SQLModel class |
25 | | - """ |
26 | | - time_column = getattr(model, "__time_column__", None) |
27 | | - validators.validate_time_column(model, time_column) |
28 | | - interval = getattr(model, "__chunk_time_interval__", None) |
29 | | - validators.validate_chunk_time_interval(model, time_column, interval) |
30 | | - sql_template = None |
31 | | - cleaned_interval = None |
32 | | - if isinstance(interval, timedelta): |
33 | | - # Convert to microseconds |
34 | | - cleaned_interval = int(interval.total_seconds()) |
35 | | - sql_template = HYPERTABLE_INTERVAL_TYPE_SQL["TIMESTAMP"] |
36 | | - elif isinstance(interval, int): |
37 | | - # Microseconds |
38 | | - cleaned_interval = interval |
39 | | - sql_template = HYPERTABLE_INTERVAL_TYPE_SQL["TIMESTAMP"] |
40 | | - elif isinstance(interval, str): |
41 | | - # Such as INTERVAL 1 day |
42 | | - # or INTERVAL '2 weeks' |
43 | | - # pop the term "INTERVAL" |
44 | | - cleaned_interval = interval.replace("INTERVAL", "").strip() |
45 | | - # remove any extra quotes |
46 | | - cleaned_interval = cleaned_interval.replace("'", "").replace('"', "") |
47 | | - sql_template = HYPERTABLE_INTERVAL_TYPE_SQL["INTERVAL"] |
48 | | - else: |
49 | | - raise ValueError("Invalid interval type") |
50 | | - if sql_template is None or cleaned_interval is None: |
51 | | - raise ValueError("Invalid interval type") |
| 24 | + """Create a TimescaleDB hypertable from a SQLModel class. |
| 25 | +
|
| 26 | + This function converts a regular table into a TimescaleDB hypertable. Hypertable parameters |
| 27 | + can be specified either through model configuration or directly via hypertable_options. |
| 28 | +
|
| 29 | + Args: |
| 30 | + session (Session): SQLAlchemy session instance |
| 31 | + commit (bool, optional): Whether to commit the transaction after creating the hypertable. |
| 32 | + Defaults to True. |
| 33 | + model (Type[SQLModel]): The SQLModel class to convert into a hypertable. Must have |
| 34 | + hypertable parameters defined either in the model or via hypertable_options. |
| 35 | + table_name (str, optional): Override the table name. If None, uses the model's table name. |
| 36 | + hypertable_options (dict, optional): Additional hypertable configuration options. Defaults to: |
| 37 | + { |
| 38 | + "if_not_exists": True, # Skip if hypertable already exists |
| 39 | + "migrate_data": True, # Migrate existing data to the hypertable |
| 40 | + } |
| 41 | + overwrite_model_params (bool, optional): If True, hypertable_options will override any |
| 42 | + conflicting parameters defined in the model. If False, model parameters take precedence. |
| 43 | + Defaults to False. |
| 44 | +
|
| 45 | + Raises: |
| 46 | + ValueError: If model parameter is None. |
52 | 47 |
|
53 | | - table_name = getattr(model, "__tablename__", None) |
| 48 | + Example: |
| 49 | + ```python |
| 50 | + from sqlmodel import SQLModel |
| 51 | +
|
| 52 | + class Metrics(SQLModel, table=True): |
| 53 | + __hypertable_params__ = { |
| 54 | + "time_column": "timestamp", |
| 55 | + "chunk_time_interval": timedelta(days=7) |
| 56 | + } |
| 57 | + # ... model fields ... |
| 58 | +
|
| 59 | + # Create hypertable using model parameters |
| 60 | + create_hypertable(session, model=Metrics) |
| 61 | +
|
| 62 | + # Create hypertable with custom options |
| 63 | + create_hypertable( |
| 64 | + session, |
| 65 | + model=Metrics, |
| 66 | + hypertable_options={ |
| 67 | + "chunk_time_interval": timedelta(days=1), |
| 68 | + "if_not_exists": True |
| 69 | + }, |
| 70 | + overwrite_model_params=True |
| 71 | + ) |
| 72 | + ``` |
| 73 | + """ |
| 74 | + if model is None and table_name is None: |
| 75 | + raise ValueError("model or table_name is required") |
54 | 76 | params = { |
| 77 | + "model": None, |
55 | 78 | "table_name": table_name, |
56 | | - "time_column": time_column, |
57 | | - "chunk_time_interval": cleaned_interval, |
58 | | - "if_not_exists": "true" if if_not_exists else "false", |
59 | | - "migrate_data": "true" if migrate_data else "false", |
| 79 | + **hypertable_options, |
60 | 80 | } |
61 | | - query = sqlalchemy.text(sql_template).bindparams(**params) |
62 | | - compiled_query = str(query.compile(compile_kwargs={"literal_binds": True})) |
63 | | - session.execute(sqlalchemy.text(compiled_query)) |
| 81 | + if model is not None: |
| 82 | + model_params = extract_model_hypertable_params(model) |
| 83 | + params = {**model_params} |
| 84 | + if overwrite_model_params: |
| 85 | + params.update(**hypertable_options) |
| 86 | + |
| 87 | + schema = HypertableCreateSchema(**params) |
| 88 | + query = schema.to_sql_query() |
| 89 | + session.execute(sqlalchemy.text(query)) |
64 | 90 | if commit: |
65 | 91 | session.commit() |
0 commit comments