Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Atomic replace (second attempt) #482

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def db(request):
db_.drop_tables()
db_.insert_multiple({'int': 1, 'char': c} for c in 'abc')

yield db_
with db_:
yield db_


@pytest.fixture
Expand Down
27 changes: 19 additions & 8 deletions tinydb/storages.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io
import json
import os
import tempfile
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional

Expand Down Expand Up @@ -125,25 +126,35 @@ def read(self) -> Optional[Dict[str, Dict[str, Any]]]:
return json.load(self._handle)

def write(self, data: Dict[str, Dict[str, Any]]):
# Move the cursor to the beginning of the file just in case
self._handle.seek(0)
if self._handle.closed:
# imitate what would be raised by calling `write()` on a closed file
raise ValueError("I/O operation on a closed file.")

file_name = self._handle.name

# Create a temporary file in the same folder
temp_file = tempfile.NamedTemporaryFile(mode=self._mode, prefix=file_name, delete=False)

# Serialize the database state using the user-provided arguments
serialized = json.dumps(data, **self.kwargs)

# Write the serialized data to the file
try:
self._handle.write(serialized)
temp_file.write(serialized)
except io.UnsupportedOperation:
raise IOError('Cannot write to the database. Access mode is "{0}"'.format(self._mode))

# Ensure the file has been written
self._handle.flush()
os.fsync(self._handle.fileno())
temp_file.flush()
os.fsync(temp_file.fileno())

# Replace the current file with the temporary file
temp_file.close()
self._handle.close()
os.replace(temp_file.name, file_name)

# Remove data that is behind the new cursor in case the file has
# gotten shorter
self._handle.truncate()
# Reopen the file
self._handle = open(file_name, mode=self._mode, encoding=self._handle.encoding)


class MemoryStorage(Storage):
Expand Down