Skip to content

Commit b7fafda

Browse files
author
rgaudin
authored
Merge pull request #115 from openzim/hint
Add support for Hints in python-libzim
2 parents 787c9ed + 5a0b305 commit b7fafda

File tree

7 files changed

+143
-9
lines changed

7 files changed

+143
-9
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: test
22
on: [push]
33

44
env:
5-
LIBZIM_VERSION: 2021-07-08
5+
LIBZIM_VERSION: 2021-08-26
66
LIBZIM_INCLUDE_PATH: include/zim
77
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
88
MAX_LINE_LENGTH: 88

libzim/lib.cxx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,20 @@ std::unique_ptr<zim::writer::ContentProvider> WriterItemWrapper::callCythonRetur
129129
return ret_val;
130130
}
131131

132+
zim::writer::Hints WriterItemWrapper::callCythonReturnHints(std::string methodName) const
133+
{
134+
if (!this->m_obj)
135+
throw std::runtime_error("Python object not set");
136+
137+
std::string error;
138+
139+
auto ret_val = hints_cy_call_fct(this->m_obj, methodName, &error);
140+
if (!error.empty())
141+
throw std::runtime_error(error);
142+
143+
return ret_val;
144+
}
145+
132146
std::string
133147
WriterItemWrapper::getPath() const
134148
{
@@ -152,3 +166,8 @@ WriterItemWrapper::getContentProvider() const
152166
{
153167
return callCythonReturnContentProvider("get_contentprovider");
154168
}
169+
170+
zim::writer::Hints WriterItemWrapper::getHints() const
171+
{
172+
return callCythonReturnHints("get_hints");
173+
}

libzim/lib.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,11 @@ class WriterItemWrapper : public zim::writer::Item, private ObjWrapper
115115
virtual std::string getTitle() const;
116116
virtual std::string getMimeType() const;
117117
virtual std::unique_ptr<zim::writer::ContentProvider> getContentProvider() const;
118+
virtual zim::writer::Hints getHints() const;
118119

119120
private:
120121
std::unique_ptr<zim::writer::ContentProvider> callCythonReturnContentProvider(std::string) const;
122+
zim::writer::Hints callCythonReturnHints(std::string) const;
121123
};
122124

123125
class ContentProviderWrapper : public zim::writer::ContentProvider, private ObjWrapper

libzim/wrapper.pxd

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ from libcpp cimport bool
2424
from libcpp.memory cimport shared_ptr, unique_ptr
2525
from libcpp.string cimport string
2626
from libcpp.vector cimport vector
27+
from libcpp.map cimport map
2728

2829

2930
cdef extern from "zim/zim.h" namespace "zim":
@@ -50,6 +51,9 @@ cdef extern from "zim/blob.h" namespace "zim":
5051
cdef extern from "zim/writer/item.h" namespace "zim::writer":
5152
cdef cppclass WriterItem "zim::writer::Item":
5253
pass
54+
ctypedef enum HintKeys:
55+
COMPRESS
56+
FRONT_ARTICLE
5357

5458
cdef extern from "zim/writer/contentProvider.h" namespace "zim::writer":
5559
cdef cppclass ContentProvider:
@@ -66,7 +70,7 @@ cdef extern from "zim/writer/creator.h" namespace "zim::writer":
6670
void startZimCreation(string filepath) nogil except +;
6771
void addItem(shared_ptr[WriterItem] item) nogil except +
6872
void addMetadata(string name, string content, string mimetype) nogil except +
69-
void addRedirection(string path, string title, string targetpath) nogil except +
73+
void addRedirection(string path, string title, string targetpath, map[HintKeys, uint64_t] hints) nogil except +
7074
void finishZimCreation() nogil except +
7175
void setMainPath(string mainPath)
7276
void addIllustration(unsigned int size, string content)

libzim/wrapper.pyx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@ from libcpp.string cimport string
3333
from libcpp cimport bool
3434
from libcpp.set cimport set
3535
from libcpp.memory cimport shared_ptr, make_shared, unique_ptr
36+
from libcpp.map cimport map
3637

3738
import pathlib
3839
import datetime
3940
import traceback
4041

4142

43+
pybool = type(True)
44+
4245
#########################
4346
# Blob #
4447
#########################
@@ -166,6 +169,23 @@ cdef public api:
166169

167170
return 0
168171

