Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/smmap/buf.py: 16%
68 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""Module with a simple buffer implementation using the memory manager"""
2import sys
4__all__ = ["SlidingWindowMapBuffer"]
7class SlidingWindowMapBuffer:
9 """A buffer like object which allows direct byte-wise object and slicing into
10 memory of a mapped file. The mapping is controlled by the provided cursor.
12 The buffer is relative, that is if you map an offset, index 0 will map to the
13 first byte at the offset you used during initialization or begin_access
15 **Note:** Although this type effectively hides the fact that there are mapped windows
16 underneath, it can unfortunately not be used in any non-pure python method which
17 needs a buffer or string"""
18 __slots__ = (
19 '_c', # our cursor
20 '_size', # our supposed size
21 )
23 def __init__(self, cursor=None, offset=0, size=sys.maxsize, flags=0):
24 """Initalize the instance to operate on the given cursor.
25 :param cursor: if not None, the associated cursor to the file you want to access
26 If None, you have call begin_access before using the buffer and provide a cursor
27 :param offset: absolute offset in bytes
28 :param size: the total size of the mapping. Defaults to the maximum possible size
29 From that point on, the __len__ of the buffer will be the given size or the file size.
30 If the size is larger than the mappable area, you can only access the actually available
31 area, although the length of the buffer is reported to be your given size.
32 Hence it is in your own interest to provide a proper size !
33 :param flags: Additional flags to be passed to os.open
34 :raise ValueError: if the buffer could not achieve a valid state"""
35 self._c = cursor
36 if cursor and not self.begin_access(cursor, offset, size, flags):
37 raise ValueError("Failed to allocate the buffer - probably the given offset is out of bounds")
38 # END handle offset
40 def __del__(self):
41 self.end_access()
43 def __enter__(self):
44 return self
46 def __exit__(self, exc_type, exc_value, traceback):
47 self.end_access()
49 def __len__(self):
50 return self._size
52 def __getitem__(self, i):
53 if isinstance(i, slice):
54 return self.__getslice__(i.start or 0, i.stop or self._size)
55 c = self._c
56 assert c.is_valid()
57 if i < 0:
58 i = self._size + i
59 if not c.includes_ofs(i):
60 c.use_region(i, 1)
61 # END handle region usage
62 return c.buffer()[i - c.ofs_begin()]
64 def __getslice__(self, i, j):
65 c = self._c
66 # fast path, slice fully included - safes a concatenate operation and
67 # should be the default
68 assert c.is_valid()
69 if i < 0:
70 i = self._size + i
71 if j == sys.maxsize:
72 j = self._size
73 if j < 0:
74 j = self._size + j
75 if (c.ofs_begin() <= i) and (j < c.ofs_end()):
76 b = c.ofs_begin()
77 return c.buffer()[i - b:j - b]
78 else:
79 l = j - i # total length
80 ofs = i
81 # It's fastest to keep tokens and join later, especially in py3, which was 7 times slower
82 # in the previous iteration of this code
83 md = list()
84 while l:
85 c.use_region(ofs, l)
86 assert c.is_valid()
87 d = c.buffer()[:l]
88 ofs += len(d)
89 l -= len(d)
90 # Make sure we don't keep references, as c.use_region() might attempt to free resources, but
91 # can't unless we use pure bytes
92 if hasattr(d, 'tobytes'):
93 d = d.tobytes()
94 md.append(d)
95 # END while there are bytes to read
96 return bytes().join(md)
97 # END fast or slow path
98 #{ Interface
100 def begin_access(self, cursor=None, offset=0, size=sys.maxsize, flags=0):
101 """Call this before the first use of this instance. The method was already
102 called by the constructor in case sufficient information was provided.
104 For more information no the parameters, see the __init__ method
105 :param path: if cursor is None the existing one will be used.
106 :return: True if the buffer can be used"""
107 if cursor:
108 self._c = cursor
109 # END update our cursor
111 # reuse existing cursors if possible
112 if self._c is not None and self._c.is_associated():
113 res = self._c.use_region(offset, size, flags).is_valid()
114 if res:
115 # if given size is too large or default, we computer a proper size
116 # If its smaller, we assume the combination between offset and size
117 # as chosen by the user is correct and use it !
118 # If not, the user is in trouble.
119 if size > self._c.file_size():
120 size = self._c.file_size() - offset
121 # END handle size
122 self._size = size
123 # END set size
124 return res
125 # END use our cursor
126 return False
128 def end_access(self):
129 """Call this method once you are done using the instance. It is automatically
130 called on destruction, and should be called just in time to allow system
131 resources to be freed.
133 Once you called end_access, you must call begin access before reusing this instance!"""
134 self._size = 0
135 if self._c is not None:
136 self._c.unuse_region()
137 # END unuse region
139 def cursor(self):
140 """:return: the currently set cursor which provides access to the data"""
141 return self._c
143 #}END interface