PYTHON-4946 - Add GridFSBucket.rename_by_name (#2219)

This commit is contained in:
Noah Stapp 2025-03-31 14:02:06 -04:00 committed by GitHub
parent 8675a163df
commit a3f3ec52bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 413 additions and 2 deletions

View File

@ -9,6 +9,8 @@ PyMongo 4.12 brings a number of changes including:
- Support for configuring DEK cache lifetime via the ``key_expiration_ms`` argument to
:class:`~pymongo.encryption_options.AutoEncryptionOpts`.
- Support for $lookup in CSFLE and QE supported on MongoDB 8.1+.
- Added :meth:`gridfs.asynchronous.grid_file.AsyncGridFSBucket.rename_by_name` and :meth:`gridfs.grid_file.GridFSBucket.rename_by_name`
for more performant renaming of a file with multiple revisions.
- Added :meth:`gridfs.asynchronous.grid_file.AsyncGridFSBucket.delete_by_name` and :meth:`gridfs.grid_file.GridFSBucket.delete_by_name`
for more performant deletion of a file with multiple revisions.
- AsyncMongoClient no longer performs DNS resolution for "mongodb+srv://" connection strings on creation.

View File

@ -1050,6 +1050,35 @@ class AsyncGridFSBucket:
"matched file_id %i" % (new_filename, file_id)
)
async def rename_by_name(
self, filename: str, new_filename: str, session: Optional[AsyncClientSession] = None
) -> None:
"""Renames the stored file with the specified filename.
For example::
my_db = AsyncMongoClient().test
fs = AsyncGridFSBucket(my_db)
await fs.upload_from_stream("test_file", "data I want to store!")
await fs.rename_by_name("test_file", "new_test_name")
Raises :exc:`~gridfs.errors.NoFile` if no file with the given filename exists.
:param filename: The filename of the file to be renamed.
:param new_filename: The new name of the file.
:param session: a :class:`~pymongo.client_session.AsyncClientSession`
.. versionadded:: 4.12
"""
_disallow_transactions(session)
result = await self._files.update_many(
{"filename": filename}, {"$set": {"filename": new_filename}}, session=session
)
if not result.matched_count:
raise NoFile(
f"no files could be renamed {new_filename!r} because none matched filename {filename!r}"
)
class AsyncGridIn:
"""Class to write data to GridFS."""

View File

@ -1042,6 +1042,35 @@ class GridFSBucket:
"matched file_id %i" % (new_filename, file_id)
)
def rename_by_name(
self, filename: str, new_filename: str, session: Optional[ClientSession] = None
) -> None:
"""Renames the stored file with the specified filename.
For example::
my_db = MongoClient().test
fs = GridFSBucket(my_db)
fs.upload_from_stream("test_file", "data I want to store!")
fs.rename_by_name("test_file", "new_test_name")
Raises :exc:`~gridfs.errors.NoFile` if no file with the given filename exists.
:param filename: The filename of the file to be renamed.
:param new_filename: The new name of the file.
:param session: a :class:`~pymongo.client_session.ClientSession`
.. versionadded:: 4.12
"""
_disallow_transactions(session)
result = self._files.update_many(
{"filename": filename}, {"$set": {"filename": new_filename}}, session=session
)
if not result.matched_count:
raise NoFile(
f"no files could be renamed {new_filename!r} because none matched filename {filename!r}"
)
class GridIn:
"""Class to write data to GridFS."""

View File

@ -450,6 +450,19 @@ class TestGridfs(AsyncIntegrationTest):
b"testing", await (await self.fs.open_download_stream_by_name("second_name")).read()
)
async def test_rename_by_name(self):
_id = await self.fs.upload_from_stream("first_name", b"testing")
self.assertEqual(
b"testing", await (await self.fs.open_download_stream_by_name("first_name")).read()
)
await self.fs.rename_by_name("first_name", "second_name")
with self.assertRaises(NoFile):
await self.fs.open_download_stream_by_name("first_name")
self.assertEqual(
b"testing", await (await self.fs.open_download_stream_by_name("second_name")).read()
)
@patch("gridfs.asynchronous.grid_file._UPLOAD_BUFFER_SIZE", 5)
async def test_abort(self):
gin = self.fs.open_upload_stream("test_filename", chunk_size_bytes=5)

