hh-applicant-tool 0.5.5__py3-none-any.whl → 0.5.6__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.
Potentially problematic release.
This version of hh-applicant-tool might be problematic. Click here for more details.
- hh_applicant_tool/api/client.py +2 -2
- hh_applicant_tool/jsonc.py +138 -0
- hh_applicant_tool/utils.py +2 -4
- {hh_applicant_tool-0.5.5.dist-info → hh_applicant_tool-0.5.6.dist-info}/METADATA +1 -1
- {hh_applicant_tool-0.5.5.dist-info → hh_applicant_tool-0.5.6.dist-info}/RECORD +7 -6
- {hh_applicant_tool-0.5.5.dist-info → hh_applicant_tool-0.5.6.dist-info}/WHEEL +0 -0
- {hh_applicant_tool-0.5.5.dist-info → hh_applicant_tool-0.5.6.dist-info}/entry_points.txt +0 -0
hh_applicant_tool/api/client.py
CHANGED
|
@@ -117,8 +117,8 @@ class BaseClient:
|
|
|
117
117
|
try:
|
|
118
118
|
rv = response.json()
|
|
119
119
|
except json.decoder.JSONDecodeError:
|
|
120
|
-
if response.status_code not in [201, 204]:
|
|
121
|
-
|
|
120
|
+
# if response.status_code not in [201, 204]:
|
|
121
|
+
# raise
|
|
122
122
|
rv = {}
|
|
123
123
|
finally:
|
|
124
124
|
logger.debug(
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import enum
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import ast
|
|
5
|
+
from typing import Any, Iterator
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TokenType(enum.Enum):
|
|
10
|
+
WHITESPACE = r"\s+"
|
|
11
|
+
COMMENT = r"//.*|/\*[\s\S]*?\*/"
|
|
12
|
+
NUMBER = r"-?\d+(?:\.\d+)?"
|
|
13
|
+
STRING = r'"(?:\\"|[^"]+)*"'
|
|
14
|
+
KEYWORD = r"null|true|false"
|
|
15
|
+
OPEN_CURLY = r"\{"
|
|
16
|
+
CLOSE_CURLY = r"\}"
|
|
17
|
+
OPEN_SQUARE = r"\["
|
|
18
|
+
CLOSE_SQUARE = r"\]"
|
|
19
|
+
COLON = r":"
|
|
20
|
+
COMMA = r","
|
|
21
|
+
UNKNOWN = r"."
|
|
22
|
+
EOF = r"$"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class Token:
|
|
27
|
+
token_type: TokenType
|
|
28
|
+
value: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def tokenize(s: str) -> Iterator[Token]:
|
|
32
|
+
token_patterns = "|".join(f"(?P<{t.name}>{t.value})" for t in TokenType)
|
|
33
|
+
regex = re.compile(token_patterns, re.MULTILINE)
|
|
34
|
+
for m in regex.finditer(s):
|
|
35
|
+
assert type(m.lastgroup) is str
|
|
36
|
+
yield Token(TokenType[m.lastgroup], m.group())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class JSONCParser:
|
|
40
|
+
def parse(self, s: str) -> Any:
|
|
41
|
+
self.token_it = filter(
|
|
42
|
+
lambda t: t.token_type not in [TokenType.COMMENT, TokenType.WHITESPACE],
|
|
43
|
+
tokenize(s),
|
|
44
|
+
)
|
|
45
|
+
self.next_token = None
|
|
46
|
+
self.advance()
|
|
47
|
+
result = self.parse_value()
|
|
48
|
+
self.expect(TokenType.EOF)
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
def parse_object(self) -> dict:
|
|
52
|
+
# obj = OrderedDict()
|
|
53
|
+
obj = {}
|
|
54
|
+
|
|
55
|
+
while True:
|
|
56
|
+
self.expect(TokenType.STRING)
|
|
57
|
+
key = ast.literal_eval(self.token.value)
|
|
58
|
+
self.expect(TokenType.COLON)
|
|
59
|
+
value = self.parse_value()
|
|
60
|
+
obj[key] = value
|
|
61
|
+
if not self.match(TokenType.COMMA):
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
self.expect(TokenType.CLOSE_CURLY)
|
|
65
|
+
return obj
|
|
66
|
+
|
|
67
|
+
def parse_array(self) -> list:
|
|
68
|
+
arr = []
|
|
69
|
+
|
|
70
|
+
while True:
|
|
71
|
+
arr.append(self.parse_value())
|
|
72
|
+
if not self.match(TokenType.COMMA):
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
self.expect(TokenType.CLOSE_SQUARE)
|
|
76
|
+
return arr
|
|
77
|
+
|
|
78
|
+
def parse_value(self) -> Any:
|
|
79
|
+
if self.match(TokenType.OPEN_CURLY):
|
|
80
|
+
return self.parse_object()
|
|
81
|
+
elif self.match(TokenType.OPEN_SQUARE):
|
|
82
|
+
return self.parse_array()
|
|
83
|
+
elif self.match(TokenType.STRING):
|
|
84
|
+
return ast.literal_eval(self.token.value)
|
|
85
|
+
elif self.match(TokenType.NUMBER):
|
|
86
|
+
num = self.token.value
|
|
87
|
+
return float(num) if "." in num else int(num)
|
|
88
|
+
elif self.match(TokenType.KEYWORD):
|
|
89
|
+
return {"null": None, "true": True, "false": False}[self.token.value]
|
|
90
|
+
else:
|
|
91
|
+
raise SyntaxError(f"Unexpected token: {self.token.token_type.name}")
|
|
92
|
+
|
|
93
|
+
def advance(self):
|
|
94
|
+
self.token, self.next_token = (
|
|
95
|
+
self.next_token,
|
|
96
|
+
next(self.token_it, Token(TokenType.EOF, "")),
|
|
97
|
+
)
|
|
98
|
+
# print(f"{self.token =}, {self.next_token =}")
|
|
99
|
+
|
|
100
|
+
def match(self, token_type: TokenType) -> bool:
|
|
101
|
+
if self.next_token.token_type == token_type:
|
|
102
|
+
self.advance()
|
|
103
|
+
return True
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
def expect(self, token_type: TokenType):
|
|
107
|
+
if not self.match(token_type):
|
|
108
|
+
raise SyntaxError(
|
|
109
|
+
f"Expected {token_type.name}, got {self.next_token.token_type.name}"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def parse_jsonc(s: str) -> Any:
|
|
114
|
+
return JSONCParser().parse(s)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
json_str = """\
|
|
119
|
+
{
|
|
120
|
+
// Это комментарий
|
|
121
|
+
"name": "John",
|
|
122
|
+
"age": 30,
|
|
123
|
+
"scores": [95.5, 88, null],
|
|
124
|
+
"metadata": {
|
|
125
|
+
"active": true,
|
|
126
|
+
/* Многострочный
|
|
127
|
+
комментарий */
|
|
128
|
+
"tags": [ "foo", "bar" ]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
result = parse_jsonc(json_str)
|
|
134
|
+
import pprint
|
|
135
|
+
|
|
136
|
+
pprint.pprint(result)
|
|
137
|
+
except SyntaxError as e:
|
|
138
|
+
print(f"Syntax error: {e}")
|
hh_applicant_tool/utils.py
CHANGED
|
@@ -14,6 +14,7 @@ from threading import Lock
|
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
16
|
from .constants import INVALID_ISO8601_FORMAT
|
|
17
|
+
from .jsonc import parse_jsonc
|
|
17
18
|
|
|
18
19
|
print_err = partial(print, file=sys.stderr, flush=True)
|
|
19
20
|
|
|
@@ -45,10 +46,7 @@ class Config(dict):
|
|
|
45
46
|
if self._config_path.exists():
|
|
46
47
|
with self._lock:
|
|
47
48
|
with self._config_path.open("r", encoding="utf-8", errors="replace") as f:
|
|
48
|
-
|
|
49
|
-
self.update(json.load(f))
|
|
50
|
-
except ValueError:
|
|
51
|
-
pass
|
|
49
|
+
self.update(json.load(f))
|
|
52
50
|
|
|
53
51
|
def save(self, *args: Any, **kwargs: Any) -> None:
|
|
54
52
|
self.update(*args, **kwargs)
|
|
@@ -3,10 +3,11 @@ hh_applicant_tool/__main__.py,sha256=cwKJAAML0RRKT9Qbzcwf07HHcuSd8oh7kx4P1apndWQ
|
|
|
3
3
|
hh_applicant_tool/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
hh_applicant_tool/ai/blackbox.py,sha256=vqkEpsX7q7bgX49dmmifYJmrbuz_WBg5u9M9J9XdQlI,1670
|
|
5
5
|
hh_applicant_tool/api/__init__.py,sha256=kgFSHibRaAugN2BA3U1djEa20qgKJUUVouwJzjEB0DU,84
|
|
6
|
-
hh_applicant_tool/api/client.py,sha256=
|
|
6
|
+
hh_applicant_tool/api/client.py,sha256=KtSPJoAYWrTrdgWBIF9aipLqWd1khufLmyL6G4rnBGs,8001
|
|
7
7
|
hh_applicant_tool/api/errors.py,sha256=0SoWKnsSUA0K2YgQA8GwGhe-pRMwtfK94MR6_MgbORQ,1722
|
|
8
8
|
hh_applicant_tool/color_log.py,sha256=gN6j1Ayy1G7qOMI_e3WvfYw_ublzeQbKgsVLhqGg_3s,823
|
|
9
9
|
hh_applicant_tool/constants.py,sha256=lpgKkP2chWgTnBXvzxbSPWpKcfzp8VxMTlkssFcQhH4,469
|
|
10
|
+
hh_applicant_tool/jsonc.py,sha256=Vni5ksSgoJBBNApS9EyMzD2jVUxYMFa2q1f8bUPH2OY,3791
|
|
10
11
|
hh_applicant_tool/main.py,sha256=j4kG7H0qxJebGwVs3fjc4udOk7rVvffsB2114UgI9e8,4776
|
|
11
12
|
hh_applicant_tool/mixins.py,sha256=66LmyYSsDfhrpUwoAONjzrd5aoXqaZVoQ-zXhyYbYMk,418
|
|
12
13
|
hh_applicant_tool/operations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -23,8 +24,8 @@ hh_applicant_tool/operations/update_resumes.py,sha256=gGxMYMoT9GqJjwn4AgrOAEJCZu
|
|
|
23
24
|
hh_applicant_tool/operations/whoami.py,sha256=sg0r7m6oKkpMEmGt4QdtYdS-1gf-1KKdnk32yblbRJs,714
|
|
24
25
|
hh_applicant_tool/telemetry_client.py,sha256=wYLbKnx3sOmESFHqjLt-0Gww1O3lJiXFYdWnsorIhK8,3261
|
|
25
26
|
hh_applicant_tool/types.py,sha256=q3yaIcq-UOkPzjxws0OFa4w9fTty-yx79_dic70_dUM,843
|
|
26
|
-
hh_applicant_tool/utils.py,sha256=
|
|
27
|
-
hh_applicant_tool-0.5.
|
|
28
|
-
hh_applicant_tool-0.5.
|
|
29
|
-
hh_applicant_tool-0.5.
|
|
30
|
-
hh_applicant_tool-0.5.
|
|
27
|
+
hh_applicant_tool/utils.py,sha256=3T4A2AykGqTwtGAttmYplIjHwFl3pNAcbWIVuA-OheQ,3080
|
|
28
|
+
hh_applicant_tool-0.5.6.dist-info/METADATA,sha256=IMtOrcHN6AVVH177zmoM41a36R4PPmdR-3vKgzi_OJ4,20166
|
|
29
|
+
hh_applicant_tool-0.5.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
30
|
+
hh_applicant_tool-0.5.6.dist-info/entry_points.txt,sha256=Vb7M2YaYLMtKYJZh8chIrXZApMzSRFT1-rQw-U9r10g,65
|
|
31
|
+
hh_applicant_tool-0.5.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|