Coverage for src / cufile_patcher / cufile.py: 100%
75 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-11 15:06 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-11 15:06 +0000
1from __future__ import annotations
3import ctypes
4import os
6from .bindings import (
7 cuFileDriverClose,
8 cuFileDriverOpen,
9 cuFileHandleDeregister,
10 cuFileHandleRegister,
11 cuFileRead,
12 cuFileWrite,
13)
16class CuFileDriver:
17 _instance: CuFileDriver | None = None
19 def __new__(cls) -> CuFileDriver:
20 if cls._instance is None:
21 cls._instance = super().__new__(cls)
22 cls._instance._initialized = False
23 return cls._instance
25 def __init__(self) -> None:
26 if self._initialized:
27 return
28 cuFileDriverOpen()
29 self._initialized = True
31 def close(self) -> None:
32 if self._initialized:
33 cuFileDriverClose()
34 self._initialized = False
36 def __del__(self) -> None:
37 self.close()
39 @classmethod
40 def _reset_for_tests(cls) -> None:
41 if cls._instance is not None:
42 cls._instance.close()
43 cls._instance = None
46def _os_mode(mode: str) -> int:
47 modes = {
48 "r": os.O_RDONLY,
49 "r+": os.O_RDWR,
50 "w": os.O_CREAT | os.O_WRONLY | os.O_TRUNC,
51 "w+": os.O_CREAT | os.O_RDWR | os.O_TRUNC,
52 "a": os.O_CREAT | os.O_WRONLY | os.O_APPEND,
53 "a+": os.O_CREAT | os.O_RDWR | os.O_APPEND,
54 }
55 try:
56 return modes[mode]
57 except KeyError as exc:
58 raise ValueError(f"unsupported mode: {mode}") from exc
61class CuFile:
62 def __init__(self, path: str, mode: str = "r", use_direct_io: bool = False):
63 self._driver = CuFileDriver()
64 self._path = path
65 self._mode = mode
66 self._os_mode = _os_mode(mode)
67 if use_direct_io and hasattr(os, "O_DIRECT"):
68 self._os_mode |= os.O_DIRECT
70 self._handle: int | None = None
71 self._cu_file_handle = None
73 @property
74 def is_open(self) -> bool:
75 return self._handle is not None
77 def open(self) -> None:
78 if self.is_open:
79 return
80 self._handle = os.open(self._path, self._os_mode)
81 self._cu_file_handle = cuFileHandleRegister(self._handle)
83 def close(self) -> None:
84 if not self.is_open:
85 return
86 cuFileHandleDeregister(self._cu_file_handle)
87 os.close(self._handle)
88 self._handle = None
89 self._cu_file_handle = None
91 def __enter__(self) -> CuFile:
92 self.open()
93 return self
95 def __exit__(self, exc_type, exc_val, exc_tb) -> None:
96 self.close()
98 def __del__(self) -> None:
99 self.close()
101 def read(
102 self,
103 dest: ctypes.c_void_p,
104 size: int,
105 file_offset: int = 0,
106 dev_offset: int = 0,
107 ) -> int:
108 if not self.is_open:
109 raise OSError("File is not open.")
110 return cuFileRead(self._cu_file_handle, dest, size, file_offset, dev_offset)
112 def write(
113 self,
114 src: ctypes.c_void_p,
115 size: int,
116 file_offset: int = 0,
117 dev_offset: int = 0,
118 ) -> int:
119 if not self.is_open:
120 raise OSError("File is not open.")
121 return cuFileWrite(self._cu_file_handle, src, size, file_offset, dev_offset)
123 def get_handle(self) -> int | None:
124 return self._handle