View File

@ -541,6 +541,7 @@ class TestSession(AsyncIntegrationTest):
(bucket.download_to_stream_by_name, ["f", sio], {}),
(find, [], {}),
(bucket.rename, [1, "f2"], {}),
(bucket.rename_by_name, ["f2", "f3"], {}),
# Delete both files so _test_ops can run these operations twice.
(bucket.delete, [1], {}),
(bucket.delete_by_name, ["f"], {}),

View File

@ -295,7 +295,14 @@ class TestTransactions(AsyncTransactionsBase):
"new-name",
),
),
(bucket.delete_by_name, ("new-name",)),
(
bucket.rename_by_name,
(
"new-name",
"new-name2",
),
),
(bucket.delete_by_name, ("new-name2",)),
]
async with client.start_session() as s, await s.start_transaction():

View File

@ -0,0 +1,313 @@
{
"description": "gridfs-renameByName",
"schemaVersion": "1.0",
"createEntities": [
{
"client": {
"id": "client0"
}
},
{
"database": {
"id": "database0",
"client": "client0",
"databaseName": "gridfs-tests"
}
},
{
"bucket": {
"id": "bucket0",
"database": "database0"
}
},
{
"collection": {
"id": "bucket0_files_collection",
"database": "database0",
"collectionName": "fs.files"
}
},
{
"collection": {
"id": "bucket0_chunks_collection",
"database": "database0",
"collectionName": "fs.chunks"
}
}
],
"initialData": [
{
"collectionName": "fs.files",
"databaseName": "gridfs-tests",
"documents": [
{
"_id": {
"$oid": "000000000000000000000001"
},
"length": 0,
"chunkSize": 4,
"uploadDate": {
"$date": "1970-01-01T00:00:00.000Z"
},
"filename": "filename",
"metadata": {}
},
{
"_id": {
"$oid": "000000000000000000000002"
},
"length": 0,
"chunkSize": 4,
"uploadDate": {
"$date": "1970-01-01T00:00:00.000Z"
},
"filename": "filename",
"metadata": {}
},
{
"_id": {
"$oid": "000000000000000000000003"
},
"length": 2,
"chunkSize": 4,
"uploadDate": {
"$date": "1970-01-01T00:00:00.000Z"
},
"filename": "filename",
"metadata": {}
},
{
"_id": {
"$oid": "000000000000000000000004"
},
"length": 8,
"chunkSize": 4,
"uploadDate": {
"$date": "1970-01-01T00:00:00.000Z"
},
"filename": "otherfilename",
"metadata": {}
}
]
},
{
"collectionName": "fs.chunks",
"databaseName": "gridfs-tests",
"documents": [
{
"_id": {
"$oid": "000000000000000000000001"
},
"files_id": {
"$oid": "000000000000000000000002"
},
"n": 0,
"data": {
"$binary": {
"base64": "",
"subType": "00"
}
}
},
{
"_id": {
"$oid": "000000000000000000000002"
},
"files_id": {
"$oid": "000000000000000000000003"
},
"n": 0,
"data": {
"$binary": {
"base64": "",
"subType": "00"
}
}
},
{
"_id": {
"$oid": "000000000000000000000003"
},
"files_id": {
"$oid": "000000000000000000000004"
},
"n": 0,
"data": {
"$binary": {
"base64": "",
"subType": "00"
}
}
},
{
"_id": {
"$oid": "000000000000000000000004"
},
"files_id": {
"$oid": "000000000000000000000004"
},
"n": 1,
"data": {
"$binary": {
"base64": "",
"subType": "00"
}
}
}
]
}
],
"tests": [
{
"description": "rename when multiple revisions of the file exist",
"operations": [
{
"name": "renameByName",
"object": "bucket0",
"arguments": {
"filename": "filename",
"newFilename": "newfilename"
}
}
],
"outcome": [
{
"collectionName": "fs.files",
"databaseName": "gridfs-tests",
"documents": [
{
"_id": {
"$oid": "000000000000000000000001"
},
"length": 0,
"chunkSize": 4,
"uploadDate": {
"$date": "1970-01-01T00:00:00.000Z"
},
"filename": "newfilename",
"metadata": {}
},
{
"_id": {
"$oid": "000000000000000000000002"
},
"length": 0,
"chunkSize": 4,
"uploadDate": {
"$date": "1970-01-01T00:00:00.000Z"
},
"filename": "newfilename",
"metadata": {}
},
{
"_id": {
"$oid": "000000000000000000000003"
},
"length": 2,
"chunkSize": 4,
"uploadDate": {
"$date": "1970-01-01T00:00:00.000Z"
},
"filename": "newfilename",
"metadata": {}
},
{
"_id": {
"$oid": "000000000000000000000004"
},
"length": 8,
"chunkSize": 4,
"uploadDate": {
"$date": "1970-01-01T00:00:00.000Z"
},
"filename": "otherfilename",
"metadata": {}
}
]
},
{
"collectionName": "fs.chunks",
"databaseName": "gridfs-tests",
"documents": [
{
"_id": {
"$oid": "000000000000000000000001"
},
"files_id": {
"$oid": "000000000000000000000002"
},
"n": 0,
"data": {
"$binary": {
"base64": "",
"subType": "00"
}
}
},
{
"_id": {
"$oid": "000000000000000000000002"
},
"files_id": {
"$oid": "000000000000000000000003"
},
"n": 0,
"data": {
"$binary": {
"base64": "",
"subType": "00"
}
}
},
{
"_id": {
"$oid": "000000000000000000000003"
},
"files_id": {
"$oid": "000000000000000000000004"
},
"n": 0,
"data": {
"$binary": {
"base64": "",
"subType": "00"
}
}
},
{
"_id": {
"$oid": "000000000000000000000004"
},
"files_id": {
"$oid": "000000000000000000000004"
},
"n": 1,
"data": {
"$binary": {
"base64": "",
"subType": "00"
}
}
}
]
}
]
},
{
"description": "rename when file name does not exist",
"operations": [
{
"name": "renameByName",
"object": "bucket0",
"arguments": {
"filename": "missing-file",
"newFilename": "newfilename"
},
"expectError": {
"isClientError": true
}
}
]
}
]
}