172+
map[HintKeys, uint64_t] convertToCppHints(dict hintsDict):
173+
cdef map[HintKeys, uint64_t] ret;
174+
for key, value in hintsDict.items():
175+
ret[key.value] = <uint64_t>value
176+
return ret
177+
178+
map[HintKeys, uint64_t] hints_cy_call_fct(object obj, string method, string* error) with gil:
179+
cdef map[HintKeys, uint64_t] ret;
180+
try:
181+
func = getattr(obj, method.decode('UTF-8'))
182+
hintsDict = {k: pybool(v) for k, v in func().items() if isinstance(k, Hint)}
183+
return convertToCppHints(hintsDict)
184+
except Exception as e:
185+
error[0] = traceback.format_exc().encode('UTF-8')
186+
187+
return ret
188+
169189

170190
class Compression(enum.Enum):
171191
""" Compression algorithms available to create ZIM files """
@@ -174,6 +194,12 @@ class Compression(enum.Enum):
174194
zstd = wrapper.CompressionType.zimcompZstd
175195

176196

197+
class Hint(enum.Enum):
198+
COMPRESS = wrapper.HintKeys.COMPRESS
199+
FRONT_ARTICLE = wrapper.HintKeys.FRONT_ARTICLE
200+
201+
202+
177203
cdef class Creator:
178204
""" Zim Creator
179205
@@ -278,15 +304,16 @@ cdef class Creator:
278304
with nogil:
279305
self.c_creator.addMetadata(_name, _content, _mimetype)
280306

281-
def add_redirection(self, str path, str title, str targetPath):
307+
def add_redirection(self, str path, str title, str targetPath, dict hints):
282308
if not self._started:
283309
raise RuntimeError("ZimCreator not started")
284310

285311
cdef string _path = path.encode('utf8')
286312
cdef string _title = title.encode('utf8')
287313
cdef string _targetPath = targetPath.encode('utf8')
314+
cdef map[HintKeys, uint64_t] _hints = convertToCppHints(hints)
288315
with nogil:
289-
self.c_creator.addRedirection(_path, _title, _targetPath)
316+
self.c_creator.addRedirection(_path, _title, _targetPath, _hints)
290317

291318
def __enter__(self):
292319
cdef string _path = str(self._filename).encode('utf8')

libzim/writer.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@
3333

3434
import os
3535
import datetime
36-
from typing import Union
36+
from typing import Union, Dict
3737

3838
from .wrapper import Creator as _Creator, Compression
3939
from .wrapper import WritingBlob as Blob
40+
from .wrapper import Hint
4041

4142
__all__ = [
4243
"Item",
@@ -132,6 +133,9 @@ def get_contentprovider(self) -> ContentProvider:
132133
"""ContentProvider containing the complete content of the item"""
133134
raise NotImplementedError("get_contentprovider must be implemented.")
134135

136+
def get_hints(self) -> Dict[Hint, int]:
137+
raise NotImplementedError("get_hints must be implemented.")
138+
135139
def __repr__(self) -> str:
136140
return (
137141
f"{self.__class__.__name__}(path={self.get_path()}, "

tests/test_libzim_creator.py

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import datetime
77
import itertools
88
import subprocess
9-
9+
from typing import Dict
1010

1111
import pytest
1212

@@ -18,6 +18,7 @@
1818
FileProvider,
1919
StringProvider,
2020
Blob,
21+
Hint,
2122
)
2223
from libzim.reader import Archive
2324

@@ -45,6 +46,9 @@ def get_contentprovider(self) -> libzim.writer.ContentProvider:
4546
return FileProvider(filepath=self.filepath)
4647
return StringProvider(content=getattr(self, "content", ""))
4748

49+
def get_hints(self) -> Dict[Hint, int]:
50+
return getattr(self, "hints", {Hint.FRONT_ARTICLE: True})
51+
4852

4953
@pytest.fixture(scope="function")
5054
def fpath(tmpdir):
@@ -423,13 +427,13 @@ def test_creator_redirection(fpath, lipsum_item):
423427
# ensure we can't add if not started
424428
c = Creator(fpath)
425429
with pytest.raises(RuntimeError, match="not started"):
426-
c.add_redirection("home", "hello", HOME_PATH)
430+
c.add_redirection("home", "hello", HOME_PATH, {Hint.FRONT_ARTICLE: True})
427431
del c
428432

429433
with Creator(fpath) as c:
430434
c.add_item(lipsum_item)
431-
c.add_redirection("home", "hello", HOME_PATH)
432-
c.add_redirection("accueil", "bonjour", HOME_PATH)
435+
c.add_redirection("home", "hello", HOME_PATH, {Hint.FRONT_ARTICLE: True})
436+
c.add_redirection("accueil", "bonjour", HOME_PATH, {Hint.FRONT_ARTICLE: True})
433437

434438
zim = Archive(fpath)
435439
assert zim.entry_count == 3
@@ -524,6 +528,9 @@ def get_mimetype(self):
524528
def get_contentprovider(self):
525529
return ""
526530

531+
def get_hints(self):
532+
return {}
533+
527534
with Creator(fpath) as c:
528535
with pytest.raises(RuntimeError, match="ContentProvider is None"):
529536
c.add_item(AnItem())
@@ -540,11 +547,76 @@ def get_title(self):
540547
def get_mimetype(self):
541548
return ""
542549

550+
def get_hints(self):
551+
return {}
552+
543553
with Creator(fpath) as c:
544554
with pytest.raises(RuntimeError, match="has no attribute"):
545555
c.add_item(AnItem())
546556

547557

558+
def test_missing_hints(fpath):
559+
class AnItem:
560+
def get_path(self):
561+
return ""
562+
563+
def get_title(self):
564+
return ""
565+
566+
def get_mimetype(self):
567+
return ""
568+
569+
with Creator(fpath) as c:
570+
with pytest.raises(RuntimeError, match="has no attribute 'get_hints'"):
571+
c.add_item(AnItem())
572+
573+
with pytest.raises(RuntimeError, match="must be implemented"):
574+
c.add_item(libzim.writer.Item())
575+
576+
577+
def test_nondict_hints(fpath):
578+
with Creator(fpath) as c:
579+
with pytest.raises(RuntimeError, match="has no attribute 'items'"):
580+
c.add_item(StaticItem(path="1", title="", hints=1))
581+
582+
with pytest.raises(TypeError, match="hints"):
583+
c.add_redirection("a", "", "b", hints=1)
584+
585+
586+
def test_hints_values(fpath):
587+
with Creator(fpath) as c:
588+
# correct values
589+
c.add_item(StaticItem(path="0", title="", hints={}))
590+
c.add_item(
591+
StaticItem(
592+
path="1",
593+
title="",
594+
hints={Hint.FRONT_ARTICLE: True, Hint.COMPRESS: False},
595+
)
596+
)
597+
# non-expected Hints are ignored
598+
c.add_item(StaticItem(path="2", title="", hints={"hello": "world"}))
599+
# Hint values are casted to bool
600+
c.add_item(StaticItem(path="3", title="", hints={Hint.FRONT_ARTICLE: "world"}))
601+
c.add_redirection(
602+
path="4", title="", targetPath="0", hints={Hint.COMPRESS: True}
603+
)
604+
# filtered-out values
605+
c.add_item(StaticItem(path="5", title="", hints={5: True}))
606+
c.add_item(StaticItem(path="6", title="", hints={"yolo": True}))
607+
c.add_item(StaticItem(path="7", title="", hints={"FRONT_ARTICLE": True}))
608+
c.add_item(StaticItem(path="8", title="", hints={0: True}))
609+
610+
# non-existent Hint
611+
with pytest.raises(AttributeError, match="YOLO"):
612+
c.add_item(StaticItem(path="0", title="", hints={Hint.YOLO: True}))
613+
614+
with pytest.raises(AttributeError, match="YOLO"):
615+
c.add_redirection(
616+
path="5", title="", target_path="0", hints={Hint.YOLO: True}
617+
)
618+
619+
548620
def test_reimpfeed(fpath):
549621
class AContentProvider:
550622
def __init__(self):
@@ -569,6 +641,9 @@ def get_title(self):
569641
def get_mimetype(self):
570642
return ""
571643

644+
def get_hints(self):
645+
return {}
646+
572647
def get_contentprovider(self):
573648
return AContentProvider()
574649

@@ -599,6 +674,9 @@ def get_title(self):
599674
def get_mimetype(self):
600675
return ""
601676

677+
def get_hints(self):
678+
return {}
679+
602680
def get_contentprovider(self):
603681
return AContentProvider()
604682

0 commit comments

Comments
 (0)