Passed
Push — main ( 50e464...1bea4f )
by Sat CFDI
01:49
created

satdigitalinvoice.file_data_managers.finalize_html()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
import logging
2
import os
3
from decimal import Decimal
4
from html import escape as html_escape
5
import collections.abc
6
import jinja2
7
import yaml
8
from jinja2 import Environment
9
from jinja2.filters import do_mark_safe
10
import jsonschema as jsonschema
11
from jsonschema.validators import Draft202012Validator
12
from satcfdi import Code
13
from satcfdi.pacs import sat
14
# noinspection PyUnresolvedReferences
15
from satcfdi.transform.catalog import CATALOGS
16
from satcfdi.transform.helpers import Xint
17
from satdigitalinvoice import SOURCE_DIRECTORY
18
from yaml import MappingNode, SafeLoader
19
from yaml.constructor import ConstructorError
20
21
REGIMEN_FISCAL = CATALOGS['{http://www.sat.gob.mx/sitio_internet/cfd/catalogos}c_RegimenFiscal']
22
23
logger = logging.getLogger(__name__)
24
sat_manager = sat.SAT()
25
26
environment_default = Environment(
27
    loader=jinja2.FileSystemLoader(searchpath=['templates']),
28
    autoescape=False,
29
    trim_blocks=True,
30
    lstrip_blocks=True,
31
    undefined=jinja2.StrictUndefined,
32
)
33
34
35
def finalize_html(val):
36
    return do_mark_safe(
37
        tag(html_escape(val), "b")
38
    )
39
40
41
environment_bold_escaped = Environment(
42
    loader=jinja2.FileSystemLoader(searchpath=['templates']),
43
    autoescape=True,
44
    trim_blocks=True,
45
    lstrip_blocks=True,
46
    undefined=jinja2.StrictUndefined,
47
    finalize=finalize_html
48
)
49
50
51
class DuplicateKeySafeLoader(yaml.SafeLoader):
52
    def construct_mapping(self, node, deep=False):
53
        if isinstance(node, MappingNode):
54
            self.flatten_mapping(node)
55
56
        if not isinstance(node, MappingNode):
57
            raise ConstructorError(None, None,
58
                                   "expected a mapping node, but found %s" % node.id,
59
                                   node.start_mark)
60
        mapping = {}
61
        for key_node, value_node in node.value:
62
            key = self.construct_object(key_node, deep=deep)
63
            if not isinstance(key, collections.abc.Hashable):
64
                raise ConstructorError("while constructing a mapping", node.start_mark,
65
                                       "found unhashable key", key_node.start_mark)
66
            value = self.construct_object(value_node, deep=deep)
67
            if key in mapping:
68
                raise ConstructorError("while constructing a mapping", node.start_mark,
69
                                       "found duplicate key (%s)" % key, key_node.start_mark)
70
            mapping[key] = value
71
        return mapping
72
73
74
class LocalData(dict):
75
    file_source = None
76
77
    def __new__(cls, *args, **kwargs):
78
        return super().__new__(cls)
79
80
    def __init__(self):
81
        super().__init__(self._raw())
82
83
    def reload(self):
84
        super().update(self._raw())
85
86
    def _raw(self):
87
        with open(self.file_source, "r", encoding="utf-8") as fs:
88
            return yaml.load(fs, DuplicateKeySafeLoader)
89
90
91
class ConfigManager(LocalData):
92
    file_source = "config.yaml"
93
94
    def folio(self):
95
        return self["folio"]
96
97
    def serie(self):
98
        return self["serie"]
99
100
    def inc_folio(self):
101
        self["folio"] += 1
102
        self.save()
103
104
    def save(self):
105
        with open(self.file_source, "w", encoding="utf-8") as fs:
106
            yaml.dump_all([self], fs, Dumper=yaml.SafeDumper, encoding="utf-8", allow_unicode=True, sort_keys=False)
107
108
109
def load_validator(schema_file):
110
    with open(os.path.join(SOURCE_DIRECTORY, 'schemas', schema_file), "r", encoding="utf-8") as fs:
111
        return Draft202012Validator(yaml.load(fs, SafeLoader))
112
113
114
client_validator = load_validator("client.yaml")
115
factura_validator = load_validator("factura.yaml")
116
117
118
class ClientsManager(LocalData):
119
    file_source = "clients.yaml"
120
121
    def __init__(self):
122
        super().__init__()
123
124
        for k, v in self.items():
125
            if error := jsonschema.exceptions.best_match(client_validator.iter_errors(v)):
126
                raise error
127
            self[k]["Rfc"] = k
128
            self[k]["RegimenFiscal"] = Code(self[k]["RegimenFiscal"], REGIMEN_FISCAL.get(self[k]["RegimenFiscal"]))
129
130
131
class FacturasManager(LocalData):
132
    file_source = "facturas.yaml"
133
134
    def __init__(self):
135
        super().__init__()
136
137
        for v in self["Facturas"]:
138
            if error := jsonschema.exceptions.best_match(factura_validator.iter_errors(v)):
139
                raise error
140
141
142
def tag(text, tag):
143
    return '<' + tag + '>' + text + '</' + tag + '>'
144
145
146
yaml.SafeDumper.add_multi_representer(dict, lambda dumper, data: dumper.represent_dict(data))
147
yaml.SafeLoader.add_constructor("!decimal", lambda loader, node: Decimal(loader.construct_scalar(node)))
148
149
150
def represent_decimal(dumper, data):
151
    return dumper.represent_scalar('tag:yaml.org,2002:str', str(data))
152
153
154
def represent_str(dumper, data):
155
    return dumper.represent_scalar('tag:yaml.org,2002:str', str(data))
156
157
158
yaml.SafeDumper.add_representer(Decimal, represent_decimal)
159
yaml.SafeDumper.add_representer(Code, represent_str)
160
yaml.SafeDumper.add_representer(Xint, represent_str)
161