View File

@ -424,6 +424,15 @@ class TestGridfs(IntegrationTest):
self.fs.open_download_stream_by_name("first_name")
self.assertEqual(b"testing", (self.fs.open_download_stream_by_name("second_name")).read())
def test_rename_by_name(self):
_id = self.fs.upload_from_stream("first_name", b"testing")
self.assertEqual(b"testing", (self.fs.open_download_stream_by_name("first_name")).read())
self.fs.rename_by_name("first_name", "second_name")
with self.assertRaises(NoFile):
self.fs.open_download_stream_by_name("first_name")
self.assertEqual(b"testing", (self.fs.open_download_stream_by_name("second_name")).read())
@patch("gridfs.synchronous.grid_file._UPLOAD_BUFFER_SIZE", 5)
def test_abort(self):
gin = self.fs.open_upload_stream("test_filename", chunk_size_bytes=5)

View File

@ -541,6 +541,7 @@ class TestSession(IntegrationTest):
(bucket.download_to_stream_by_name, ["f", sio], {}),
(find, [], {}),
(bucket.rename, [1, "f2"], {}),
(bucket.rename_by_name, ["f2", "f3"], {}),
# Delete both files so _test_ops can run these operations twice.
(bucket.delete, [1], {}),
(bucket.delete_by_name, ["f"], {}),

View File

@ -287,7 +287,14 @@ class TestTransactions(TransactionsBase):
"new-name",
),
),
(bucket.delete_by_name, ("new-name",)),
(
bucket.rename_by_name,
(
"new-name",
"new-name2",
),
),
(bucket.delete_by_name, ("new-name2",)),
]
with client.start_session() as s, s.start_transaction():