|
1 | 1 | import importlib.util |
2 | 2 | import os |
3 | 3 | import urllib.parse |
4 | | -from typing import Any |
| 4 | +from typing import Any, List, Literal, Optional, TYPE_CHECKING |
5 | 5 |
|
6 | 6 | import pyarrow as pa |
| 7 | + |
| 8 | +if TYPE_CHECKING: |
| 9 | + import pandas as pd |
| 10 | + import polars as pl |
7 | 11 | from pyarrow import ArrowException |
8 | 12 |
|
9 | 13 | from influxdb_client_3.exceptions import InfluxDB3ClientQueryError |
@@ -385,6 +389,77 @@ def write(self, record=None, database=None, **kwargs): |
385 | 389 | except InfluxDBError as e: |
386 | 390 | raise e |
387 | 391 |
|
| 392 | + def write_dataframe( |
| 393 | + self, |
| 394 | + df: "pd.DataFrame | pl.DataFrame", |
| 395 | + measurement: str, |
| 396 | + timestamp_column: str, |
| 397 | + tags: Optional[List[str]] = None, |
| 398 | + timestamp_timezone: Optional[str] = None, |
| 399 | + database: Optional[str] = None, |
| 400 | + **kwargs |
| 401 | + ): |
| 402 | + """ |
| 403 | + Write a DataFrame to InfluxDB. |
| 404 | +
|
| 405 | + This method supports both pandas and polars DataFrames, automatically detecting |
| 406 | + the DataFrame type and using the appropriate serializer. |
| 407 | +
|
| 408 | + :param df: The DataFrame to write. Can be a pandas or polars DataFrame. |
| 409 | + :type df: pandas.DataFrame or polars.DataFrame |
| 410 | + :param measurement: The name of the measurement to write to. |
| 411 | + :type measurement: str |
| 412 | + :param timestamp_column: The name of the column containing timestamps. |
| 413 | + This parameter is required for consistency between pandas and polars. |
| 414 | + :type timestamp_column: str |
| 415 | + :param tags: List of column names to use as tags. Remaining columns will be fields. |
| 416 | + :type tags: list[str], optional |
| 417 | + :param timestamp_timezone: Timezone for the timestamp column (e.g., 'UTC', 'America/New_York'). |
| 418 | + :type timestamp_timezone: str, optional |
| 419 | + :param database: The database to write to. If not provided, uses the database from initialization. |
| 420 | + :type database: str, optional |
| 421 | + :param kwargs: Additional arguments to pass to the write API. |
| 422 | + :raises TypeError: If df is not a pandas or polars DataFrame. |
| 423 | + :raises InfluxDBError: If there is an error writing to the database. |
| 424 | +
|
| 425 | + Example: |
| 426 | + >>> import pandas as pd |
| 427 | + >>> df = pd.DataFrame({ |
| 428 | + ... 'time': pd.to_datetime(['2024-01-01', '2024-01-02']), |
| 429 | + ... 'city': ['London', 'Paris'], |
| 430 | + ... 'temperature': [15.0, 18.0] |
| 431 | + ... }) |
| 432 | + >>> client.write_dataframe( |
| 433 | + ... df, |
| 434 | + ... measurement='weather', |
| 435 | + ... timestamp_column='time', |
| 436 | + ... tags=['city'] |
| 437 | + ... ) |
| 438 | + """ |
| 439 | + if database is None: |
| 440 | + database = self._database |
| 441 | + |
| 442 | + # Detect DataFrame type |
| 443 | + df_type = str(type(df)) |
| 444 | + if 'pandas' not in df_type and 'polars' not in df_type: |
| 445 | + raise TypeError( |
| 446 | + f"Expected a pandas or polars DataFrame, but got {type(df).__name__}. " |
| 447 | + "Please pass a valid DataFrame object." |
| 448 | + ) |
| 449 | + |
| 450 | + try: |
| 451 | + return self._write_api.write( |
| 452 | + bucket=database, |
| 453 | + record=df, |
| 454 | + data_frame_measurement_name=measurement, |
| 455 | + data_frame_tag_columns=tags or [], |
| 456 | + data_frame_timestamp_column=timestamp_column, |
| 457 | + data_frame_timestamp_timezone=timestamp_timezone, |
| 458 | + **kwargs |
| 459 | + ) |
| 460 | + except InfluxDBError as e: |
| 461 | + raise e |
| 462 | + |
388 | 463 | def write_file(self, file, measurement_name=None, tag_columns=None, timestamp_column='time', database=None, |
389 | 464 | file_parser_options=None, **kwargs): |
390 | 465 | """ |
@@ -467,6 +542,51 @@ def query(self, query: str, language: str = "sql", mode: str = "all", database: |
467 | 542 | except ArrowException as e: |
468 | 543 | raise InfluxDB3ClientQueryError(f"Error while executing query: {e}") |
469 | 544 |
|
| 545 | + def query_dataframe( |
| 546 | + self, |
| 547 | + query: str, |
| 548 | + language: str = "sql", |
| 549 | + database: Optional[str] = None, |
| 550 | + frame_type: Literal["pandas", "polars"] = "pandas", |
| 551 | + **kwargs |
| 552 | + ) -> "pd.DataFrame | pl.DataFrame": |
| 553 | + """ |
| 554 | + Query data from InfluxDB and return as a DataFrame. |
| 555 | +
|
| 556 | + This is a convenience method that wraps query() and returns the result |
| 557 | + directly as a pandas or polars DataFrame. |
| 558 | +
|
| 559 | + :param query: The query to execute on the database. |
| 560 | + :type query: str |
| 561 | + :param language: The query language to use. Should be "sql" or "influxql". Defaults to "sql". |
| 562 | + :type language: str |
| 563 | + :param database: The database to query from. If not provided, uses the database from initialization. |
| 564 | + :type database: str, optional |
| 565 | + :param frame_type: The type of DataFrame to return. Either "pandas" or "polars". Defaults to "pandas". |
| 566 | + :type frame_type: Literal["pandas", "polars"] |
| 567 | + :param kwargs: Additional arguments to pass to the query API. |
| 568 | + :keyword query_parameters: Query parameters as a dictionary of key-value pairs. |
| 569 | + :return: Query result as a pandas or polars DataFrame. |
| 570 | + :rtype: pandas.DataFrame or polars.DataFrame |
| 571 | + :raises ImportError: If polars is requested but not installed. |
| 572 | +
|
| 573 | + Example: |
| 574 | + >>> # Query and get a pandas DataFrame |
| 575 | + >>> df = client.query_dataframe("SELECT * FROM weather WHERE city = 'London'") |
| 576 | + >>> |
| 577 | + >>> # Query and get a polars DataFrame |
| 578 | + >>> df = client.query_dataframe( |
| 579 | + ... "SELECT * FROM weather", |
| 580 | + ... frame_type="polars" |
| 581 | + ... ) |
| 582 | + """ |
| 583 | + if frame_type == "polars" and polars is False: |
| 584 | + raise ImportError( |
| 585 | + "Polars is not installed. Please install it with `pip install polars`." |
| 586 | + ) |
| 587 | + |
| 588 | + return self.query(query=query, language=language, mode=frame_type, database=database, **kwargs) |
| 589 | + |
470 | 590 | async def query_async(self, query: str, language: str = "sql", mode: str = "all", database: str = None, **kwargs): |
471 | 591 | """Query data from InfluxDB asynchronously. |
472 | 592 |
|
|
0 commit comments