Skip to content

Commit 5c640b6

Browse files
authored
Merge pull request #363 from reef-technologies/list_files_with_no_responses_but_follow_up
Additional tests for listing files / versions – edge case scenario
2 parents a3f4daa + 7e96066 commit 5c640b6

File tree

3 files changed

+74
-2
lines changed

3 files changed

+74
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Infrastructure
10+
* Additional tests for listing files/versions
11+
912
## [1.18.0] - 2022-09-20
1013

1114
### Added

doc/source/advanced.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Streaming interface
4545

4646
Some object creation methods start writing data before reading the whole input (iterator). This can be used to write objects that do not have fully known contents without writing them first locally, so that they could be copied. Such usage pattern can be relevant to small devices which stream data to B2 from an external NAS, where caching large files such as media files or virtual machine images is not an option.
4747

48-
Please see :ref:`advanced method support table <advanced_methods_support_table>` to see where streaming interface is supported.
48+
Please see :ref:`advanced method support table <advanced_methods_support_table>` to see where streaming interface is supported.
4949

5050
Continuation
5151
============
@@ -184,7 +184,7 @@ Change the middle of the remote file
184184
For more information see :meth:`b2sdk.v2.Bucket.create_file`.
185185

186186

187-
Synthetize a file from local and remote parts
187+
Synthesize a file from local and remote parts
188188
=============================================
189189

190190
This is useful for expert usage patterns such as:

test/unit/bucket/test_bucket.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2173,3 +2173,72 @@ def test_file_info_3(self):
21732173
def test_file_info_4(self):
21742174
download_version = self.bucket.get_file_info_by_name('test.txt%253Ffoo%253Dbar')
21752175
assert download_version.file_name == 'test.txt%253Ffoo%253Dbar'
2176+
2177+
2178+
# Listing where every other response returns no entries and pointer to the next file
2179+
2180+
2181+
class EmptyListBucketSimulator(BucketSimulator):
2182+
def __init__(self, *args, **kwargs):
2183+
super().__init__(*args, **kwargs)
2184+
# Whenever we receive a list request, if it's the first time
2185+
# for this particular ``start_file_name``, we'll return
2186+
# an empty response pointing to the same file.
2187+
self.last_queried_file = None
2188+
2189+
def _should_return_empty(self, file_name: str) -> bool:
2190+
# Note that every other request is empty – the logic is as follows:
2191+
# 1st request – unknown start name – empty response
2192+
# 2nd request – known start name – normal response with a proper next filename
2193+
# 3rd request – unknown start name (as it's the next filename from the previous request) – empty response
2194+
# 4th request – known start name
2195+
# etc. This works especially well when using limiter of number of files fetched set to 1.
2196+
should_return_empty = self.last_queried_file != file_name
2197+
self.last_queried_file = file_name
2198+
return should_return_empty
2199+
2200+
def list_file_versions(
2201+
self,
2202+
account_auth_token,
2203+
start_file_name=None,
2204+
start_file_id=None,
2205+
max_file_count=None, # noqa
2206+
prefix=None,
2207+
):
2208+
if self._should_return_empty(start_file_name):
2209+
return dict(files=[], nextFileName=start_file_name, nextFileId=start_file_id)
2210+
return super().list_file_versions(
2211+
account_auth_token,
2212+
start_file_name,
2213+
start_file_id,
2214+
1, # Forcing only a single file per response.
2215+
prefix,
2216+
)
2217+
2218+
def list_file_names(
2219+
self,
2220+
account_auth_token,
2221+
start_file_name=None,
2222+
max_file_count=None, # noqa
2223+
prefix=None,
2224+
):
2225+
if self._should_return_empty(start_file_name):
2226+
return dict(files=[], nextFileName=start_file_name)
2227+
return super().list_file_names(
2228+
account_auth_token,
2229+
start_file_name,
2230+
1, # Forcing only a single file per response.
2231+
prefix,
2232+
)
2233+
2234+
2235+
class EmptyListSimulator(RawSimulator):
2236+
BUCKET_SIMULATOR_CLASS = EmptyListBucketSimulator
2237+
2238+
2239+
class TestEmptyListVersions(TestListVersions):
2240+
RAW_SIMULATOR_CLASS = EmptyListSimulator
2241+
2242+
2243+
class TestEmptyLs(TestLs):
2244+
RAW_SIMULATOR_CLASS = EmptyListSimulator

0 commit comments

Comments
 (0)