df2tables 0.0.1__py3-none-any.whl
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.
- df2tables/__init__.py +2 -0
- df2tables/comnt.py +227 -0
- df2tables/datatable_templ.html +192 -0
- df2tables/df2tables.py +161 -0
- df2tables/examples.py +112 -0
- df2tables-0.0.1.dist-info/METADATA +12 -0
- df2tables-0.0.1.dist-info/RECORD +10 -0
- df2tables-0.0.1.dist-info/WHEEL +5 -0
- df2tables-0.0.1.dist-info/licenses/LICENCE.txt +21 -0
- df2tables-0.0.1.dist-info/top_level.txt +1 -0
df2tables/__init__.py
ADDED
df2tables/comnt.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# coding=utf8
|
|
3
|
+
from sys import exc_info
|
|
4
|
+
"""
|
|
5
|
+
Comnt - template content using block-annotated comments
|
|
6
|
+
|
|
7
|
+
A dead-simple, human-friendly way to manage template regions using
|
|
8
|
+
**standard comment syntax** — no weird symbols, no broken files, no preprocessing
|
|
9
|
+
to view your code in a browser.
|
|
10
|
+
|
|
11
|
+
One huge practical benefit: you can inject actual JavaScript-ready values from Python—
|
|
12
|
+
not just strings or you can refer to javascript functions defined elsewhere.
|
|
13
|
+
That means you don’t need to wrap your data in quotes and then call
|
|
14
|
+
`JSON.parse(...)` on the frontend, like you often do with Jinja2.
|
|
15
|
+
|
|
16
|
+
Supported tag/comment Formats:
|
|
17
|
+
HTML/XML style:
|
|
18
|
+
<!--[tag_name-->
|
|
19
|
+
Content to be replaced
|
|
20
|
+
<!--tag_name]-->
|
|
21
|
+
|
|
22
|
+
JavaScript/CSS style:
|
|
23
|
+
/*[tag_name*/
|
|
24
|
+
const arr_to_change = [0,1]
|
|
25
|
+
/*tag_name]*/
|
|
26
|
+
|
|
27
|
+
Tag Format Rules:
|
|
28
|
+
- Opening tag: Comment start + '[' + tag_name + Comment continuation
|
|
29
|
+
- Closing tag: Comment start + tag_name + ']' + Comment end
|
|
30
|
+
- Tag names must match exactly between opening and closing tags
|
|
31
|
+
|
|
32
|
+
Author: [Tomasz Sługocki]
|
|
33
|
+
Version: 0.0.1
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
__all__ = ['render', 'write_from_template', 'simple_example', 'example']
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class NotFoundError(Exception):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _render_block(text, tag_id, val=None):
|
|
44
|
+
assert isinstance(text, str) and isinstance(tag_id, str)
|
|
45
|
+
assert len(text) > len(tag_id)
|
|
46
|
+
|
|
47
|
+
assert val is None or isinstance(val, str)
|
|
48
|
+
|
|
49
|
+
def get_tags(text, tag_id, ext):
|
|
50
|
+
assert ext in ("html", "js")
|
|
51
|
+
start, end = ("/*", "*/") if ext == "js" else ("<!--", "-->")
|
|
52
|
+
start_tag_js, start_tag_html = f"/*[{tag_id}*/", f"<!--[{tag_id}-->"
|
|
53
|
+
if start_tag_js in text and start_tag_html in text:
|
|
54
|
+
raise AssertionError(f"same tag id in javascript and html is not allowed")
|
|
55
|
+
start_tag = "".join((start, "[", tag_id, end))
|
|
56
|
+
end_tag = "".join((start, tag_id, "]", end))
|
|
57
|
+
start_cnt = len(text.split(start_tag)) - 1
|
|
58
|
+
end_cnt = len(text.split(end_tag)) - 1
|
|
59
|
+
if not start_cnt and not end_cnt:
|
|
60
|
+
raise ValueError(f"not found tags {start_tag} {end_tag}, ext:{ext}")
|
|
61
|
+
if not start_cnt:
|
|
62
|
+
raise NotFoundError(f"start tag not found in form: '{start_tag}'")
|
|
63
|
+
if not end_cnt:
|
|
64
|
+
raise NotFoundError(f"end tag not found in form: '{end_tag}'")
|
|
65
|
+
if start_cnt > 1:
|
|
66
|
+
raise AssertionError(f"more than one start tag: '{start_tag}'")
|
|
67
|
+
if end_cnt > 1:
|
|
68
|
+
raise AssertionError(f"more than one end tag: '{end_tag}'")
|
|
69
|
+
return start_tag, end_tag
|
|
70
|
+
|
|
71
|
+
start_tag, end_tag = None, None
|
|
72
|
+
errs = []
|
|
73
|
+
for ext in ["js", "html"]:
|
|
74
|
+
try:
|
|
75
|
+
start_tag, end_tag = get_tags(text, tag_id, ext)
|
|
76
|
+
except ValueError:
|
|
77
|
+
errs.append(repr(exc_info()[1]))
|
|
78
|
+
if not start_tag:
|
|
79
|
+
raise ValueError(f"Errors happened: {''.join(errs)}")
|
|
80
|
+
|
|
81
|
+
start_idx = text.find(start_tag)
|
|
82
|
+
end_idx = text.find(end_tag)
|
|
83
|
+
prefix = text[:start_idx + len(start_tag)]
|
|
84
|
+
suffix = text[end_idx:]
|
|
85
|
+
|
|
86
|
+
if val is None: # empty strings are ok
|
|
87
|
+
old_val = text[start_idx + len(start_tag):end_idx]
|
|
88
|
+
return old_val
|
|
89
|
+
return "\n".join((prefix, val, suffix))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_tag_content(instr, tag):
|
|
93
|
+
assert len(instr) > len(tag) and tag + instr
|
|
94
|
+
return _render_block(instr, tag, None)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def render(instr, repldict):
|
|
98
|
+
for key, val in repldict.items():
|
|
99
|
+
try:
|
|
100
|
+
instr = _render_block(instr, key, val)
|
|
101
|
+
except ValueError:
|
|
102
|
+
print(exc_info()[1])
|
|
103
|
+
|
|
104
|
+
return instr
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def write_from_template(template, newfile, repldict):
|
|
108
|
+
assert isinstance(repldict, dict)
|
|
109
|
+
assert template != newfile
|
|
110
|
+
with open(template, encoding="utf-8") as op_file:
|
|
111
|
+
instr = op_file.read()
|
|
112
|
+
replaced = render(instr, repldict)
|
|
113
|
+
with open(newfile, "w", encoding="utf8") as outfile:
|
|
114
|
+
outfile.write(replaced)
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def example():
|
|
119
|
+
import os
|
|
120
|
+
import subprocess
|
|
121
|
+
import sys
|
|
122
|
+
|
|
123
|
+
def open_file(filename):
|
|
124
|
+
if sys.platform.startswith("win"):
|
|
125
|
+
os.startfile(filename)
|
|
126
|
+
else:
|
|
127
|
+
opener = "open" if sys.platform == "darwin" else "xdg-open"
|
|
128
|
+
subprocess.call([opener, filename])
|
|
129
|
+
|
|
130
|
+
content = """
|
|
131
|
+
<!DOCTYPE html>
|
|
132
|
+
<html>
|
|
133
|
+
<head>
|
|
134
|
+
<title>
|
|
135
|
+
Example comnt rendered page
|
|
136
|
+
</title>
|
|
137
|
+
|
|
138
|
+
</head>
|
|
139
|
+
<body> <h3>
|
|
140
|
+
<!--[title-->
|
|
141
|
+
Welcome to our site!
|
|
142
|
+
|
|
143
|
+
<!--title]--></h3>
|
|
144
|
+
<!--[content-->
|
|
145
|
+
|
|
146
|
+
<p>
|
|
147
|
+
This is placeholder content that shows in the browser.
|
|
148
|
+
</p>
|
|
149
|
+
<!--content]-->
|
|
150
|
+
<code id="data-display" style=
|
|
151
|
+
"background-color: rgb(245, 245, 245); padding: 10px; border: 1px solid rgb(221, 221, 221);">
|
|
152
|
+
</code>
|
|
153
|
+
<script>
|
|
154
|
+
// Example data array for
|
|
155
|
+
const data = /*[data_arr*/ [0, 1]
|
|
156
|
+
/*data_arr]*/;
|
|
157
|
+
|
|
158
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
159
|
+
// Display data array in paragraph (like Python's repr)
|
|
160
|
+
const dataDisplay = document.getElementById('data-display');
|
|
161
|
+
dataDisplay.textContent = JSON.stringify(data, null, 2);
|
|
162
|
+
});
|
|
163
|
+
</script>
|
|
164
|
+
</body>
|
|
165
|
+
</html>
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
outstr = render(
|
|
169
|
+
content,
|
|
170
|
+
{
|
|
171
|
+
"title": "Example rendered python object",
|
|
172
|
+
"content": "<p>Below python range rendered as javascript variable</p>",
|
|
173
|
+
"data_arr": repr(list(range(10))), # dont even need json here
|
|
174
|
+
# , 'none_existing' : '0'
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
file_name = os.path.join(os.getcwd(), "comnt_test.html")
|
|
178
|
+
with open(file_name, "w", encoding="utf8") as outfile:
|
|
179
|
+
outfile.write(outstr)
|
|
180
|
+
open_file(file_name)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def simple_example():
|
|
184
|
+
import json
|
|
185
|
+
import os
|
|
186
|
+
import subprocess
|
|
187
|
+
import sys
|
|
188
|
+
|
|
189
|
+
def open_file(filename):
|
|
190
|
+
if sys.platform.startswith("win"):
|
|
191
|
+
os.startfile(filename)
|
|
192
|
+
else:
|
|
193
|
+
opener = "open" if sys.platform == "darwin" else "xdg-open"
|
|
194
|
+
subprocess.call([opener, filename])
|
|
195
|
+
|
|
196
|
+
content = """
|
|
197
|
+
<!DOCTYPE html>
|
|
198
|
+
<p id="title"> </p>
|
|
199
|
+
<div id="data_arr"></div>
|
|
200
|
+
<script>
|
|
201
|
+
const title = /*[title*/
|
|
202
|
+
"Example title"
|
|
203
|
+
/*title]*/;
|
|
204
|
+
const data = /*[data_arr*/
|
|
205
|
+
[0, 1]
|
|
206
|
+
/*data_arr]*/;
|
|
207
|
+
|
|
208
|
+
document.getElementById("data_arr").textContent = JSON.stringify(data);
|
|
209
|
+
document.getElementById("title").textContent = title;
|
|
210
|
+
</script>
|
|
211
|
+
"""
|
|
212
|
+
outstr = render(
|
|
213
|
+
content,
|
|
214
|
+
{
|
|
215
|
+
"title": json.dumps("Example rendered python object"),
|
|
216
|
+
"data_arr": json.dumps(list(range(10))),
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
file_name = os.path.join(os.getcwd(), "comnt_simple.html")
|
|
220
|
+
with open(file_name, "w", encoding="utf8") as outfile:
|
|
221
|
+
outfile.write(outstr)
|
|
222
|
+
open_file(file_name)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
if __name__ == "__main__":
|
|
226
|
+
# simple_example()
|
|
227
|
+
example()
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<link
|
|
5
|
+
rel="stylesheet"
|
|
6
|
+
href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css"
|
|
7
|
+
integrity="sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls"
|
|
8
|
+
crossorigin="anonymous"
|
|
9
|
+
/>
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
11
|
+
<title>DataFrame</title>
|
|
12
|
+
<script
|
|
13
|
+
src="https://code.jquery.com/jquery-3.7.1.min.js"
|
|
14
|
+
></script>
|
|
15
|
+
<link
|
|
16
|
+
href="https://cdn.datatables.net/2.3.2/css/dataTables.dataTables.min.css"
|
|
17
|
+
rel="stylesheet"
|
|
18
|
+
/>
|
|
19
|
+
<script src="https://cdn.datatables.net/2.3.2/js/dataTables.min.js"></script>
|
|
20
|
+
<style>
|
|
21
|
+
<style > .dataTables_wrapper {
|
|
22
|
+
margin-top: 20px;
|
|
23
|
+
}
|
|
24
|
+
td.dt-nowrap {
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
}
|
|
27
|
+
table.dataTable.display tbody td {
|
|
28
|
+
white-space: nowrap;
|
|
29
|
+
}
|
|
30
|
+
table.dataTable.display tbody td span {
|
|
31
|
+
|
|
32
|
+
float: right;
|
|
33
|
+
}
|
|
34
|
+
.dt-info {
|
|
35
|
+
padding: 1em;
|
|
36
|
+
font-size: 10pt;
|
|
37
|
+
color: gray;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<!--[min_content-->
|
|
43
|
+
<div class="pure-g">
|
|
44
|
+
<div class="pure-u-23-24">
|
|
45
|
+
<div id="tabcnt" style="width: fit-content; margin-left: 1em">
|
|
46
|
+
|
|
47
|
+
<!-- <div id="tabcnt" style="width: 800px; margin-left: 1em"> -->
|
|
48
|
+
<p>
|
|
49
|
+
<!--[title-->
|
|
50
|
+
Example datatable
|
|
51
|
+
<!--title]-->
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
<table
|
|
55
|
+
id="pd_datatab"
|
|
56
|
+
class="display compact hover"
|
|
57
|
+
style="font-size: 10pt"
|
|
58
|
+
></table>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<script type="text/javascript">
|
|
62
|
+
function _anyNumber(a) {
|
|
63
|
+
var reg = /[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?/;
|
|
64
|
+
if (typeof a === "string") {
|
|
65
|
+
a = a.replace(",", ".").replace(" ", "").match(reg);
|
|
66
|
+
a = a !== null ? parseFloat(a[0]) : Number.POSITIVE_INFINITY;
|
|
67
|
+
}
|
|
68
|
+
return a;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
jQuery.extend(jQuery.fn.dataTableExt.oSort, {
|
|
72
|
+
"num-html-pre": function (a) {
|
|
73
|
+
var x = String(a).replace(/<[\s\S]*?>/g, "");
|
|
74
|
+
return parseFloat(_anyNumber(x));
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
"num-html-asc": function (a, b) {
|
|
78
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
"num-html-desc": function (a, b) {
|
|
82
|
+
return a < b ? 1 : a > b ? -1 : 0;
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
const render_num = (data, type) => {
|
|
86
|
+
const number = DataTable.render.number(" ", ",", 2).display(data);
|
|
87
|
+
|
|
88
|
+
if (type !== "display") return number;
|
|
89
|
+
|
|
90
|
+
const color = data < 0 ? "red" : "black";
|
|
91
|
+
return `<span style="color:${color}">${number}</span>`;
|
|
92
|
+
};
|
|
93
|
+
$(document).ready(function () {
|
|
94
|
+
const table = $("#pd_datatab").DataTable({
|
|
95
|
+
data: data,
|
|
96
|
+
autoWidth: true,
|
|
97
|
+
columns: columns,
|
|
98
|
+
pageLength: 100,
|
|
99
|
+
responsive: true,
|
|
100
|
+
scrollX: true,
|
|
101
|
+
layout: {
|
|
102
|
+
topStart: {
|
|
103
|
+
search: {
|
|
104
|
+
placeholder: "Search",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
topEnd: "info",
|
|
108
|
+
},
|
|
109
|
+
order: [],
|
|
110
|
+
language: {
|
|
111
|
+
search: "Filter:",
|
|
112
|
+
},
|
|
113
|
+
initComplete: function () {
|
|
114
|
+
// Add a note about searchable columns
|
|
115
|
+
const searchableColumns = search_columns;
|
|
116
|
+
const searchNote = $("<p>")
|
|
117
|
+
.css({
|
|
118
|
+
"margin-bottom": "10px",
|
|
119
|
+
"font-size": "0.7em",
|
|
120
|
+
color: "#666",
|
|
121
|
+
})
|
|
122
|
+
.text(
|
|
123
|
+
"Search is enabled for text columns: " +
|
|
124
|
+
searchableColumns.join(", "),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
$("#pd_datatab_wrapper").prepend(searchNote);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const data =
|
|
133
|
+
/*[tab_data*/
|
|
134
|
+
[
|
|
135
|
+
["2025-06-22T10:26:53.635125", 0.09, 2.11, -0.33, -1000, "a"],
|
|
136
|
+
[
|
|
137
|
+
"Lorem ipsum dolor sit amet, consectetur adipiscing",
|
|
138
|
+
-0.59,
|
|
139
|
+
1,
|
|
140
|
+
1.0,
|
|
141
|
+
1,
|
|
142
|
+
"B",
|
|
143
|
+
],
|
|
144
|
+
["<b>Integer</b> laoreet odio et.", 0.2, 9, -9.0, 2, "c"],
|
|
145
|
+
[NaN, -0.49, 8, 4.0, 3, "D"],
|
|
146
|
+
[" class 'datetime.datetime' ", -0.18, 7, 2.0, 4, "e"],
|
|
147
|
+
[
|
|
148
|
+
" function simple. locals . lambda at 0x7847f6b207c0 ",
|
|
149
|
+
-0.8,
|
|
150
|
+
4,
|
|
151
|
+
3.0,
|
|
152
|
+
5,
|
|
153
|
+
"F",
|
|
154
|
+
],
|
|
155
|
+
["C", -0.52, true, 1111.11, 70000, "X "],
|
|
156
|
+
];
|
|
157
|
+
/*tab_data]*/
|
|
158
|
+
|
|
159
|
+
const columns =
|
|
160
|
+
/*[tab_columns*/
|
|
161
|
+
[
|
|
162
|
+
{ title: "col1", searchable: true },
|
|
163
|
+
{
|
|
164
|
+
title: "col2",
|
|
165
|
+
searchable: false,
|
|
166
|
+
render: render_num,
|
|
167
|
+
type: "num-html",
|
|
168
|
+
},
|
|
169
|
+
{ title: "col3", searchable: true },
|
|
170
|
+
{
|
|
171
|
+
title: "col4",
|
|
172
|
+
searchable: false,
|
|
173
|
+
render: render_num,
|
|
174
|
+
type: "num-html",
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
title: "col5",
|
|
178
|
+
searchable: false,
|
|
179
|
+
render: render_num,
|
|
180
|
+
type: "num-html",
|
|
181
|
+
},
|
|
182
|
+
{ title: "col6", searchable: true },
|
|
183
|
+
];
|
|
184
|
+
/*tab_columns]*/
|
|
185
|
+
|
|
186
|
+
const search_columns = /*[search_columns*/ ["col1"];
|
|
187
|
+
/*search_columns]*/
|
|
188
|
+
</script>
|
|
189
|
+
</div>
|
|
190
|
+
<!--min_content]-->
|
|
191
|
+
</body>
|
|
192
|
+
</html>
|
df2tables/df2tables.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# coding=utf8
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
TEMPLATE_FILE = "datatable_templ.html"
|
|
12
|
+
try:
|
|
13
|
+
# python 3.9+
|
|
14
|
+
from importlib import resources
|
|
15
|
+
TEMPLATE_PATH = str(resources.files("df2tables").joinpath(TEMPLATE_FILE))
|
|
16
|
+
except ImportError:
|
|
17
|
+
TEMPLATE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), TEMPLATE_FILE)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from .comnt import render as c_render
|
|
21
|
+
except ImportError:
|
|
22
|
+
from comnt import render as c_render
|
|
23
|
+
|
|
24
|
+
__all__ = ["TEMPLATE_PATH", "sample_df", "render"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def open_file(filename):
|
|
28
|
+
if sys.platform.startswith("win"):
|
|
29
|
+
os.startfile(filename)
|
|
30
|
+
else:
|
|
31
|
+
opener = "open" if sys.platform == "darwin" else "xdg-open"
|
|
32
|
+
subprocess.call([opener, filename])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DataJSONEncoder(json.JSONEncoder):
|
|
36
|
+
"""Custom JSON encoder with fallback to string representation"""
|
|
37
|
+
|
|
38
|
+
def default(self, obj):
|
|
39
|
+
try:
|
|
40
|
+
obj_type = str(type(obj))
|
|
41
|
+
if "str" in obj_type:
|
|
42
|
+
return obj.strip()
|
|
43
|
+
elif "date" in obj_type:
|
|
44
|
+
return obj.isoformat()
|
|
45
|
+
elif "int" in obj_type:
|
|
46
|
+
return int(obj)
|
|
47
|
+
elif "float" in obj_type:
|
|
48
|
+
return round(float(obj), 2)
|
|
49
|
+
elif "bool" in obj_type:
|
|
50
|
+
return bool(obj)
|
|
51
|
+
elif isinstance(obj, (np.void)):
|
|
52
|
+
return 0
|
|
53
|
+
return super().default(obj)
|
|
54
|
+
except BaseException:
|
|
55
|
+
print("json error: ", repr(obj), sys.exc_info()[1])
|
|
56
|
+
# Fallback to string representation for any problematic objects
|
|
57
|
+
return repr(obj).replace("<", " ").replace(">", " ")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def render(df,
|
|
61
|
+
title="Title",
|
|
62
|
+
precision=2,
|
|
63
|
+
num_html=[],
|
|
64
|
+
to_file=None,
|
|
65
|
+
startfile=True,
|
|
66
|
+
templ_path=TEMPLATE_PATH):
|
|
67
|
+
|
|
68
|
+
if "MultiIndex" in repr(df.columns): # experimental
|
|
69
|
+
df.columns = ["_".join(x) for x in df.columns]
|
|
70
|
+
|
|
71
|
+
assert isinstance(df, pd.DataFrame)
|
|
72
|
+
|
|
73
|
+
missing_cols = set(num_html).difference(df.columns)
|
|
74
|
+
if missing_cols:
|
|
75
|
+
raise AssertionError(f"column(s): {missing_cols} not found in dataframe")
|
|
76
|
+
|
|
77
|
+
for col in df.columns:
|
|
78
|
+
try:
|
|
79
|
+
test_val = df[col].dropna().iloc[0] if not df[col].isna().all() else None
|
|
80
|
+
json.dumps(test_val, cls=DataJSONEncoder)
|
|
81
|
+
except BaseException:
|
|
82
|
+
print(f"! column error: {col}", sys.exc_info())
|
|
83
|
+
df[col] = df[col].apply(lambda x: repr(x) if not pd.isna(x) else None)
|
|
84
|
+
|
|
85
|
+
float_cols = df.select_dtypes(include=[np.float16, np.float32, np.float64])
|
|
86
|
+
|
|
87
|
+
df.loc[:, float_cols.columns] = np.round(float_cols, precision)
|
|
88
|
+
data_arrays = df.values.tolist()
|
|
89
|
+
data_json = json.dumps(data_arrays, cls=DataJSONEncoder)
|
|
90
|
+
|
|
91
|
+
# Get string column indices
|
|
92
|
+
str_cols = df.select_dtypes(include=["object", "string"]).columns
|
|
93
|
+
str_col_indices = [list(df.columns).index(col) for col in str_cols]
|
|
94
|
+
|
|
95
|
+
# Get column names and create column definitions for DataTable
|
|
96
|
+
columns = []
|
|
97
|
+
for i, col in enumerate(df.columns):
|
|
98
|
+
is_text = i in str_col_indices
|
|
99
|
+
col_def = {"title": col, "searchable": is_text}
|
|
100
|
+
if col in num_html:
|
|
101
|
+
col_def["render"] = "#render_num"
|
|
102
|
+
col_def["type"] = "num-html"
|
|
103
|
+
columns.append(col_def)
|
|
104
|
+
|
|
105
|
+
columns_json = json.dumps(columns)
|
|
106
|
+
if num_html:
|
|
107
|
+
# we need properly refer to javascript function - json can have string so get rid of the quotes
|
|
108
|
+
columns_json = columns_json.replace('"#render_num"', "render_num")
|
|
109
|
+
|
|
110
|
+
search_cols_json = json.dumps(list(str_cols))
|
|
111
|
+
|
|
112
|
+
template_vars = {
|
|
113
|
+
"title": title,
|
|
114
|
+
"tab_data": data_json,
|
|
115
|
+
"tab_columns": columns_json,
|
|
116
|
+
"search_columns": search_cols_json,
|
|
117
|
+
}
|
|
118
|
+
with open(templ_path, encoding="utf-8") as op_file:
|
|
119
|
+
instr = op_file.read()
|
|
120
|
+
html = c_render(instr, template_vars)
|
|
121
|
+
if not to_file:
|
|
122
|
+
return html
|
|
123
|
+
else:
|
|
124
|
+
assert templ_path != to_file and templ_path not in to_file
|
|
125
|
+
with open(to_file, "w", encoding="utf8") as outfile:
|
|
126
|
+
outfile.write(html)
|
|
127
|
+
if startfile:
|
|
128
|
+
open_file(to_file)
|
|
129
|
+
return outfile
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def sample_df():
|
|
133
|
+
import datetime
|
|
134
|
+
|
|
135
|
+
df = pd.DataFrame({
|
|
136
|
+
"col1": [
|
|
137
|
+
datetime.datetime.now(),
|
|
138
|
+
"Lorem ipsum dolor sit amet, consectetur adipiscing",
|
|
139
|
+
"<b>Integer</b> laoreet odio et.",
|
|
140
|
+
np.nan,
|
|
141
|
+
datetime.datetime,
|
|
142
|
+
lambda x: 1 / x,
|
|
143
|
+
"C",
|
|
144
|
+
],
|
|
145
|
+
"col2": [0.09, -0.591, 0.201, -0.487, -0.175, -0.797, -0.519],
|
|
146
|
+
"col3": [2.11, 1, 9, 8, 7, 4, True],
|
|
147
|
+
"col4": [-0.333, 1, -9, 4, 2, 3, 1111.111],
|
|
148
|
+
"col5": [-1000, 1, 2, 3, 4, 5, 70_000],
|
|
149
|
+
"col6": ["a", "B", "c", "D", "e", "F", "X "],
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
outfile = "df_table.html"
|
|
153
|
+
result = render(df,
|
|
154
|
+
to_file=None,
|
|
155
|
+
title="Example dataframe",
|
|
156
|
+
num_html=["col5", "col4", "col2"])
|
|
157
|
+
return result
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
sample_df()
|
df2tables/examples.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
import string
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
|
|
9
|
+
import matplotlib
|
|
10
|
+
import matplotlib.colors as mcolors
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
import df2tables as df2dtb
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def random_data(num_rows=100):
|
|
18
|
+
|
|
19
|
+
def get_rdylgn_colors(num_colors=100):
|
|
20
|
+
cmap = matplotlib.colormaps["YlGnBu"]
|
|
21
|
+
color_indices = np.linspace(0, 1, num_colors)
|
|
22
|
+
colors = []
|
|
23
|
+
for i in color_indices:
|
|
24
|
+
rgba_color = cmap(i)
|
|
25
|
+
hex_color = mcolors.rgb2hex(rgba_color[:3])
|
|
26
|
+
colors.append(
|
|
27
|
+
f"<div style='font-family: monospace;background-color:{hex_color}'>{hex_color}</div>"
|
|
28
|
+
)
|
|
29
|
+
return colors
|
|
30
|
+
|
|
31
|
+
unicode_ranges = [
|
|
32
|
+
(0x0020, 0x007E), # Basic Latin (printable ASCII)
|
|
33
|
+
(0x00A0, 0x00FF), # Latin-1 Supplement (e.g., accented characters)
|
|
34
|
+
(0x0100, 0x017F), # Latin Extended-A (more European characters)
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
def gen_datetime(min_year=1990, max_year=datetime.now().year):
|
|
38
|
+
# generate a datetime in format yyyy-mm-dd hh:mm:ss.000000
|
|
39
|
+
start = datetime(min_year, 1, 1, 00, 00, 00)
|
|
40
|
+
years = max_year - min_year + 1
|
|
41
|
+
end = start + timedelta(days=365 * years)
|
|
42
|
+
return start + (end - start) * random.random()
|
|
43
|
+
|
|
44
|
+
def get_random_unicode_char():
|
|
45
|
+
"""Get a random Unicode character from various language ranges."""
|
|
46
|
+
range_start, range_end = random.choice(unicode_ranges)
|
|
47
|
+
code_point = random.randint(range_start, range_end)
|
|
48
|
+
try:
|
|
49
|
+
return chr(code_point)
|
|
50
|
+
except ValueError:
|
|
51
|
+
return chr(random.randint(0x00C0, 0x00FF))
|
|
52
|
+
|
|
53
|
+
result = []
|
|
54
|
+
# colors = list(reversed(get_rdylgn_colors(num_rows)))
|
|
55
|
+
colors = get_rdylgn_colors(num_rows)
|
|
56
|
+
for i in range(num_rows):
|
|
57
|
+
row = [
|
|
58
|
+
random.choice(string.ascii_letters),
|
|
59
|
+
random.randint(100, 100000),
|
|
60
|
+
random.uniform(-1, 1),
|
|
61
|
+
get_random_unicode_char(),
|
|
62
|
+
random.choice([True, False]),
|
|
63
|
+
str(gen_datetime()),
|
|
64
|
+
colors[i],
|
|
65
|
+
]
|
|
66
|
+
result.append(row)
|
|
67
|
+
columns= [f'col{i}' for i in range(len(result[0]))]
|
|
68
|
+
df = pd.DataFrame(result, columns=columns)
|
|
69
|
+
|
|
70
|
+
outfile = "rnd_table2.html"
|
|
71
|
+
df2dtb.to_html(df, outfile=outfile, title="Example Diverse Random Data", html_cols=["col1", "col2"])
|
|
72
|
+
return result
|
|
73
|
+
|
|
74
|
+
def pkg_test():
|
|
75
|
+
|
|
76
|
+
def get_packages():
|
|
77
|
+
try:
|
|
78
|
+
import pkg_resources
|
|
79
|
+
|
|
80
|
+
dists = [repr(d).split(" ") for d in sorted(pkg_resources.working_set)]
|
|
81
|
+
dists = sorted(dists, key=lambda x: x[0].lower())
|
|
82
|
+
except ModuleNotFoundError:
|
|
83
|
+
print("Error loading module pkg_resources - using random data")
|
|
84
|
+
dists = generate_random_data(num_rows=100)
|
|
85
|
+
# dists.insert(0, ["name1", "name2", "name3", "name4", "name5", "name6"])
|
|
86
|
+
return dists
|
|
87
|
+
|
|
88
|
+
header_list = ["name ", "ver ", "full package path"]
|
|
89
|
+
df = pd.DataFrame(get_packages(), columns=header_list)
|
|
90
|
+
|
|
91
|
+
outfile = "pkg_table.html"
|
|
92
|
+
df2dtb.to_html(df, outfile=outfile)
|
|
93
|
+
|
|
94
|
+
def test_yfinance(ticker='AAPL'):
|
|
95
|
+
import yfinance as yf
|
|
96
|
+
per = "2y"
|
|
97
|
+
df = yf.download(ticker, period=per)
|
|
98
|
+
df['Date'] = df.index.map(lambda x: pd.to_datetime(x).date())
|
|
99
|
+
|
|
100
|
+
outfile = "yfinance.html"
|
|
101
|
+
df2dtb.to_html(
|
|
102
|
+
df,
|
|
103
|
+
outfile=outfile,
|
|
104
|
+
title=f"{ticker}, last {per}",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
# random_data()
|
|
111
|
+
test_yfinance()
|
|
112
|
+
# pkg_test()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: df2tables
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: dftables creation
|
|
5
|
+
Author-email: Tomasz Sługocki <ts.kontakt@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ts-kontakt/df2tables
|
|
8
|
+
Project-URL: Issues, https://github.com/ts-kontakt/df2tables/issues
|
|
9
|
+
License-File: LICENCE.txt
|
|
10
|
+
Requires-Dist: pandas
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
df2tables/__init__.py,sha256=Ry5LO4692ZzOnP1mf7Z8VU4FWXx_h2e6vKwue1jEFGg,74
|
|
2
|
+
df2tables/comnt.py,sha256=MgDN-M3alTBpBPSJcFpcqI514_qr4C5z1Bpmfdz3Nm8,6931
|
|
3
|
+
df2tables/datatable_templ.html,sha256=gu86AYHyg92Y3A84QJlWlEgy8U_0KxfeYONrCghCdPQ,5517
|
|
4
|
+
df2tables/df2tables.py,sha256=QknFOlmDITczJ8K1GeCCmrLenOo_k4YAxEWnENnCgao,5079
|
|
5
|
+
df2tables/examples.py,sha256=JI6QB8Wd98ZOIWOr5Tv050NXR7AErhxPSwX8p7tX4Lg,3456
|
|
6
|
+
df2tables-0.0.1.dist-info/licenses/LICENCE.txt,sha256=ACwmltkrXIz5VsEQcrqljq-fat6ZXAMepjXGoe40KtE,1069
|
|
7
|
+
df2tables-0.0.1.dist-info/METADATA,sha256=gmV4D39857DiidYp_TicAXXKwC09rhDHZq7kABUfja8,380
|
|
8
|
+
df2tables-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
df2tables-0.0.1.dist-info/top_level.txt,sha256=fxT795rSYjPhcxjUIq6Fy7DpEbsMIJDSp7vJuQ0NVMk,10
|
|
10
|
+
df2tables-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [year] [fullname]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
df2tables
|