Changeset View
Standalone View
swh/perfecthash/__init__.py
| # Copyright (C) 2021 The Software Heritage developers | |||||||||||||||||||||||||
ardumont: copyright headers to match other swh modules? | |||||||||||||||||||||||||
Done Inline ActionsGood point, done! dachary: Good point, done! | |||||||||||||||||||||||||
| # See the AUTHORS file at the top-level directory of this distribution | |||||||||||||||||||||||||
| # License: GNU General Public License version 3, or any later version | |||||||||||||||||||||||||
| # See top-level LICENSE file for more information | |||||||||||||||||||||||||
| from typing import NewType | |||||||||||||||||||||||||
| from _hash_cffi import lib | |||||||||||||||||||||||||
| from cffi import FFI | |||||||||||||||||||||||||
| Key = NewType("Key", bytes) | |||||||||||||||||||||||||
| HashObject = NewType("HashObject", bytes) | |||||||||||||||||||||||||
| class Shard: | |||||||||||||||||||||||||
| """Low level management for files indexed with a perfect hash table. | |||||||||||||||||||||||||
| This class allows creating a Read Shard by adding key/object pairs | |||||||||||||||||||||||||
Done Inline Actions
Let's add some types. ardumont: Let's add some types. | |||||||||||||||||||||||||
| and looking up the content of an object when given the key. | |||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||
| def __init__(self, path: str): | |||||||||||||||||||||||||
| """Initialize with an existing Read Shard. | |||||||||||||||||||||||||
| Args: | |||||||||||||||||||||||||
| path: path to an existing Read Shard file or device | |||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||
| self.ffi = FFI() | |||||||||||||||||||||||||
| self.shard = lib.shard_init(path.encode("utf-8")) | |||||||||||||||||||||||||
| def __del__(self): | |||||||||||||||||||||||||
Done Inline Actions
fyi, we now are documenting directly the types within the code and no longer in the docstring. ardumont: fyi, we now are documenting directly the types within the code and no longer in the docstring. | |||||||||||||||||||||||||
| lib.shard_destroy(self.shard) | |||||||||||||||||||||||||
| def create(self, objects_count: int) -> "Shard": | |||||||||||||||||||||||||
| """Wipe out the content of the Read Shard. It must be followed by | |||||||||||||||||||||||||
| **object_count** calls to the **write** method otherwise the content | |||||||||||||||||||||||||
| of the Read Shard will be inconsistent. When all objects are inserted, | |||||||||||||||||||||||||
| the Read Shard must be made persistent by calling the **save** method. | |||||||||||||||||||||||||
| Args: | |||||||||||||||||||||||||
| objects_count: number of objects in the Read Shard. | |||||||||||||||||||||||||
| Returns: | |||||||||||||||||||||||||
| self. | |||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||
| assert lib.shard_create(self.shard, objects_count) != -1 | |||||||||||||||||||||||||
| return self | |||||||||||||||||||||||||
| def load(self): | |||||||||||||||||||||||||
Done Inline Actions
From the docstring. ardumont: From the docstring. | |||||||||||||||||||||||||
| """Open the Read Shard file in read-only mode. | |||||||||||||||||||||||||
| Returns: | |||||||||||||||||||||||||
| self. | |||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||
| assert lib.shard_load(self.shard) != -1 | |||||||||||||||||||||||||
| return self | |||||||||||||||||||||||||
| def save(self): | |||||||||||||||||||||||||
Done Inline Actions
From the docstring ;) ardumont: From the docstring ;) | |||||||||||||||||||||||||
| """Create the perfect hash table the **lookup** method | |||||||||||||||||||||||||
| relies on to find the content of the objects. | |||||||||||||||||||||||||
| It must be called after **create** an **write** otherwise the | |||||||||||||||||||||||||
| content of the Read Shard will be inconsistent. | |||||||||||||||||||||||||
| Returns: | |||||||||||||||||||||||||
| 0 on success, -1 on error. | |||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||
| return lib.shard_save(self.shard) | |||||||||||||||||||||||||
| def lookup(self, key: Key) -> HashObject: | |||||||||||||||||||||||||
| """Fetch the object matching the key in the Read Shard. | |||||||||||||||||||||||||
Done Inline Actionsindent is off by 2 chars almost everywhere. ardumont: indent is off by 2 chars almost everywhere.
We normalize on a 4 chars indent below "Args:" and… | |||||||||||||||||||||||||
Done Inline Actions
Let's add some types around this, then define at the top-level module something like: from typing import NewType
Key = NewType("Key", bytes)
HashObjects = NewType("HashObject", bytes)I believe that improves the reading significantly. ardumont: Let's add some types around this, then define at the top-level module something like:
```
from… | |||||||||||||||||||||||||
| Fetching an object is O(1): one lookup in the index to obtain | |||||||||||||||||||||||||
| the offset of the object in the Read Shard and one read to get | |||||||||||||||||||||||||
| the payload. | |||||||||||||||||||||||||
| Args: | |||||||||||||||||||||||||
| key: the key associated with the object to retrieve. | |||||||||||||||||||||||||
| Returns: | |||||||||||||||||||||||||
| the object as bytes. | |||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||
| object_size_pointer = self.ffi.new("size_t*") | |||||||||||||||||||||||||
| lib.shard_lookup_object_size(self.shard, key, object_size_pointer) | |||||||||||||||||||||||||
| object_size = object_size_pointer[0] | |||||||||||||||||||||||||
| object_pointer = self.ffi.new("char[]", object_size) | |||||||||||||||||||||||||
| lib.shard_lookup_object(self.shard, object_pointer, object_size) | |||||||||||||||||||||||||
| return self.ffi.buffer(object_pointer, object_size) | |||||||||||||||||||||||||
Done Inline ActionsSomething hit me, can't the lookup fail? What would be the return if that was the case None? ardumont: Something hit me, can't the lookup fail?
What would be the return if that was the case None? | |||||||||||||||||||||||||
Done Inline ActionsI overlooked this question, sorry. The lookup cannot fail: if given a key that does not exist it will return a random object. Which is ok because the global index ensures the Read Shard is only queried with objects that are known to exist in the hash map. dachary: I overlooked this question, sorry. The lookup cannot fail: if given a key that does not exist… | |||||||||||||||||||||||||
Done Inline Actionsyou did not overlook it, it's a new one! If anything, that's me who overlooked that code last time ;) Great! "Can't happen!" ardumont: you did not overlook it, it's a new one!
If anything, that's me who overlooked that code last… | |||||||||||||||||||||||||
| def write(self, key: Key, object: HashObject) -> int: | |||||||||||||||||||||||||
Done Inline Actions
ardumont: | |||||||||||||||||||||||||
| """Add the key/object pair to the Read Shard. | |||||||||||||||||||||||||
| The **create** method must have been called prior to calling | |||||||||||||||||||||||||
| the **write** method. | |||||||||||||||||||||||||
| Args: | |||||||||||||||||||||||||
| key: the unique key associated with the object. | |||||||||||||||||||||||||
| object: the object | |||||||||||||||||||||||||
| Returns: | |||||||||||||||||||||||||
| 0 on success, -1 on error. | |||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||
| return lib.shard_object_write(self.shard, key, object, len(object)) | |||||||||||||||||||||||||
copyright headers to match other swh modules?