Coverage for gws-app/gws/plugin/csv_helper/__init__.py: 0%
82 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
1"""Common csv writer helper."""
3from typing import Optional
5import decimal
6import datetime
8import gws
9import gws.lib.intl
11gws.ext.new.helper('csv')
14class FormatConfig(gws.Config):
15 """CSV format settings"""
17 delimiter: str = ','
18 """Field delimiter."""
19 encoding: str = 'utf8'
20 """Text encoding."""
21 formulaHack: bool = True
22 """Prepend numeric strings with an equals sign."""
23 quote: str = '"'
24 """Quote character."""
25 quoteAll: bool = False
26 """Quote all fields."""
27 rowDelimiter: str = 'LF'
28 """Row delimiter."""
31class Config(gws.Config):
32 """CSV helper."""
34 format: FormatConfig
35 """CSV format settings."""
38class Format(gws.Data):
39 delimiter: str
40 encoding: str
41 formulaHack: bool
42 quote: str
43 quoteAll: bool
44 rowDelimiter: str
47class Object(gws.Node):
48 format: Format
50 def configure(self):
51 self.format = Format(
52 delimiter=self.cfg('format.delimiter', default=','),
53 encoding=self.cfg('format.encoding', default='utf8'),
54 formulaHack=self.cfg('format.formulaHack', default=True),
55 quote=self.cfg('format.quote', default='"'),
56 quoteAll=self.cfg('format.quoteAll', default=False),
57 rowDelimiter=self.cfg('format.rowDelimiter', default='LF').replace('CR', '\r').replace('LF', '\n'),
58 )
60 def writer(self, locale: gws.Locale):
61 """Creates a new csv Writer.
63 Args:
64 locale: Locale to use.
65 """
67 return _Writer(self, locale)
70class _Writer:
71 def __init__(self, helper, locale: gws.Locale):
72 self.helper: Object = helper
73 self.format = self.helper.format
75 self.rows = []
76 self.headers = ''
78 f = gws.lib.intl.formatters(locale)
79 self.dateFormatter = f[0]
80 self.timeFormatter = f[1]
81 self.numberFormatter = f[2]
83 def write_headers(self, headers: list[str]):
84 """Writes headers into the header attribute.
86 Args:
87 headers: Multiple header names.
88 """
90 self.headers = self.format.delimiter.join(self._quote(s) for s in headers)
91 return self
93 def write_row(self, row: list):
94 """Writes entries into rows attribute.
96 Args:
97 row: Row entries.
98 """
99 self.rows.append(self.format.delimiter.join(self._format(v) for v in row))
100 return self
102 def to_str(self):
103 """Converts the headers and rows to a CSV string."""
105 rows = []
106 if self.headers:
107 rows.append(self.headers)
108 rows.extend(self.rows)
109 return self.format.rowDelimiter.join(rows)
111 def to_bytes(self, encoding=None):
112 """Converts the table the writer object describes to a CSV byte string."""
114 return self.to_str().encode(encoding or self.format.encoding, errors='replace')
116 def _format(self, val):
117 if val is None:
118 return self._quote('')
120 if isinstance(val, (float, decimal.Decimal)):
121 s = self.numberFormatter.decimal(val)
122 return self._quote(s) if self.format.quoteAll else s
124 if isinstance(val, int):
125 s = str(val)
126 return self._quote(s) if self.format.quoteAll else s
128 if isinstance(val, (datetime.datetime, datetime.date)):
129 s = self.dateFormatter.short(val)
130 return self._quote(s)
132 if isinstance(val, datetime.time):
133 s = self.timeFormatter.short(val)
134 return self._quote(s)
136 val = gws.u.to_str(val)
138 if val and val.isdigit() and self.format.formulaHack:
139 val = '=' + self._quote(val)
141 return self._quote(val)
143 def _quote(self, val):
144 q = self.format.quote
145 s = gws.u.to_str(val).replace(q, q + q)
146 return q + s + q