Skip to content

Commit 6da7488

Browse files
committed
improve the AutoDeleteFileName class (avoid __del__ issues)
1 parent 81f586e commit 6da7488

File tree

1 file changed

+55
-4
lines changed

1 file changed

+55
-4
lines changed

capsul/engine/__init__.py

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import os.path as osp
2626
import re
2727
import tempfile
28+
import weakref
2829

2930
from traits.api import Dict, String, Undefined
3031

@@ -493,17 +494,60 @@ def raise_for_status(self, status, execution_id=None):
493494

494495

495496
class AutoDeleteFileName(str):
497+
"""
498+
A temporary file name that automatically deletes the file when `cleanup()`
499+
is called.
500+
501+
This class creates a temporary file using `tempfile.mkstemp()`, stores its
502+
file descriptor and path, and behaves like a string (inherits from `str`).
503+
It is useful when you need to pass a file path around but also want to
504+
ensure the file is properly deleted later.
505+
506+
:param **kwargs: Keyword arguments passed to `tempfile.mkstemp()` such as
507+
`prefix`,`suffix`, or `dir`.
508+
509+
:returns (AutoDeleteFileName): An instance that acts like a string path
510+
but can clean up its underlying file.
511+
"""
512+
496513
def __new__(cls, **kwargs):
514+
"""
515+
Create a temporary file and return an AutoDeleteFileName instance with
516+
the file path.
517+
518+
:param **kwargs: Keyword arguments passed to `tempfile.mkstemp()`.
519+
520+
:returns (AutoDeleteFileName): The string path of the created
521+
temporary file.
522+
"""
497523
fd, path = tempfile.mkstemp(**kwargs)
498524
instance = super().__new__(cls, path)
499525
instance.path = path
500526
instance.fd = fd
501527
return instance
502528

503-
def __del__(self):
504-
os.close(self.fd)
505-
os.remove(self.path)
529+
def cleanup(self):
530+
"""
531+
Close and delete the temporary file if it exists.
532+
533+
This method ensures the file descriptor is closed and the file is
534+
removed from the filesystem. Errors during cleanup are silently
535+
ignored.
536+
"""
537+
538+
try:
539+
os.close(self.fd)
540+
541+
except OSError:
542+
pass
506543

544+
try:
545+
546+
if os.path.exists(self.path):
547+
os.remove(self.path)
548+
549+
except OSError:
550+
pass
507551

508552
def database_factory(database_location):
509553
"""
@@ -516,7 +560,9 @@ def database_factory(database_location):
516560
engine_directory = None
517561

518562
if database_location is None:
519-
database_location = AutoDeleteFileName(prefix="populse_db_", suffix=".sqlite")
563+
database_location = AutoDeleteFileName(
564+
prefix="populse_db_", suffix=".sqlite"
565+
)
520566
match = _populsedb_url_re.match(database_location)
521567
if match:
522568
path = match.groups(2)
@@ -533,6 +579,11 @@ def database_factory(database_location):
533579
raise ValueError("Invalid database location: %s" % database_location)
534580

535581
engine = PopulseDBEngine(populse_db)
582+
583+
if isinstance(database_location, AutoDeleteFileName):
584+
# attach cleanup so it deletes when engine is done
585+
weakref.finalize(engine, database_location.cleanup)
586+
536587
if engine_directory:
537588
engine.set_named_directory("capsul_engine", engine_directory)
538589
return engine

0 commit comments

Comments
 (0)