Skip to content

Support Vended Credentials for Azure Data Lake Store #1146

Description

@c-thiel

Feature Request / Improvement

Vended-Credentials for Azure Data Lake Store are supported by Java. For getTable / createTable endpoints, the catalog returns a "config" that looks like:

"config": {
"adls.sas-token.<storage-account-name>.dfs.core.windows.net": "sv=2023-11-03&st=2024-09-08T11%3A34%3A08Z&....(rest of SAS Token)"
}

This is currently not respected by Pyiceberg. Instead we get the error:

ValueError: unable to connect to account for Must provide either a connection_string or account_name with credentials!!

Full Traceback:

---------------------------------------------------------------------------ValueErrorTraceback (mostrecentcalllast)
File [/opt/conda/lib/python3.11/site-packages/adlfs/spec.py:515](http://localhost:8888/opt/conda/lib/python3.11/site-packages/adlfs/spec.py#line=514), in AzureBlobFileSystem.do_connect(self)514else:
-->515raiseValueError(
516"Must provide either a connection_string or account_name with credentials!!"517 )
519exceptRuntimeError:
ValueError: Mustprovideeitheraconnection_stringoraccount_namewithcredentials!!
Duringhandlingoftheaboveexception, anotherexceptionoccurred:
ValueErrorTraceback (mostrecentcalllast)
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py:509](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py#line=508), in Transaction.append(self, df, snapshot_properties)506data_files=_dataframe_to_data_files(
507table_metadata=self.table_metadata, write_uuid=append_files.commit_uuid, df=df, io=self._table.io508 )
-->509fordata_fileindata_files:
510append_files.append_data_file(data_file)
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/io/pyarrow.py:2354](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/io/pyarrow.py#line=2353), in _dataframe_to_data_files(table_metadata, df, io, write_uuid, counter)2353iftable_metadata.spec().is_unpartitioned():
->2354yieldfromwrite_file(
2355io=io,
2356table_metadata=table_metadata,
2357tasks=iter([
2358WriteTask(write_uuid=write_uuid, task_id=next(counter), record_batches=batches, schema=task_schema)
2359forbatchesinbin_pack_arrow_table(df, target_file_size)
2360 ]),
2361 )
2362else:
File [/opt/conda/lib/python3.11/concurrent/futures/_base.py:619](http://localhost:8888/opt/conda/lib/python3.11/concurrent/futures/_base.py#line=618), in Executor.map.<locals>.result_iterator()618iftimeoutisNone:
-->619yield_result_or_cancel(fs.pop())
620else:
File [/opt/conda/lib/python3.11/concurrent/futures/_base.py:317](http://localhost:8888/opt/conda/lib/python3.11/concurrent/futures/_base.py#line=316), in _result_or_cancel(***failed resolving arguments***)316try:
-->317returnfut.result(timeout)
318finally:
File [/opt/conda/lib/python3.11/concurrent/futures/_base.py:456](http://localhost:8888/opt/conda/lib/python3.11/concurrent/futures/_base.py#line=455), in Future.result(self, timeout)455elifself._state==FINISHED:
-->456returnself.__get_result()
457else:
File [/opt/conda/lib/python3.11/concurrent/futures/_base.py:401](http://localhost:8888/opt/conda/lib/python3.11/concurrent/futures/_base.py#line=400), in Future.__get_result(self)400try:
-->401raiseself._exception402finally:
403# Break a reference cycle with the exception in self._exceptionFile [/opt/conda/lib/python3.11/concurrent/futures/thread.py:58](http://localhost:8888/opt/conda/lib/python3.11/concurrent/futures/thread.py#line=57), in _WorkItem.run(self)57try:
--->58result=self.fn(*self.args, **self.kwargs)
59exceptBaseExceptionasexc:
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/io/pyarrow.py:2173](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/io/pyarrow.py#line=2172), in write_file.<locals>.write_parquet(task)2172file_path=f'{table_metadata.location}[/data/](http://localhost:8888/data/){task.generate_data_file_path("parquet")}'->2173fo=io.new_output(file_path)
2174withfo.create(overwrite=True) asfos:
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py:331](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py#line=330), in FsspecFileIO.new_output(self, location)330uri=urlparse(location)
-->331fs=self.get_fs(uri.scheme)
332returnFsspecOutputFile(location=location, fs=fs)
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py:355](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py#line=354), in FsspecFileIO._get_fs(self, scheme)354raiseValueError(f"No registered filesystem for scheme: {scheme}")
-->355returnself._scheme_to_fs[scheme](self.properties)
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py:179](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py#line=178), in _adlfs(properties)177fromadlfsimportAzureBlobFileSystem-->179returnAzureBlobFileSystem(
180connection_string=properties.get(ADLFS_CONNECTION_STRING),
181account_name=properties.get(ADLFS_ACCOUNT_NAME),
182account_key=properties.get(ADLFS_ACCOUNT_KEY),
183sas_token=properties.get(ADLFS_SAS_TOKEN),
184tenant_id=properties.get(ADLFS_TENANT_ID),
185client_id=properties.get(ADLFS_CLIENT_ID),
186client_secret=properties.get(ADLFS_ClIENT_SECRET),
187 )
File [/opt/conda/lib/python3.11/site-packages/fsspec/spec.py:80](http://localhost:8888/opt/conda/lib/python3.11/site-packages/fsspec/spec.py#line=79), in _Cached.__call__(cls, *args, **kwargs)79else:
--->80obj=super().__call__(*args, **kwargs)
81# Setting _fs_token here causes some static linters to complain.File [/opt/conda/lib/python3.11/site-packages/adlfs/spec.py:344](http://localhost:8888/opt/conda/lib/python3.11/site-packages/adlfs/spec.py#line=343), in AzureBlobFileSystem.__init__(self, account_name, account_key, connection_string, credential, sas_token, request_session, socket_timeout, blocksize, client_id, client_secret, tenant_id, anon, location_mode, loop, asynchronous, default_fill_cache, default_cache_type, version_aware, assume_container_exists, max_concurrency, timeout, connection_timeout, read_timeout, account_host, **kwargs)339 (
340self.credential,
341self.sync_credential,
342 ) =self._get_default_azure_credential(**kwargs)
-->344self.do_connect()
345weakref.finalize(self, sync, self.loop, close_service_client, self)
File [/opt/conda/lib/python3.11/site-packages/adlfs/spec.py:525](http://localhost:8888/opt/conda/lib/python3.11/site-packages/adlfs/spec.py#line=524), in AzureBlobFileSystem.do_connect(self)524exceptExceptionase:
-->525raiseValueError(f"unable to connect to account for {e}")
ValueError: unabletoconnecttoaccountforMustprovideeitheraconnection_stringoraccount_namewithcredentials!!
Duringhandlingoftheaboveexception, anotherexceptionoccurred:
ValueErrorTraceback (mostrecentcalllast)
File [/opt/conda/lib/python3.11/site-packages/adlfs/spec.py:515](http://localhost:8888/opt/conda/lib/python3.11/site-packages/adlfs/spec.py#line=514), in AzureBlobFileSystem.do_connect(self)514else:
-->515raiseValueError(
516"Must provide either a connection_string or account_name with credentials!!"517 )
519exceptRuntimeError:
ValueError: Mustprovideeitheraconnection_stringoraccount_namewithcredentials!!
Duringhandlingoftheaboveexception, anotherexceptionoccurred:
ValueErrorTraceback (mostrecentcalllast)
CellIn[8], line1---->1table.append(pa_df)
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py:1578](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py#line=1577), in Table.append(self, df, snapshot_properties)1570""" 1571 Shorthand API for appending a PyArrow table to the table. 1572  (...) 1575 snapshot_properties: Custom properties to be added to the snapshot summary 1576 """1577withself.transaction() astx:
->1578tx.append(df=df, snapshot_properties=snapshot_properties)
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py:503](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py#line=502), in Transaction.append(self, df, snapshot_properties)500update_snapshot=self.update_snapshot(snapshot_properties=snapshot_properties)
501append_method=update_snapshot.merge_appendifmanifest_merge_enabledelseupdate_snapshot.fast_append-->503withappend_method() asappend_files:
504# skip writing data files if the dataframe is empty505ifdf.shape[0] >0:
506data_files=_dataframe_to_data_files(
507table_metadata=self.table_metadata, write_uuid=append_files.commit_uuid, df=df, io=self._table.io508 )
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py:2094](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py#line=2093), in UpdateTableMetadata.__exit__(self, _, value, traceback)2092def__exit__(self, _: Any, value: Any, traceback: Any) ->None:
2093"""Close and commit the change."""->2094self.commit()
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py:2090](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py#line=2089), in UpdateTableMetadata.commit(self)2089defcommit(self) ->None:
->2090self._transaction._apply(*self._commit())
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py:3220](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/table/__init__.py#line=3219), in _SnapshotProducer._commit(self)3210summary=self._summary(self.snapshot_properties)
3212manifest_list_file_path=_generate_manifest_list_path(
3213location=self._transaction.table_metadata.location,
3214snapshot_id=self._snapshot_id,
3215attempt=0,
3216commit_uuid=self.commit_uuid,
3217 )
3218withwrite_manifest_list(
3219format_version=self._transaction.table_metadata.format_version,
->3220output_file=self._io.new_output(manifest_list_file_path),
3221snapshot_id=self._snapshot_id,
3222parent_snapshot_id=self._parent_snapshot_id,
3223sequence_number=next_sequence_number,
3224 ) aswriter:
3225writer.add_manifests(new_manifests)
3227snapshot=Snapshot(
3228snapshot_id=self._snapshot_id,
3229parent_snapshot_id=self._parent_snapshot_id,
(...)
3233schema_id=self._transaction.table_metadata.current_schema_id,
3234 )
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py:331](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py#line=330), in FsspecFileIO.new_output(self, location)322"""Get an FsspecOutputFile instance to write bytes to the file at the given location. 323  324 Args: (...) 328 FsspecOutputFile: An FsspecOutputFile instance for the given location. 329 """330uri=urlparse(location)
-->331fs=self.get_fs(uri.scheme)
332returnFsspecOutputFile(location=location, fs=fs)
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py:355](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py#line=354), in FsspecFileIO._get_fs(self, scheme)353ifschemenotinself._scheme_to_fs:
354raiseValueError(f"No registered filesystem for scheme: {scheme}")
-->355returnself._scheme_to_fs[scheme](self.properties)
File [/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py:179](http://localhost:8888/opt/conda/lib/python3.11/site-packages/pyiceberg/io/fsspec.py#line=178), in _adlfs(properties)176def_adlfs(properties: Properties) ->AbstractFileSystem:
177fromadlfsimportAzureBlobFileSystem-->179returnAzureBlobFileSystem(
180connection_string=properties.get(ADLFS_CONNECTION_STRING),
181account_name=properties.get(ADLFS_ACCOUNT_NAME),
182account_key=properties.get(ADLFS_ACCOUNT_KEY),
183sas_token=properties.get(ADLFS_SAS_TOKEN),
184tenant_id=properties.get(ADLFS_TENANT_ID),
185client_id=properties.get(ADLFS_CLIENT_ID),
186client_secret=properties.get(ADLFS_ClIENT_SECRET),
187 )
File [/opt/conda/lib/python3.11/site-packages/fsspec/spec.py:80](http://localhost:8888/opt/conda/lib/python3.11/site-packages/fsspec/spec.py#line=79), in _Cached.__call__(cls, *args, **kwargs)78returncls._cache[token]
79else:
--->80obj=super().__call__(*args, **kwargs)
81# Setting _fs_token here causes some static linters to complain.82obj._fs_token_=tokenFile [/opt/conda/lib/python3.11/site-packages/adlfs/spec.py:344](http://localhost:8888/opt/conda/lib/python3.11/site-packages/adlfs/spec.py#line=343), in AzureBlobFileSystem.__init__(self, account_name, account_key, connection_string, credential, sas_token, request_session, socket_timeout, blocksize, client_id, client_secret, tenant_id, anon, location_mode, loop, asynchronous, default_fill_cache, default_cache_type, version_aware, assume_container_exists, max_concurrency, timeout, connection_timeout, read_timeout, account_host, **kwargs)333if (
334self.credentialisNone335andself.anonisFalse336andself.sas_tokenisNone337andself.account_keyisNone338 ):
339 (
340self.credential,
341self.sync_credential,
342 ) =self._get_default_azure_credential(**kwargs)
-->344self.do_connect()
345weakref.finalize(self, sync, self.loop, close_service_client, self)
347ifself.credentialisnotNone:
File [/opt/conda/lib/python3.11/site-packages/adlfs/spec.py:525](http://localhost:8888/opt/conda/lib/python3.11/site-packages/adlfs/spec.py#line=524), in AzureBlobFileSystem.do_connect(self)522self.do_connect()
524exceptExceptionase:
-->525raiseValueError(f"unable to connect to account for {e}")
ValueError: unabletoconnecttoaccountforMustprovideeitheraconnection_stringoraccount_namewithcredentials!!

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions