excelipy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- excelipy-0.1.0/MANIFEST.in +1 -0
- excelipy-0.1.0/PKG-INFO +13 -0
- excelipy-0.1.0/README.md +3 -0
- excelipy-0.1.0/excelipy/__init__.py +22 -0
- excelipy-0.1.0/excelipy/const.py +15 -0
- excelipy-0.1.0/excelipy/main.py +74 -0
- excelipy-0.1.0/excelipy/models.py +123 -0
- excelipy-0.1.0/excelipy/service.py +65 -0
- excelipy-0.1.0/excelipy/style.py +33 -0
- excelipy-0.1.0/excelipy/writers/__init__.py +9 -0
- excelipy-0.1.0/excelipy/writers/fill.py +34 -0
- excelipy-0.1.0/excelipy/writers/table.py +105 -0
- excelipy-0.1.0/excelipy/writers/text.py +41 -0
- excelipy-0.1.0/excelipy.egg-info/PKG-INFO +13 -0
- excelipy-0.1.0/excelipy.egg-info/SOURCES.txt +19 -0
- excelipy-0.1.0/excelipy.egg-info/dependency_links.txt +1 -0
- excelipy-0.1.0/excelipy.egg-info/requires.txt +3 -0
- excelipy-0.1.0/excelipy.egg-info/top_level.txt +1 -0
- excelipy-0.1.0/pyproject.toml +22 -0
- excelipy-0.1.0/setup.cfg +4 -0
- excelipy-0.1.0/test/test_load.py +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recursive-include excelipy *
|
excelipy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: excelipy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Wrapper around xlsxwriter to improve usability
|
|
5
|
+
Requires-Python: >=3.9.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pandas>=2.2.3
|
|
8
|
+
Requires-Dist: pydantic>=2.11.3
|
|
9
|
+
Requires-Dist: xlsxwriter>=3.2.2
|
|
10
|
+
|
|
11
|
+
# Excelipy
|
|
12
|
+
|
|
13
|
+
[](https://codecov.io/gh/choinhet/excelipy)
|
excelipy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"Style",
|
|
3
|
+
"Component",
|
|
4
|
+
"Fill",
|
|
5
|
+
"Text",
|
|
6
|
+
"Table",
|
|
7
|
+
"Sheet",
|
|
8
|
+
"Excel",
|
|
9
|
+
"save",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
from excelipy.models import (
|
|
13
|
+
Style,
|
|
14
|
+
Component,
|
|
15
|
+
Fill,
|
|
16
|
+
Text,
|
|
17
|
+
Table,
|
|
18
|
+
Sheet,
|
|
19
|
+
Excel,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from excelipy.service import save
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
PROP_MAP = dict(
|
|
2
|
+
align="align",
|
|
3
|
+
valign="valign",
|
|
4
|
+
font_size="font_size",
|
|
5
|
+
font_color="font_color",
|
|
6
|
+
font_family="font_name",
|
|
7
|
+
bold="bold",
|
|
8
|
+
border="border",
|
|
9
|
+
border_left="left",
|
|
10
|
+
border_right="right",
|
|
11
|
+
border_top="top",
|
|
12
|
+
border_bottom="bottom",
|
|
13
|
+
border_color="border_color",
|
|
14
|
+
background="bg_color",
|
|
15
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
import excelipy as ep
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
df = pd.DataFrame(
|
|
11
|
+
{
|
|
12
|
+
"testing": [1, 2, 3],
|
|
13
|
+
"tested": ["Yay", "Thanks", "Bud"],
|
|
14
|
+
}
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
sheets = [
|
|
18
|
+
ep.Sheet(
|
|
19
|
+
name="Hello!",
|
|
20
|
+
components=[
|
|
21
|
+
ep.Text(
|
|
22
|
+
text="This is my table",
|
|
23
|
+
style=ep.Style(bold=True),
|
|
24
|
+
width=4,
|
|
25
|
+
),
|
|
26
|
+
ep.Fill(
|
|
27
|
+
width=4,
|
|
28
|
+
style=ep.Style(background="#D0D0D0"),
|
|
29
|
+
),
|
|
30
|
+
ep.Table(
|
|
31
|
+
data=df,
|
|
32
|
+
header_style=ep.Style(
|
|
33
|
+
bold=True,
|
|
34
|
+
border=5,
|
|
35
|
+
border_color="#F02932",
|
|
36
|
+
),
|
|
37
|
+
body_style=ep.Style(font_size=18),
|
|
38
|
+
column_style={
|
|
39
|
+
"testing": ep.Style(
|
|
40
|
+
font_size=10,
|
|
41
|
+
align="center",
|
|
42
|
+
),
|
|
43
|
+
},
|
|
44
|
+
column_width={
|
|
45
|
+
"tested": 20,
|
|
46
|
+
},
|
|
47
|
+
row_style={
|
|
48
|
+
1: ep.Style(
|
|
49
|
+
border=2,
|
|
50
|
+
border_color="#F02932",
|
|
51
|
+
)
|
|
52
|
+
},
|
|
53
|
+
style=ep.Style(padding=1),
|
|
54
|
+
).with_stripes(pattern="even"),
|
|
55
|
+
],
|
|
56
|
+
style=ep.Style(
|
|
57
|
+
font_size=14,
|
|
58
|
+
font_family="Times New Roman",
|
|
59
|
+
padding=1,
|
|
60
|
+
),
|
|
61
|
+
),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
excel = ep.Excel(
|
|
65
|
+
path=Path("filename.xlsx"),
|
|
66
|
+
sheets=sheets,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
ep.save(excel)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
74
|
+
main()
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Dict, Optional, Sequence, Literal
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Style(BaseModel):
|
|
9
|
+
class Config:
|
|
10
|
+
frozen = True
|
|
11
|
+
|
|
12
|
+
align: Optional[
|
|
13
|
+
Literal[
|
|
14
|
+
"left",
|
|
15
|
+
"center",
|
|
16
|
+
"right",
|
|
17
|
+
"fill",
|
|
18
|
+
"justify",
|
|
19
|
+
"center_across",
|
|
20
|
+
"distributed",
|
|
21
|
+
]
|
|
22
|
+
] = Field(default=None)
|
|
23
|
+
valign: Optional[
|
|
24
|
+
Literal[
|
|
25
|
+
"top",
|
|
26
|
+
"vcenter",
|
|
27
|
+
"bottom",
|
|
28
|
+
"vcenter",
|
|
29
|
+
"bottom",
|
|
30
|
+
"vjustify",
|
|
31
|
+
]
|
|
32
|
+
] = Field(default=None)
|
|
33
|
+
padding: Optional[int] = Field(default=None)
|
|
34
|
+
padding_left: Optional[int] = Field(default=None)
|
|
35
|
+
padding_right: Optional[int] = Field(default=None)
|
|
36
|
+
padding_top: Optional[int] = Field(default=None)
|
|
37
|
+
padding_bottom: Optional[int] = Field(default=None)
|
|
38
|
+
font_size: Optional[int] = Field(default=None)
|
|
39
|
+
font_color: Optional[str] = Field(default=None)
|
|
40
|
+
font_family: Optional[str] = Field(default=None)
|
|
41
|
+
bold: Optional[bool] = Field(default=None)
|
|
42
|
+
border: Optional[int] = Field(default=None)
|
|
43
|
+
border_left: Optional[int] = Field(default=None)
|
|
44
|
+
border_right: Optional[int] = Field(default=None)
|
|
45
|
+
border_top: Optional[int] = Field(default=None)
|
|
46
|
+
border_bottom: Optional[int] = Field(default=None)
|
|
47
|
+
border_color: Optional[str] = Field(default=None)
|
|
48
|
+
background: Optional[str] = Field(default=None)
|
|
49
|
+
|
|
50
|
+
def merge(self, other: "Style") -> "Style":
|
|
51
|
+
self_dict = self.model_dump(exclude_none=True)
|
|
52
|
+
other_dict = other.model_dump(exclude_none=True)
|
|
53
|
+
self_dict.update(other_dict)
|
|
54
|
+
return self.model_validate(self_dict)
|
|
55
|
+
|
|
56
|
+
def pl(self) -> int:
|
|
57
|
+
return self.padding_left or self.padding or 0
|
|
58
|
+
|
|
59
|
+
def pt(self) -> int:
|
|
60
|
+
return self.padding_top or self.padding or 0
|
|
61
|
+
|
|
62
|
+
def pr(self) -> int:
|
|
63
|
+
return self.padding_right or self.padding or 0
|
|
64
|
+
|
|
65
|
+
def pb(self) -> int:
|
|
66
|
+
return self.padding_bottom or self.padding or 0
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Component(BaseModel):
|
|
70
|
+
style: Style = Field(default_factory=Style)
|
|
71
|
+
|
|
72
|
+
class Config:
|
|
73
|
+
arbitrary_types_allowed = True
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Text(Component):
|
|
77
|
+
text: str
|
|
78
|
+
width: int = Field(default=1)
|
|
79
|
+
height: int = Field(default=1)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Fill(Component):
|
|
83
|
+
width: int = Field(default=1)
|
|
84
|
+
height: int = Field(default=1)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Table(Component):
|
|
88
|
+
data: pd.DataFrame
|
|
89
|
+
header_style: Style = Field(default_factory=Style)
|
|
90
|
+
body_style: Style = Field(default_factory=Style)
|
|
91
|
+
column_style: Dict[str, Style] = Field(default_factory=dict)
|
|
92
|
+
column_width: Dict[str, int] = Field(default_factory=dict)
|
|
93
|
+
row_style: Dict[int, Style] = Field(default_factory=dict)
|
|
94
|
+
max_col_width: Optional[int] = Field(default=None)
|
|
95
|
+
header_filters: bool = Field(default=True)
|
|
96
|
+
|
|
97
|
+
def with_stripes(
|
|
98
|
+
self,
|
|
99
|
+
color: str = "#D0D0D0",
|
|
100
|
+
pattern: Literal["even", "odd"] = "odd",
|
|
101
|
+
) -> "Table":
|
|
102
|
+
return self.model_copy(
|
|
103
|
+
update=dict(
|
|
104
|
+
row_style={
|
|
105
|
+
idx: self.row_style.get(idx, Style()).merge(Style(background=color))
|
|
106
|
+
if (pattern == "odd" and idx % 2 != 0)
|
|
107
|
+
or (pattern == "even" and idx % 2 == 0)
|
|
108
|
+
else self.row_style.get(idx, Style())
|
|
109
|
+
for idx in range(self.data.shape[0])
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Sheet(BaseModel):
|
|
116
|
+
name: str
|
|
117
|
+
components: Sequence[Component] = Field(default_factory=list)
|
|
118
|
+
style: Style = Field(default_factory=Style)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class Excel(BaseModel):
|
|
122
|
+
path: Path
|
|
123
|
+
sheets: Sequence[Sheet] = Field(default_factory=list)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
import xlsxwriter
|
|
5
|
+
from xlsxwriter.workbook import Workbook, Worksheet
|
|
6
|
+
|
|
7
|
+
from excelipy.models import Component, Excel, Fill, Style, Table, Text
|
|
8
|
+
from excelipy.writers import (
|
|
9
|
+
write_fill,
|
|
10
|
+
write_table,
|
|
11
|
+
write_text,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger("excelipy")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def write_component(
|
|
18
|
+
workbook: Workbook,
|
|
19
|
+
worksheet: Worksheet,
|
|
20
|
+
component: Component,
|
|
21
|
+
default_style: Style,
|
|
22
|
+
origin: Tuple[int, int] = (0, 0),
|
|
23
|
+
) -> Tuple[int, int]:
|
|
24
|
+
writing_map = {
|
|
25
|
+
Table: write_table,
|
|
26
|
+
Text: write_text,
|
|
27
|
+
Fill: write_fill,
|
|
28
|
+
}
|
|
29
|
+
render_func = writing_map.get(type(component))
|
|
30
|
+
if render_func is None:
|
|
31
|
+
return 0, 0
|
|
32
|
+
return render_func(
|
|
33
|
+
workbook,
|
|
34
|
+
worksheet,
|
|
35
|
+
component,
|
|
36
|
+
default_style,
|
|
37
|
+
origin,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def save(excel: Excel):
|
|
42
|
+
workbook = xlsxwriter.Workbook(excel.path)
|
|
43
|
+
log.debug("Workbook opened")
|
|
44
|
+
for sheet in excel.sheets:
|
|
45
|
+
origin = (
|
|
46
|
+
sheet.style.pl(),
|
|
47
|
+
sheet.style.pt(),
|
|
48
|
+
)
|
|
49
|
+
worksheet = workbook.add_worksheet(sheet.name)
|
|
50
|
+
for component in sheet.components:
|
|
51
|
+
cur_origin = (
|
|
52
|
+
origin[0] + component.style.pl(),
|
|
53
|
+
origin[1] + component.style.pt(),
|
|
54
|
+
)
|
|
55
|
+
x, y = write_component(
|
|
56
|
+
workbook,
|
|
57
|
+
worksheet,
|
|
58
|
+
component,
|
|
59
|
+
sheet.style,
|
|
60
|
+
cur_origin,
|
|
61
|
+
)
|
|
62
|
+
origin = origin[0], origin[1] + y
|
|
63
|
+
|
|
64
|
+
workbook.close()
|
|
65
|
+
log.debug("Workbook closed")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Dict, Sequence
|
|
2
|
+
|
|
3
|
+
from xlsxwriter.workbook import Format, Workbook
|
|
4
|
+
|
|
5
|
+
from excelipy.const import PROP_MAP
|
|
6
|
+
from excelipy.models import Style
|
|
7
|
+
|
|
8
|
+
cached_styles: Dict[Style, Format] = {}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _process_single(workbook: Workbook, style: Style) -> Format:
|
|
12
|
+
style_dict = style.model_dump(exclude_none=True)
|
|
13
|
+
style_map = {
|
|
14
|
+
mapped_prop: value
|
|
15
|
+
for property, value in style_dict.items()
|
|
16
|
+
if (mapped_prop := PROP_MAP.get(property)) is not None
|
|
17
|
+
}
|
|
18
|
+
format = workbook.add_format(style_map)
|
|
19
|
+
cached_styles[style] = format
|
|
20
|
+
return format
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def process_style(
|
|
24
|
+
workbook: Workbook,
|
|
25
|
+
styles: Sequence[Style],
|
|
26
|
+
) -> Format:
|
|
27
|
+
cur_style = Style()
|
|
28
|
+
for style in styles:
|
|
29
|
+
cur_style = cur_style.merge(style)
|
|
30
|
+
if cur_style in cached_styles:
|
|
31
|
+
return cached_styles[cur_style]
|
|
32
|
+
format = _process_single(workbook, cur_style)
|
|
33
|
+
return format
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
from xlsxwriter.workbook import Workbook, Worksheet
|
|
5
|
+
|
|
6
|
+
from excelipy.models import Fill, Style
|
|
7
|
+
from excelipy.style import process_style
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger("excelipy")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def write_fill(
|
|
13
|
+
workbook: Workbook,
|
|
14
|
+
worksheet: Worksheet,
|
|
15
|
+
component: Fill,
|
|
16
|
+
default_style: Style,
|
|
17
|
+
origin: Tuple[int, int] = (0, 0),
|
|
18
|
+
) -> Tuple[int, int]:
|
|
19
|
+
log.debug(f"Writing fill at {origin}")
|
|
20
|
+
worksheet.merge_range(
|
|
21
|
+
origin[1],
|
|
22
|
+
origin[0],
|
|
23
|
+
origin[1] + component.height - 1,
|
|
24
|
+
origin[0] + component.width - 1,
|
|
25
|
+
"",
|
|
26
|
+
process_style(
|
|
27
|
+
workbook,
|
|
28
|
+
[
|
|
29
|
+
default_style,
|
|
30
|
+
component.style,
|
|
31
|
+
],
|
|
32
|
+
),
|
|
33
|
+
)
|
|
34
|
+
return component.width, component.height
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
from xlsxwriter.workbook import Workbook, Worksheet
|
|
5
|
+
|
|
6
|
+
from excelipy.models import Style, Table
|
|
7
|
+
from excelipy.style import process_style
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger("excelipy")
|
|
10
|
+
|
|
11
|
+
DEFAULT_FONT_SIZE = 11
|
|
12
|
+
SCALING_FACTOR = 1
|
|
13
|
+
BASE_PADDING = 2
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_auto_width(
|
|
17
|
+
header: str,
|
|
18
|
+
component: Table,
|
|
19
|
+
default_style: Style,
|
|
20
|
+
) -> int:
|
|
21
|
+
header_len = len(header)
|
|
22
|
+
col_len = component.data[header].apply(str).apply(len).max()
|
|
23
|
+
max_len = max(header_len, col_len)
|
|
24
|
+
max_font_size = max(
|
|
25
|
+
(
|
|
26
|
+
component.header_style.font_size
|
|
27
|
+
or default_style.font_size
|
|
28
|
+
or DEFAULT_FONT_SIZE
|
|
29
|
+
),
|
|
30
|
+
(
|
|
31
|
+
component.column_style.get(header, Style()).font_size
|
|
32
|
+
or component.body_style.font_size
|
|
33
|
+
or default_style.font_size
|
|
34
|
+
or DEFAULT_FONT_SIZE
|
|
35
|
+
),
|
|
36
|
+
(
|
|
37
|
+
max(
|
|
38
|
+
s.font_size
|
|
39
|
+
or component.body_style.font_size
|
|
40
|
+
or default_style.font_size
|
|
41
|
+
or DEFAULT_FONT_SIZE
|
|
42
|
+
for s in component.row_style.values()
|
|
43
|
+
)
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
font_factor = max_font_size / DEFAULT_FONT_SIZE
|
|
47
|
+
return SCALING_FACTOR * font_factor * max_len + BASE_PADDING
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def write_table(
|
|
51
|
+
workbook: Workbook,
|
|
52
|
+
worksheet: Worksheet,
|
|
53
|
+
component: Table,
|
|
54
|
+
default_style: Style,
|
|
55
|
+
origin: Tuple[int, int] = (0, 0),
|
|
56
|
+
) -> Tuple[int, int]:
|
|
57
|
+
x_size = component.data.shape[1]
|
|
58
|
+
y_size = component.data.shape[0]
|
|
59
|
+
|
|
60
|
+
header_format = process_style(workbook, [default_style, component.header_style])
|
|
61
|
+
for col_idx, header in enumerate(component.data.columns):
|
|
62
|
+
worksheet.write(
|
|
63
|
+
origin[1],
|
|
64
|
+
origin[0] + col_idx,
|
|
65
|
+
header,
|
|
66
|
+
header_format,
|
|
67
|
+
)
|
|
68
|
+
set_width = component.column_width.get(header)
|
|
69
|
+
if set_width:
|
|
70
|
+
estimated_width = set_width
|
|
71
|
+
else:
|
|
72
|
+
estimated_width = get_auto_width(header, component, default_style)
|
|
73
|
+
worksheet.set_column(origin[1], origin[0] + col_idx, int(estimated_width))
|
|
74
|
+
|
|
75
|
+
if component.header_filters:
|
|
76
|
+
worksheet.autofilter(
|
|
77
|
+
origin[1],
|
|
78
|
+
origin[0],
|
|
79
|
+
origin[1],
|
|
80
|
+
origin[0] + len(list(component.data.columns)) - 1,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
for col_idx, col in enumerate(component.data.columns):
|
|
84
|
+
col_style = component.column_style.get(col)
|
|
85
|
+
for row_idx, (_, row) in enumerate(component.data.iterrows()):
|
|
86
|
+
row_style = component.row_style.get(row_idx)
|
|
87
|
+
non_none = filter(
|
|
88
|
+
None,
|
|
89
|
+
[
|
|
90
|
+
default_style,
|
|
91
|
+
component.body_style,
|
|
92
|
+
col_style,
|
|
93
|
+
row_style,
|
|
94
|
+
],
|
|
95
|
+
)
|
|
96
|
+
current_format = process_style(workbook, list(non_none))
|
|
97
|
+
cell = row[col]
|
|
98
|
+
worksheet.write(
|
|
99
|
+
origin[1] + row_idx + 1,
|
|
100
|
+
origin[0] + col_idx,
|
|
101
|
+
cell,
|
|
102
|
+
current_format,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return x_size, y_size
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
from xlsxwriter.workbook import Workbook, Worksheet
|
|
5
|
+
|
|
6
|
+
from excelipy.models import Style, Text
|
|
7
|
+
from excelipy.style import process_style
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger("excelipy")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def write_text(
|
|
13
|
+
workbook: Workbook,
|
|
14
|
+
worksheet: Worksheet,
|
|
15
|
+
component: Text,
|
|
16
|
+
default_style: Style,
|
|
17
|
+
origin: Tuple[int, int] = (0, 0),
|
|
18
|
+
) -> Tuple[int, int]:
|
|
19
|
+
log.debug(f"Writing text at {origin}")
|
|
20
|
+
|
|
21
|
+
worksheet.merge_range(
|
|
22
|
+
origin[1],
|
|
23
|
+
origin[0],
|
|
24
|
+
origin[1] + component.height - 1,
|
|
25
|
+
origin[0] + component.width - 1,
|
|
26
|
+
"",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
worksheet.write(
|
|
30
|
+
origin[1],
|
|
31
|
+
origin[0],
|
|
32
|
+
component.text,
|
|
33
|
+
process_style(
|
|
34
|
+
workbook,
|
|
35
|
+
[
|
|
36
|
+
default_style,
|
|
37
|
+
component.style,
|
|
38
|
+
],
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
return component.width, component.height
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: excelipy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Wrapper around xlsxwriter to improve usability
|
|
5
|
+
Requires-Python: >=3.9.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: pandas>=2.2.3
|
|
8
|
+
Requires-Dist: pydantic>=2.11.3
|
|
9
|
+
Requires-Dist: xlsxwriter>=3.2.2
|
|
10
|
+
|
|
11
|
+
# Excelipy
|
|
12
|
+
|
|
13
|
+
[](https://codecov.io/gh/choinhet/excelipy)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
excelipy/__init__.py
|
|
5
|
+
excelipy/const.py
|
|
6
|
+
excelipy/main.py
|
|
7
|
+
excelipy/models.py
|
|
8
|
+
excelipy/service.py
|
|
9
|
+
excelipy/style.py
|
|
10
|
+
excelipy.egg-info/PKG-INFO
|
|
11
|
+
excelipy.egg-info/SOURCES.txt
|
|
12
|
+
excelipy.egg-info/dependency_links.txt
|
|
13
|
+
excelipy.egg-info/requires.txt
|
|
14
|
+
excelipy.egg-info/top_level.txt
|
|
15
|
+
excelipy/writers/__init__.py
|
|
16
|
+
excelipy/writers/fill.py
|
|
17
|
+
excelipy/writers/table.py
|
|
18
|
+
excelipy/writers/text.py
|
|
19
|
+
test/test_load.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
excelipy
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "excelipy"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Wrapper around xlsxwriter to improve usability"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.9.10"
|
|
7
|
+
|
|
8
|
+
dependencies = ["pandas>=2.2.3", "pydantic>=2.11.3", "xlsxwriter>=3.2.2"]
|
|
9
|
+
|
|
10
|
+
[tool.uv]
|
|
11
|
+
dev-dependencies = [
|
|
12
|
+
"pytest>=8.3.3",
|
|
13
|
+
"pytest-cov>=4.1.0",
|
|
14
|
+
"coverage>=7.6.1",
|
|
15
|
+
"pytest-mock>=3.14.0",
|
|
16
|
+
"pytest-asyncio>=0.25.3",
|
|
17
|
+
"debugpy>=1.8.13",
|
|
18
|
+
"pyright>=1.1.398",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
packages = ["excelipy"]
|
excelipy-0.1.0/setup.cfg
ADDED