Skip to content

Commit 9ab10ba

Browse files
authored
Merge pull request #199 from openzim/types
Introducing Type Hints
2 parents dee26e4 + 5821694 commit 9ab10ba

13 files changed

Lines changed: 281 additions & 18 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Windows (x64) support (#91)
13+
- Type stubs (#198)
1314

1415
### Changed
1516

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ with Creator("test.zim") as creator:
149149
creator.add_item(item2)
150150
```
151151

152+
#### Type hints
153+
154+
`libzim` being a binary extension, there is no Python source to provide types information. We provide them as type stub files. When using `pyright`, you would normally receive a warning when importing from `libzim` as there could be discrepencies between actual sources and the (manually crafted) stub files.
155+
156+
You can disable the warning via `reportMissingModuleSource = "none"`.
157+
152158
## Building
153159

154160
`libzim` package building offers different behaviors via environment variables

libzim/__init__.pyi

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from libzim import (
2+
reader, # noqa: F401 # pyright: ignore[reportUnusedImport]
3+
search, # noqa: F401 # pyright: ignore[reportUnusedImport]
4+
suggestion, # noqa: F401 # pyright: ignore[reportUnusedImport]
5+
version, # noqa: F401 # pyright: ignore[reportUnusedImport]
6+
writer, # noqa: F401 # pyright: ignore[reportUnusedImport]
7+
)

libzim/libwrapper.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,13 @@ WriterItemWrapper::getContentProvider() const
224224
std::shared_ptr<zim::writer::IndexData>
225225
WriterItemWrapper::getIndexData() const
226226
{
227+
// Item without method defined (should not happen on proper subclass)
227228
if (!obj_has_attribute(m_obj, "get_indexdata")) {
228229
return zim::writer::Item::getIndexData();
229230
}
231+
if (method_is_none(m_obj, "get_indexdata")) {
232+
return zim::writer::Item::getIndexData();
233+
}
230234
return callMethodOnObj<std::shared_ptr<zim::writer::IndexData>>(m_obj, "get_indexdata");
231235
}
232236

libzim/libzim.pyx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ cdef object call_method(object obj, string method):
8888
# object to the correct cpp type.
8989
# Will be used by cpp side to call python method.
9090
cdef public api:
91+
92+
# this tells whether a method/property is none or not
93+
bool method_is_none(object obj, string method) with gil:
94+
func = getattr(obj, method.decode('UTF-8'))
95+
return func is None
96+
9197
bool obj_has_attribute(object obj, string attribute) with gil:
9298
"""Check if a object has a given attribute"""
9399
return hasattr(obj, attribute.decode('UTF-8'))
@@ -537,6 +543,7 @@ class BaseWritingItem:
537543

538544
def __init__(self):
539545
self._blob = None
546+
get_indexdata = None
540547

541548
def get_path(self) -> str:
542549
"""Full path of item"""
@@ -567,7 +574,7 @@ class BaseWritingItem:
567574

568575
class Creator(_Creator):
569576
__module__ = writer_module_name
570-
def config_compression(self, compression: Compression):
577+
def config_compression(self, compression: Union[Compression, str]):
571578
if not isinstance(compression, Compression):
572579
compression = getattr(Compression, compression.lower())
573580
return super().config_compression(compression)

libzim/reader.pyi

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from __future__ import annotations
2+
3+
import pathlib
4+
from uuid import UUID
5+
6+
class Item:
7+
@property
8+
def title(self) -> str: ...
9+
@property
10+
def path(self) -> str: ...
11+
@property
12+
def content(self) -> memoryview: ...
13+
@property
14+
def mimetype(self) -> str: ...
15+
@property
16+
def _index(self) -> int: ...
17+
@property
18+
def size(self) -> int: ...
19+
def __repr__(self) -> str: ...
20+
21+
class Entry:
22+
@property
23+
def title(self) -> str: ...
24+
@property
25+
def path(self) -> str: ...
26+
@property
27+
def _index(self) -> int: ...
28+
@property
29+
def is_redirect(self) -> bool: ...
30+
def get_redirect_entry(self) -> Entry: ...
31+
def get_item(self) -> Item: ...
32+
def __repr__(self) -> str: ...
33+
34+
class Archive:
35+
def __init__(self, filename: pathlib.Path) -> None: ...
36+
@property
37+
def filename(self) -> pathlib.Path: ...
38+
@property
39+
def filesize(self) -> int: ...
40+
def has_entry_by_path(self, path: str) -> bool: ...
41+
def get_entry_by_path(self, path: str) -> Entry: ...
42+
def has_entry_by_title(self, title: str) -> bool: ...
43+
def get_entry_by_title(self, title: str) -> Entry: ...
44+
@property
45+
def metadata_keys(self) -> list[str]: ...
46+
def get_metadata_item(self, name: str) -> Item: ...
47+
def get_metadata(self, name: str) -> bytes: ...
48+
def _get_entry_by_id(self, entry_id: int) -> Entry: ...
49+
@property
50+
def has_main_entry(self) -> bool: ...
51+
@property
52+
def main_entry(self) -> Entry: ...
53+
@property
54+
def uuid(self) -> UUID: ...
55+
@property
56+
def has_new_namespace_scheme(self) -> bool: ...
57+
@property
58+
def is_multipart(self) -> bool: ...
59+
@property
60+
def has_fulltext_index(self) -> bool: ...
61+
@property
62+
def has_title_index(self) -> bool: ...
63+
@property
64+
def has_checksum(self) -> str: ...
65+
@property
66+
def checksum(self) -> str: ...
67+
def check(self) -> bool: ...
68+
@property
69+
def entry_count(self) -> int: ...
70+
@property
71+
def all_entry_count(self) -> int: ...
72+
@property
73+
def article_count(self) -> int: ...
74+
@property
75+
def media_count(self) -> int: ...
76+
def get_illustration_sizes(self) -> set[int]: ...
77+
def has_illustration(self, size: int | None = None) -> bool: ...
78+
def get_illustration_item(self, size: int | None = None) -> Item: ...
79+
def __repr__(self) -> str: ...

libzim/search.pyi

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Iterator
4+
from typing import Self
5+
6+
from libzim.reader import Archive
7+
8+
class Query:
9+
def set_query(self, query: str) -> Self: ...
10+
11+
class SearchResultSet:
12+
def __iter__(self) -> Iterator[str]: ...
13+
14+
class Search:
15+
def getEstimatedMatches(self) -> int: ... # noqa: N802
16+
def getResults(self, start: int, count: int) -> SearchResultSet: ... # noqa: N802
17+
18+
class Searcher:
19+
def __init__(self, archive: Archive) -> None: ...
20+
def search(self, query: Query) -> Search: ...

libzim/suggestion.pyi

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Iterator
4+
5+
from libzim.reader import Archive
6+
7+
class SuggestionResultSet:
8+
def __iter__(self) -> Iterator[str]: ...
9+
10+
class SuggestionSearch:
11+
def getEstimatedMatches(self) -> int: ... # noqa: N802
12+
def getResults( # noqa: N802
13+
self, start: int, count: int
14+
) -> SuggestionResultSet: ...
15+
16+
class SuggestionSearcher:
17+
def __init__(self, archive: Archive) -> None: ...
18+
def suggest(self, query: str) -> SuggestionSearch: ...

libzim/version.pyi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
from collections import OrderedDict
5+
from typing import TextIO
6+
7+
def print_versions(out: TextIO = sys.stdout) -> None: ...
8+
def get_versions() -> OrderedDict[str, str]: ...
9+
def get_libzim_version() -> str: ...

libzim/writer.pyi

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from __future__ import annotations
2+
3+
import datetime
4+
import enum
5+
import pathlib
6+
import types
7+
from collections.abc import Callable, Generator
8+
from typing import Self
9+
10+
class Compression(enum.Enum):
11+
none: Self
12+
zstd: Self
13+
14+
class Hint(enum.Enum):
15+
COMPRESS: Self
16+
FRONT_ARTICLE: Self
17+
18+
class Blob:
19+
def __init__(self, content: str | bytes) -> None: ...
20+
def size(self) -> int: ...
21+
ref_content: bytes
22+
23+
class ContentProvider:
24+
def feed(self) -> Blob: ...
25+
def get_size(self) -> int: ...
26+
def gen_blob(self) -> Generator[Blob, None, None]: ...
27+
28+
generator: Generator[Blob, None, None]
29+
30+
class StringProvider(ContentProvider):
31+
def __init__(self, content: str | bytes) -> None: ...
32+
33+
class FileProvider(ContentProvider):
34+
def __init__(self, filepath: pathlib.Path | str) -> None: ...
35+
36+
class Item:
37+
def get_path(self) -> str: ...
38+
def get_title(self) -> str: ...
39+
def get_mimetype(self) -> str: ...
40+
def get_contentprovider(self) -> ContentProvider: ...
41+
def get_hints(self) -> dict[Hint, int]: ...
42+
def __repr__(self) -> str: ...
43+
44+
get_indexdata: Callable[[], IndexData | None] | None
45+
_blob: Blob
46+
47+
class IndexData:
48+
def has_indexdata(self) -> bool: ...
49+
def get_title(self) -> str: ...
50+
def get_content(self) -> str: ...
51+
def get_keywords(self) -> str: ...
52+
def get_wordcount(self) -> int: ...
53+
def get_geoposition(self) -> tuple[float, float] | None: ...
54+
55+
class Creator:
56+
def __init__(self, filename: pathlib.Path) -> None: ...
57+
def config_verbose(self, verbose: bool) -> Self: ...
58+
def config_compression(self, compression: Compression | str) -> Self: ...
59+
def config_clustersize(self, size: int) -> Self: ...
60+
def config_indexing(self, indexing: bool, language: str) -> Self: ...
61+
def config_nbworkers(self, nbWorkers: int) -> Self: ... # noqa: N803
62+
def set_mainpath(self, mainPath: str) -> Self: ... # noqa: N803
63+
def add_illustration(self, size: int, content: bytes) -> None: ...
64+
def add_item(self, writer_item: Item) -> None: ...
65+
def add_metadata(
66+
self,
67+
name: str,
68+
content: str | bytes | datetime.date | datetime.datetime,
69+
mimetype: str = "text/plain;charset=UTF-8",
70+
) -> None: ...
71+
def add_redirection(
72+
self,
73+
path: str,
74+
title: str,
75+
targetPath: str, # noqa: N803
76+
hints: dict[Hint, int],
77+
) -> None: ...
78+
def add_alias(
79+
self,
80+
path: str,
81+
title: str,
82+
targetPath: str, # noqa: N803
83+
hints: dict[Hint, int],
84+
) -> None: ...
85+
def __enter__(self) -> Self: ...
86+
def __exit__(
87+
self,
88+
exc_type: type[BaseException] | None,
89+
exc_val: BaseException | None,
90+
exc_tb: types.TracebackType | None,
91+
) -> None: ...
92+
@property
93+
def filename(self) -> pathlib.Path: ...
94+
def __repr__(self) -> str: ...
95+
96+
_filename: pathlib.Path
97+
_started: bool

0 commit comments

Comments
 (0)