5454 from typing_extensions import deprecated # Python 3.12
5555
5656
57+ from urllib .parse import urlparse
58+
5759import pyarrow as pa
5860
5961from datafusion .catalog import (
@@ -611,6 +613,45 @@ def deregister_object_store(self, schema: str, host: str | None = None) -> None:
611613 """
612614 self .ctx .deregister_object_store (schema , host )
613615
616+ def _register_object_store_for_path (
617+ self , path : str | pathlib .Path , store : Any
618+ ) -> None :
619+ """Parse a URL path and register the given object store for its scheme and host.
620+
621+ This is a convenience helper used by methods like
622+ :py:meth:`register_parquet` and :py:meth:`read_parquet` to
623+ automatically register an object store when an ``object_store``
624+ parameter is provided.
625+
626+ Args:
627+ path: A URL-style path (e.g. ``"s3://bucket/key.parquet"`` or
628+ ``"file:///tmp/data.parquet"``).
629+ store: An object store instance to register.
630+
631+ Raises:
632+ ValueError: If the path does not contain a URL scheme, or if
633+ a non-file scheme is missing a host/bucket component.
634+ """
635+ parsed = urlparse (str (path ))
636+ if not parsed .scheme :
637+ msg = (
638+ f"Cannot determine object store URL from path { path !r} . "
639+ "The path must use a URL scheme (e.g. 's3://bucket/key')."
640+ )
641+ raise ValueError (msg )
642+ # file:// URLs typically have an empty netloc (e.g. file:///tmp/a.parquet)
643+ # For other schemes (s3, gs, az, https) the netloc (bucket/host) is required.
644+ if parsed .scheme != "file" and not parsed .netloc :
645+ msg = (
646+ f"Cannot determine object store URL from path { path !r} . "
647+ "The path must include a host or bucket "
648+ "(e.g. 's3://bucket/key')."
649+ )
650+ raise ValueError (msg )
651+ scheme = f"{ parsed .scheme } ://"
652+ host = parsed .netloc or None
653+ self .register_object_store (scheme , store , host = host )
654+
614655 def register_listing_table (
615656 self ,
616657 name : str ,
@@ -1028,6 +1069,7 @@ def register_parquet(
10281069 skip_metadata : bool = True ,
10291070 schema : pa .Schema | None = None ,
10301071 file_sort_order : Sequence [Sequence [SortKey ]] | None = None ,
1072+ object_store : Any | None = None ,
10311073 ) -> None :
10321074 """Register a Parquet file as a table.
10331075
@@ -1049,7 +1091,41 @@ def register_parquet(
10491091 file_sort_order: Sort order for the file. Each sort key can be
10501092 specified as a column name (``str``), an expression
10511093 (``Expr``), or a ``SortExpr``.
1052- """
1094+ object_store: A pre-configured object store instance (e.g.
1095+ :py:class:`~datafusion.object_store.AmazonS3`,
1096+ :py:class:`~datafusion.object_store.GoogleCloud`,
1097+ :py:class:`~datafusion.object_store.MicrosoftAzure`) to use
1098+ for accessing the file. When provided, the store is
1099+ automatically registered for the URL scheme and host parsed
1100+ from ``path``, removing the need to call
1101+ :py:meth:`register_object_store` separately. This is
1102+ especially useful in multi-threaded environments where
1103+ setting credentials via ``os.environ`` is not thread-safe.
1104+
1105+ Examples:
1106+ Register a local Parquet file:
1107+
1108+ >>> import datafusion
1109+ >>> ctx = datafusion.SessionContext()
1110+ >>> ctx.register_parquet("my_table", "data.parquet") # doctest: +SKIP
1111+
1112+ Register from S3 with inline credentials (thread-safe):
1113+
1114+ >>> from datafusion.object_store import AmazonS3 # doctest: +SKIP
1115+ >>> store = AmazonS3(
1116+ ... bucket_name="my-bucket",
1117+ ... region="us-east-1",
1118+ ... access_key_id="...",
1119+ ... secret_access_key="...",
1120+ ... ) # doctest: +SKIP
1121+ >>> ctx.register_parquet(
1122+ ... "my_table",
1123+ ... "s3://my-bucket/data.parquet",
1124+ ... object_store=store,
1125+ ... ) # doctest: +SKIP
1126+ """
1127+ if object_store is not None :
1128+ self ._register_object_store_for_path (path , object_store )
10531129 if table_partition_cols is None :
10541130 table_partition_cols = []
10551131 table_partition_cols = _convert_table_partition_cols (table_partition_cols )
@@ -1075,6 +1151,7 @@ def register_csv(
10751151 file_extension : str = ".csv" ,
10761152 file_compression_type : str | None = None ,
10771153 options : CsvReadOptions | None = None ,
1154+ object_store : Any | None = None ,
10781155 ) -> None :
10791156 """Register a CSV file as a table.
10801157
@@ -1096,7 +1173,14 @@ def register_csv(
10961173 file_compression_type: File compression type.
10971174 options: Set advanced options for CSV reading. This cannot be
10981175 combined with any of the other options in this method.
1099- """
1176+ object_store: A pre-configured object store instance to use for
1177+ accessing the file. When provided, the store is automatically
1178+ registered for the URL scheme and host parsed from ``path``.
1179+ """
1180+ if object_store is not None :
1181+ # For list paths, register from the first entry
1182+ register_path = path [0 ] if isinstance (path , list ) else path
1183+ self ._register_object_store_for_path (register_path , object_store )
11001184 if options is not None and (
11011185 schema is not None
11021186 or not has_header
@@ -1143,6 +1227,7 @@ def register_json(
11431227 file_extension : str = ".json" ,
11441228 table_partition_cols : list [tuple [str , str | pa .DataType ]] | None = None ,
11451229 file_compression_type : str | None = None ,
1230+ object_store : Any | None = None ,
11461231 ) -> None :
11471232 """Register a JSON file as a table.
11481233
@@ -1159,7 +1244,12 @@ def register_json(
11591244 selected for data input.
11601245 table_partition_cols: Partition columns.
11611246 file_compression_type: File compression type.
1247+ object_store: A pre-configured object store instance to use for
1248+ accessing the file. When provided, the store is automatically
1249+ registered for the URL scheme and host parsed from ``path``.
11621250 """
1251+ if object_store is not None :
1252+ self ._register_object_store_for_path (path , object_store )
11631253 if table_partition_cols is None :
11641254 table_partition_cols = []
11651255 table_partition_cols = _convert_table_partition_cols (table_partition_cols )
@@ -1180,6 +1270,7 @@ def register_avro(
11801270 schema : pa .Schema | None = None ,
11811271 file_extension : str = ".avro" ,
11821272 table_partition_cols : list [tuple [str , str | pa .DataType ]] | None = None ,
1273+ object_store : Any | None = None ,
11831274 ) -> None :
11841275 """Register an Avro file as a table.
11851276
@@ -1192,7 +1283,12 @@ def register_avro(
11921283 schema: The data source schema.
11931284 file_extension: File extension to select.
11941285 table_partition_cols: Partition columns.
1286+ object_store: A pre-configured object store instance to use for
1287+ accessing the file. When provided, the store is automatically
1288+ registered for the URL scheme and host parsed from ``path``.
11951289 """
1290+ if object_store is not None :
1291+ self ._register_object_store_for_path (path , object_store )
11961292 if table_partition_cols is None :
11971293 table_partition_cols = []
11981294 table_partition_cols = _convert_table_partition_cols (table_partition_cols )
@@ -1205,6 +1301,7 @@ def register_arrow(
12051301 schema : pa .Schema | None = None ,
12061302 file_extension : str = ".arrow" ,
12071303 table_partition_cols : list [tuple [str , str | pa .DataType ]] | None = None ,
1304+ object_store : Any | None = None ,
12081305 ) -> None :
12091306 """Register an Arrow IPC file as a table.
12101307
@@ -1217,6 +1314,9 @@ def register_arrow(
12171314 schema: The data source schema.
12181315 file_extension: File extension to select.
12191316 table_partition_cols: Partition columns.
1317+ object_store: A pre-configured object store instance to use for
1318+ accessing the file. When provided, the store is automatically
1319+ registered for the URL scheme and host parsed from ``path``.
12201320
12211321 Examples:
12221322 >>> import tempfile, os
@@ -1271,6 +1371,8 @@ def register_arrow(
12711371 30
12721372 ]
12731373 """
1374+ if object_store is not None :
1375+ self ._register_object_store_for_path (path , object_store )
12741376 if table_partition_cols is None :
12751377 table_partition_cols = []
12761378 table_partition_cols = _convert_table_partition_cols (table_partition_cols )
@@ -1690,6 +1792,7 @@ def read_json(
16901792 file_extension : str = ".json" ,
16911793 table_partition_cols : list [tuple [str , str | pa .DataType ]] | None = None ,
16921794 file_compression_type : str | None = None ,
1795+ object_store : Any | None = None ,
16931796 ) -> DataFrame :
16941797 """Read a line-delimited JSON data source.
16951798
@@ -1702,10 +1805,15 @@ def read_json(
17021805 selected for data input.
17031806 table_partition_cols: Partition columns.
17041807 file_compression_type: File compression type.
1808+ object_store: A pre-configured object store instance to use for
1809+ accessing the file. When provided, the store is automatically
1810+ registered for the URL scheme and host parsed from ``path``.
17051811
17061812 Returns:
17071813 DataFrame representation of the read JSON files.
17081814 """
1815+ if object_store is not None :
1816+ self ._register_object_store_for_path (path , object_store )
17091817 if table_partition_cols is None :
17101818 table_partition_cols = []
17111819 table_partition_cols = _convert_table_partition_cols (table_partition_cols )
@@ -1731,6 +1839,7 @@ def read_csv(
17311839 table_partition_cols : list [tuple [str , str | pa .DataType ]] | None = None ,
17321840 file_compression_type : str | None = None ,
17331841 options : CsvReadOptions | None = None ,
1842+ object_store : Any | None = None ,
17341843 ) -> DataFrame :
17351844 """Read a CSV data source.
17361845
@@ -1750,10 +1859,16 @@ def read_csv(
17501859 file_compression_type: File compression type.
17511860 options: Set advanced options for CSV reading. This cannot be
17521861 combined with any of the other options in this method.
1862+ object_store: A pre-configured object store instance to use for
1863+ accessing the file. When provided, the store is automatically
1864+ registered for the URL scheme and host parsed from ``path``.
17531865
17541866 Returns:
17551867 DataFrame representation of the read CSV files
17561868 """
1869+ if object_store is not None :
1870+ register_path = path [0 ] if isinstance (path , list ) else path
1871+ self ._register_object_store_for_path (register_path , object_store )
17571872 if options is not None and (
17581873 schema is not None
17591874 or not has_header
@@ -1803,6 +1918,7 @@ def read_parquet(
18031918 skip_metadata : bool = True ,
18041919 schema : pa .Schema | None = None ,
18051920 file_sort_order : Sequence [Sequence [SortKey ]] | None = None ,
1921+ object_store : Any | None = None ,
18061922 ) -> DataFrame :
18071923 """Read a Parquet source into a :py:class:`~datafusion.dataframe.Dataframe`.
18081924
@@ -1822,10 +1938,43 @@ def read_parquet(
18221938 file_sort_order: Sort order for the file. Each sort key can be
18231939 specified as a column name (``str``), an expression
18241940 (``Expr``), or a ``SortExpr``.
1941+ object_store: A pre-configured object store instance (e.g.
1942+ :py:class:`~datafusion.object_store.AmazonS3`,
1943+ :py:class:`~datafusion.object_store.GoogleCloud`,
1944+ :py:class:`~datafusion.object_store.MicrosoftAzure`) to use
1945+ for accessing the file. When provided, the store is
1946+ automatically registered for the URL scheme and host parsed
1947+ from ``path``, removing the need to call
1948+ :py:meth:`register_object_store` separately. This is
1949+ especially useful in multi-threaded environments where
1950+ setting credentials via ``os.environ`` is not thread-safe.
18251951
18261952 Returns:
18271953 DataFrame representation of the read Parquet files
1828- """
1954+
1955+ Examples:
1956+ Read a local Parquet file:
1957+
1958+ >>> import datafusion
1959+ >>> ctx = datafusion.SessionContext()
1960+ >>> df = ctx.read_parquet("data.parquet") # doctest: +SKIP
1961+
1962+ Read from S3 with inline credentials (thread-safe):
1963+
1964+ >>> from datafusion.object_store import AmazonS3 # doctest: +SKIP
1965+ >>> store = AmazonS3(
1966+ ... bucket_name="my-bucket",
1967+ ... region="us-east-1",
1968+ ... access_key_id="...",
1969+ ... secret_access_key="...",
1970+ ... ) # doctest: +SKIP
1971+ >>> df = ctx.read_parquet(
1972+ ... "s3://my-bucket/data.parquet",
1973+ ... object_store=store,
1974+ ... ) # doctest: +SKIP
1975+ """
1976+ if object_store is not None :
1977+ self ._register_object_store_for_path (path , object_store )
18291978 if table_partition_cols is None :
18301979 table_partition_cols = []
18311980 table_partition_cols = _convert_table_partition_cols (table_partition_cols )
@@ -1848,6 +1997,7 @@ def read_avro(
18481997 schema : pa .Schema | None = None ,
18491998 file_partition_cols : list [tuple [str , str | pa .DataType ]] | None = None ,
18501999 file_extension : str = ".avro" ,
2000+ object_store : Any | None = None ,
18512001 ) -> DataFrame :
18522002 """Create a :py:class:`DataFrame` for reading Avro data source.
18532003
@@ -1856,10 +2006,15 @@ def read_avro(
18562006 schema: The data source schema.
18572007 file_partition_cols: Partition columns.
18582008 file_extension: File extension to select.
2009+ object_store: A pre-configured object store instance to use for
2010+ accessing the file. When provided, the store is automatically
2011+ registered for the URL scheme and host parsed from ``path``.
18592012
18602013 Returns:
18612014 DataFrame representation of the read Avro file
18622015 """
2016+ if object_store is not None :
2017+ self ._register_object_store_for_path (path , object_store )
18632018 if file_partition_cols is None :
18642019 file_partition_cols = []
18652020 file_partition_cols = _convert_table_partition_cols (file_partition_cols )
@@ -1873,6 +2028,7 @@ def read_arrow(
18732028 schema : pa .Schema | None = None ,
18742029 file_extension : str = ".arrow" ,
18752030 file_partition_cols : list [tuple [str , str | pa .DataType ]] | None = None ,
2031+ object_store : Any | None = None ,
18762032 ) -> DataFrame :
18772033 """Create a :py:class:`DataFrame` for reading an Arrow IPC data source.
18782034
@@ -1881,6 +2037,9 @@ def read_arrow(
18812037 schema: The data source schema.
18822038 file_extension: File extension to select.
18832039 file_partition_cols: Partition columns.
2040+ object_store: A pre-configured object store instance to use for
2041+ accessing the file. When provided, the store is automatically
2042+ registered for the URL scheme and host parsed from ``path``.
18842043
18852044 Returns:
18862045 DataFrame representation of the read Arrow IPC file.
@@ -1932,6 +2091,8 @@ def read_arrow(
19322091 3
19332092 ]
19342093 """
2094+ if object_store is not None :
2095+ self ._register_object_store_for_path (path , object_store )
19352096 if file_partition_cols is None :
19362097 file_partition_cols = []
19372098 file_partition_cols = _convert_table_partition_cols (file_partition_cols )
0 commit comments