atsfill 0.0.5.dev189__tar.gz → 0.0.5.dev192__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.
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/PKG-INFO +1 -1
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/pyproject.toml +1 -1
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/pyproject.toml.orig +1 -1
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/lever.py +11 -3
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/phenom.py +20 -10
- atsfill-0.0.5.dev192/src/atsfill/core/countries.py +260 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/matching.py +11 -3
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/LICENSE +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/README.md +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/__init__.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/__init__.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/adp.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/adpmyjobs.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/ashby.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/bamboohr.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/breezy.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/dayforce.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/eightfold.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/greenhouse.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/gupy.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/hireology.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/hiringthing.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/hrmdirect.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/isolvedhire.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/jazzhr.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/jobaffinity.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/jobstreet.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/join.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/manatal.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/oracle.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/paycor.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/paylocity.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/personio.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/pinpoint.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/recruitee.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/recruiterflow.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/rippling.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/smartrecruiters.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/successfactors.js +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/successfactors.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/taleo.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/teamtailor.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/ukg.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/welcomekit.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/workable.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/workatastartup.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/workday.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/wttj.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/adapters/zohorecruit.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/cli.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/__init__.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/accounts.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/adapter.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/attempts.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/browser.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/commands.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/conditions.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/html.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/otp.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/passwords.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/paths.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/registry.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/results.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/snapshot.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/core/telemetry.py +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/py.typed +0 -0
- {atsfill-0.0.5.dev189 → atsfill-0.0.5.dev192}/src/atsfill/registry.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atsfill
|
|
3
|
-
Version: 0.0.5.
|
|
3
|
+
Version: 0.0.5.dev192
|
|
4
4
|
Summary: Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle.
|
|
5
5
|
Keywords: job-applications,ats,ashby,greenhouse,teamtailor,ai-agents,cli
|
|
6
6
|
Author: shbernal
|
|
@@ -88,7 +88,7 @@ _READ = """form => {
|
|
|
88
88
|
}"""
|
|
89
89
|
|
|
90
90
|
# What each field shows: a choice its checked option's text, a select its option's text, a file
|
|
91
|
-
# its attached name.
|
|
91
|
+
# its attached name, the location its pick.
|
|
92
92
|
_READ_BACK = """(form, kinds) => {
|
|
93
93
|
const optionText = input => (input.closest("label")?.querySelector(
|
|
94
94
|
".application-answer-alternative")?.textContent || input.value).replace(/\\s+/g, " ").trim();
|
|
@@ -110,7 +110,10 @@ _READ_BACK = """(form, kinds) => {
|
|
|
110
110
|
} else if (kind === "checkboxes") {
|
|
111
111
|
out[name] = all.filter(e => e.type === "checkbox" && e.checked).map(optionText);
|
|
112
112
|
} else if (kind === "consent") out[name] = el.checked ? "Yes" : "No";
|
|
113
|
-
else
|
|
113
|
+
else if (kind === "location") {
|
|
114
|
+
const picked = form.querySelector("[name=selectedLocation]")?.value;
|
|
115
|
+
out[name] = picked ? JSON.parse(picked).name : null;
|
|
116
|
+
} else out[name] = el.value;
|
|
114
117
|
}
|
|
115
118
|
return out;
|
|
116
119
|
}"""
|
|
@@ -159,7 +162,7 @@ class Lever:
|
|
|
159
162
|
# the resume is parsed into the form, so it goes first and is waited for
|
|
160
163
|
for name, answer in sorted(answers.items(), key=lambda a: a[0] != "resume"):
|
|
161
164
|
values = [answer] if isinstance(answer, str) else answer
|
|
162
|
-
controls = page.locator(f"{FORM} [name=
|
|
165
|
+
controls = page.locator(f"{FORM} [name={_quoted(name)}]:not([type=hidden])")
|
|
163
166
|
match self._kinds[name]:
|
|
164
167
|
case "file":
|
|
165
168
|
_attach(controls.first, values[0], resume=name == "resume")
|
|
@@ -224,6 +227,11 @@ def _field(raw: dict[str, Any]) -> Field:
|
|
|
224
227
|
)
|
|
225
228
|
|
|
226
229
|
|
|
230
|
+
def _quoted(name: str) -> str:
|
|
231
|
+
"""A control name as a CSS string: an employer names its links, quotes and all."""
|
|
232
|
+
return '"' + name.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
233
|
+
|
|
234
|
+
|
|
227
235
|
def _option(controls: Locator, value: str) -> Locator | None:
|
|
228
236
|
"""The radio or checkbox whose text is `value`."""
|
|
229
237
|
for control in controls.all():
|
|
@@ -25,7 +25,7 @@ from typing import Any
|
|
|
25
25
|
from urllib.parse import parse_qs, urlsplit
|
|
26
26
|
|
|
27
27
|
from atsfill.core import browser, matching, paths
|
|
28
|
-
from atsfill.core.adapter import Gap, NotTaken, Unsupported, known_gap
|
|
28
|
+
from atsfill.core.adapter import Blocked, Gap, NotTaken, Unsupported, known_gap
|
|
29
29
|
from atsfill.core.browser import HTTP_TIMEOUT_S, BrowserError, Locator, Overlaid, Page, Timeout
|
|
30
30
|
from atsfill.core.results import (
|
|
31
31
|
AllOf,
|
|
@@ -238,22 +238,24 @@ _FETCH = """async url => {
|
|
|
238
238
|
return response.json();
|
|
239
239
|
}"""
|
|
240
240
|
|
|
241
|
-
# Posts each body to `/applySubmit`, `most` at a time; null for one that fails.
|
|
241
|
+
# Posts each body to `/applySubmit`, `most` at a time; null for one that fails. With the
|
|
242
|
+
# answers, whether one was refused (403), after which none is posted.
|
|
242
243
|
_POSTS = """async ([bodies, token, most]) => {
|
|
243
244
|
const out = bodies.map(() => null);
|
|
244
|
-
let next = 0;
|
|
245
|
+
let next = 0, refused = false;
|
|
245
246
|
const post = async () => {
|
|
246
|
-
for (let i = next++; i < bodies.length; i = next++) {
|
|
247
|
+
for (let i = next++; i < bodies.length && !refused; i = next++) {
|
|
247
248
|
try {
|
|
248
249
|
const response = await fetch('/applySubmit', {method: 'POST',
|
|
249
250
|
body: JSON.stringify(bodies[i]),
|
|
250
251
|
headers: {'Content-Type': 'application/json', 'x-csrf-token': token}});
|
|
251
252
|
if (response.ok) out[i] = await response.json();
|
|
253
|
+
refused ||= response.status === 403;
|
|
252
254
|
} catch {}
|
|
253
255
|
}
|
|
254
256
|
};
|
|
255
257
|
await Promise.all(Array.from({length: most}, post));
|
|
256
|
-
return out;
|
|
258
|
+
return [out, refused];
|
|
257
259
|
}"""
|
|
258
260
|
|
|
259
261
|
|
|
@@ -350,14 +352,22 @@ class Phenom:
|
|
|
350
352
|
except ValueError as e:
|
|
351
353
|
raise RuntimeError(str(e)) from None
|
|
352
354
|
token = get(ph, "sessionParams.csrfToken") or ""
|
|
355
|
+
|
|
356
|
+
refused = False
|
|
357
|
+
|
|
358
|
+
def posts(bodies: list[dict[str, Any]]) -> list[Any]:
|
|
359
|
+
nonlocal refused
|
|
360
|
+
out, now = page.evaluate(_POSTS, [bodies, token, LOADERS_AT_ONCE])
|
|
361
|
+
refused |= now
|
|
362
|
+
return out
|
|
363
|
+
|
|
353
364
|
try:
|
|
354
|
-
fields, raws = _read(
|
|
355
|
-
ph,
|
|
356
|
-
lambda url: page.evaluate(_FETCH, url),
|
|
357
|
-
lambda bodies: page.evaluate(_POSTS, [bodies, token, LOADERS_AT_ONCE]),
|
|
358
|
-
)
|
|
365
|
+
fields, raws = _read(ph, lambda url: page.evaluate(_FETCH, url), posts)
|
|
359
366
|
except (BrowserError, ValueError, KeyError) as e:
|
|
360
367
|
raise RuntimeError(f"no configuration for the wizard ({e})") from None
|
|
368
|
+
finally: # raised once the read ends, so the answers that came are kept
|
|
369
|
+
if refused:
|
|
370
|
+
raise Blocked("rate_limited: the careers site answers /applySubmit 403")
|
|
361
371
|
self._raws = {raw["id"]: raw for raw in raws}
|
|
362
372
|
return fields
|
|
363
373
|
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Countries by their ISO 3166-1 alpha-3 code and the English names an answer gives them: some
|
|
2
|
+
typeaheads end a place with its country's code, `Madrid, Comunidad de Madrid, ESP`."""
|
|
3
|
+
|
|
4
|
+
_TABLE = """
|
|
5
|
+
ABW Aruba
|
|
6
|
+
AFG Afghanistan
|
|
7
|
+
AGO Angola
|
|
8
|
+
AIA Anguilla
|
|
9
|
+
ALA Åland Islands
|
|
10
|
+
ALB Albania
|
|
11
|
+
AND Andorra
|
|
12
|
+
ARE United Arab Emirates
|
|
13
|
+
ARG Argentina
|
|
14
|
+
ARM Armenia
|
|
15
|
+
ASM American Samoa
|
|
16
|
+
ATA Antarctica
|
|
17
|
+
ATF French Southern Territories
|
|
18
|
+
ATG Antigua and Barbuda
|
|
19
|
+
AUS Australia
|
|
20
|
+
AUT Austria
|
|
21
|
+
AZE Azerbaijan
|
|
22
|
+
BDI Burundi
|
|
23
|
+
BEL Belgium
|
|
24
|
+
BEN Benin
|
|
25
|
+
BES Bonaire
|
|
26
|
+
BFA Burkina Faso
|
|
27
|
+
BGD Bangladesh
|
|
28
|
+
BGR Bulgaria
|
|
29
|
+
BHR Bahrain
|
|
30
|
+
BHS Bahamas
|
|
31
|
+
BIH Bosnia and Herzegovina
|
|
32
|
+
BLM Saint Barthélemy
|
|
33
|
+
BLR Belarus
|
|
34
|
+
BLZ Belize
|
|
35
|
+
BMU Bermuda
|
|
36
|
+
BOL Bolivia
|
|
37
|
+
BRA Brazil
|
|
38
|
+
BRB Barbados
|
|
39
|
+
BRN Brunei Darussalam | Brunei
|
|
40
|
+
BTN Bhutan
|
|
41
|
+
BVT Bouvet Island
|
|
42
|
+
BWA Botswana
|
|
43
|
+
CAF Central African Republic
|
|
44
|
+
CAN Canada
|
|
45
|
+
CCK Cocos (Keeling) Islands
|
|
46
|
+
CHE Switzerland
|
|
47
|
+
CHL Chile
|
|
48
|
+
CHN China
|
|
49
|
+
CIV Côte d'Ivoire
|
|
50
|
+
CMR Cameroon
|
|
51
|
+
COD Democratic Republic of the Congo | DR Congo
|
|
52
|
+
COG Congo
|
|
53
|
+
COK Cook Islands
|
|
54
|
+
COL Colombia
|
|
55
|
+
COM Comoros
|
|
56
|
+
CPV Cabo Verde
|
|
57
|
+
CRI Costa Rica
|
|
58
|
+
CUB Cuba
|
|
59
|
+
CUW Curaçao
|
|
60
|
+
CXR Christmas Island
|
|
61
|
+
CYM Cayman Islands
|
|
62
|
+
CYP Cyprus
|
|
63
|
+
CZE Czechia | Czech Republic
|
|
64
|
+
DEU Germany
|
|
65
|
+
DJI Djibouti
|
|
66
|
+
DMA Dominica
|
|
67
|
+
DNK Denmark
|
|
68
|
+
DOM Dominican Republic
|
|
69
|
+
DZA Algeria
|
|
70
|
+
ECU Ecuador
|
|
71
|
+
EGY Egypt
|
|
72
|
+
ERI Eritrea
|
|
73
|
+
ESH Western Sahara
|
|
74
|
+
ESP Spain
|
|
75
|
+
EST Estonia
|
|
76
|
+
ETH Ethiopia
|
|
77
|
+
FIN Finland
|
|
78
|
+
FJI Fiji
|
|
79
|
+
FLK Falkland Islands (Malvinas) | Falkland Islands
|
|
80
|
+
FRA France
|
|
81
|
+
FRO Faroe Islands
|
|
82
|
+
FSM Micronesia
|
|
83
|
+
GAB Gabon
|
|
84
|
+
GBR United Kingdom | UK | Great Britain
|
|
85
|
+
GEO Georgia
|
|
86
|
+
GGY Guernsey
|
|
87
|
+
GHA Ghana
|
|
88
|
+
GIB Gibraltar
|
|
89
|
+
GIN Guinea
|
|
90
|
+
GLP Guadeloupe
|
|
91
|
+
GMB Gambia
|
|
92
|
+
GNB Guinea-Bissau
|
|
93
|
+
GNQ Equatorial Guinea
|
|
94
|
+
GRC Greece
|
|
95
|
+
GRD Grenada
|
|
96
|
+
GRL Greenland
|
|
97
|
+
GTM Guatemala
|
|
98
|
+
GUF French Guiana
|
|
99
|
+
GUM Guam
|
|
100
|
+
GUY Guyana
|
|
101
|
+
HKG Hong Kong
|
|
102
|
+
HMD Heard Island and McDonald Islands
|
|
103
|
+
HND Honduras
|
|
104
|
+
HRV Croatia
|
|
105
|
+
HTI Haiti
|
|
106
|
+
HUN Hungary
|
|
107
|
+
IDN Indonesia
|
|
108
|
+
IMN Isle of Man
|
|
109
|
+
IND India
|
|
110
|
+
IOT British Indian Ocean Territory
|
|
111
|
+
IRL Ireland
|
|
112
|
+
IRN Iran
|
|
113
|
+
IRQ Iraq
|
|
114
|
+
ISL Iceland
|
|
115
|
+
ISR Israel
|
|
116
|
+
ITA Italy
|
|
117
|
+
JAM Jamaica
|
|
118
|
+
JEY Jersey
|
|
119
|
+
JOR Jordan
|
|
120
|
+
JPN Japan
|
|
121
|
+
KAZ Kazakhstan
|
|
122
|
+
KEN Kenya
|
|
123
|
+
KGZ Kyrgyzstan
|
|
124
|
+
KHM Cambodia
|
|
125
|
+
KIR Kiribati
|
|
126
|
+
KNA Saint Kitts and Nevis
|
|
127
|
+
KOR South Korea | Korea
|
|
128
|
+
KWT Kuwait
|
|
129
|
+
LAO Lao People's Democratic Republic | Laos
|
|
130
|
+
LBN Lebanon
|
|
131
|
+
LBR Liberia
|
|
132
|
+
LBY Libya
|
|
133
|
+
LCA Saint Lucia
|
|
134
|
+
LIE Liechtenstein
|
|
135
|
+
LKA Sri Lanka
|
|
136
|
+
LSO Lesotho
|
|
137
|
+
LTU Lithuania
|
|
138
|
+
LUX Luxembourg
|
|
139
|
+
LVA Latvia
|
|
140
|
+
MAC Macao
|
|
141
|
+
MAF Saint Martin (French part)
|
|
142
|
+
MAR Morocco
|
|
143
|
+
MCO Monaco
|
|
144
|
+
MDA Moldova
|
|
145
|
+
MDG Madagascar
|
|
146
|
+
MDV Maldives
|
|
147
|
+
MEX Mexico
|
|
148
|
+
MHL Marshall Islands
|
|
149
|
+
MKD North Macedonia
|
|
150
|
+
MLI Mali
|
|
151
|
+
MLT Malta
|
|
152
|
+
MMR Myanmar
|
|
153
|
+
MNE Montenegro
|
|
154
|
+
MNG Mongolia
|
|
155
|
+
MNP Northern Mariana Islands
|
|
156
|
+
MOZ Mozambique
|
|
157
|
+
MRT Mauritania
|
|
158
|
+
MSR Montserrat
|
|
159
|
+
MTQ Martinique
|
|
160
|
+
MUS Mauritius
|
|
161
|
+
MWI Malawi
|
|
162
|
+
MYS Malaysia
|
|
163
|
+
MYT Mayotte
|
|
164
|
+
NAM Namibia
|
|
165
|
+
NCL New Caledonia
|
|
166
|
+
NER Niger
|
|
167
|
+
NFK Norfolk Island
|
|
168
|
+
NGA Nigeria
|
|
169
|
+
NIC Nicaragua
|
|
170
|
+
NIU Niue
|
|
171
|
+
NLD Netherlands
|
|
172
|
+
NOR Norway
|
|
173
|
+
NPL Nepal
|
|
174
|
+
NRU Nauru
|
|
175
|
+
NZL New Zealand
|
|
176
|
+
OMN Oman
|
|
177
|
+
PAK Pakistan
|
|
178
|
+
PAN Panama
|
|
179
|
+
PCN Pitcairn
|
|
180
|
+
PER Peru
|
|
181
|
+
PHL Philippines
|
|
182
|
+
PLW Palau
|
|
183
|
+
PNG Papua New Guinea
|
|
184
|
+
POL Poland
|
|
185
|
+
PRI Puerto Rico
|
|
186
|
+
PRK North Korea
|
|
187
|
+
PRT Portugal
|
|
188
|
+
PRY Paraguay
|
|
189
|
+
PSE Palestine
|
|
190
|
+
PYF French Polynesia
|
|
191
|
+
QAT Qatar
|
|
192
|
+
REU Réunion
|
|
193
|
+
ROU Romania
|
|
194
|
+
RUS Russian Federation | Russia
|
|
195
|
+
RWA Rwanda
|
|
196
|
+
SAU Saudi Arabia
|
|
197
|
+
SDN Sudan
|
|
198
|
+
SEN Senegal
|
|
199
|
+
SGP Singapore
|
|
200
|
+
SGS South Georgia and the South Sandwich Islands
|
|
201
|
+
SHN Saint Helena
|
|
202
|
+
SJM Svalbard and Jan Mayen
|
|
203
|
+
SLB Solomon Islands
|
|
204
|
+
SLE Sierra Leone
|
|
205
|
+
SLV El Salvador
|
|
206
|
+
SMR San Marino
|
|
207
|
+
SOM Somalia
|
|
208
|
+
SPM Saint Pierre and Miquelon
|
|
209
|
+
SRB Serbia
|
|
210
|
+
SSD South Sudan
|
|
211
|
+
STP Sao Tome and Principe
|
|
212
|
+
SUR Suriname
|
|
213
|
+
SVK Slovakia
|
|
214
|
+
SVN Slovenia
|
|
215
|
+
SWE Sweden
|
|
216
|
+
SWZ Eswatini
|
|
217
|
+
SXM Sint Maarten (Dutch part)
|
|
218
|
+
SYC Seychelles
|
|
219
|
+
SYR Syrian Arab Republic | Syria
|
|
220
|
+
TCA Turks and Caicos Islands
|
|
221
|
+
TCD Chad
|
|
222
|
+
TGO Togo
|
|
223
|
+
THA Thailand
|
|
224
|
+
TJK Tajikistan
|
|
225
|
+
TKL Tokelau
|
|
226
|
+
TKM Turkmenistan
|
|
227
|
+
TLS Timor-Leste
|
|
228
|
+
TON Tonga
|
|
229
|
+
TTO Trinidad and Tobago
|
|
230
|
+
TUN Tunisia
|
|
231
|
+
TUR Türkiye | Turkey
|
|
232
|
+
TUV Tuvalu
|
|
233
|
+
TWN Taiwan
|
|
234
|
+
TZA Tanzania
|
|
235
|
+
UGA Uganda
|
|
236
|
+
UKR Ukraine
|
|
237
|
+
UMI United States Minor Outlying Islands
|
|
238
|
+
URY Uruguay
|
|
239
|
+
USA United States | US
|
|
240
|
+
UZB Uzbekistan
|
|
241
|
+
VAT Holy See (Vatican City State) | Vatican City
|
|
242
|
+
VCT Saint Vincent and the Grenadines
|
|
243
|
+
VEN Venezuela
|
|
244
|
+
VGB British Virgin Islands
|
|
245
|
+
VIR U.S. Virgin Islands
|
|
246
|
+
VNM Viet Nam | Vietnam
|
|
247
|
+
VUT Vanuatu
|
|
248
|
+
WLF Wallis and Futuna
|
|
249
|
+
WSM Samoa
|
|
250
|
+
YEM Yemen
|
|
251
|
+
ZAF South Africa
|
|
252
|
+
ZMB Zambia
|
|
253
|
+
ZWE Zimbabwe
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
ALPHA_3 = {
|
|
257
|
+
name.strip().casefold(): row[:3]
|
|
258
|
+
for row in _TABLE.strip().splitlines()
|
|
259
|
+
for name in row[4:].split("|")
|
|
260
|
+
}
|
|
@@ -6,6 +6,7 @@ from pathlib import Path, PurePath
|
|
|
6
6
|
from urllib.parse import urlsplit
|
|
7
7
|
|
|
8
8
|
from atsfill.core.conditions import Form, describe
|
|
9
|
+
from atsfill.core.countries import ALPHA_3
|
|
9
10
|
from atsfill.core.results import Answer, Check, Field, FieldType
|
|
10
11
|
|
|
11
12
|
|
|
@@ -25,14 +26,21 @@ def _in_options(field: Field, values: list[str], form: Form | None) -> bool:
|
|
|
25
26
|
return options is None or all(v in options for v in values)
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
def _place(part: str) -> str:
|
|
30
|
+
"""A place's part, a country's name as its alpha-3 code, which some suggestions show."""
|
|
31
|
+
part = part.strip()
|
|
32
|
+
return ALPHA_3.get(part, part).casefold()
|
|
33
|
+
|
|
34
|
+
|
|
28
35
|
def _picked_from(answer: str, suggestion: str) -> bool:
|
|
29
36
|
"""The suggestion starts with the answer, or holds its comma-separated parts in order:
|
|
30
|
-
"Paris" and "Paris, France" both pick "Paris, Île-de-France, France"
|
|
37
|
+
"Paris" and "Paris, France" both pick "Paris, Île-de-France, France" and
|
|
38
|
+
"Paris, Île-de-France, FRA"."""
|
|
31
39
|
answer, suggestion = _norm(answer).casefold(), _norm(suggestion).casefold()
|
|
32
40
|
if suggestion.startswith(answer):
|
|
33
41
|
return True
|
|
34
|
-
parts = iter(p
|
|
35
|
-
return all(any(p == wanted
|
|
42
|
+
parts = iter(_place(p) for p in suggestion.split(","))
|
|
43
|
+
return all(any(p == _place(wanted) for p in parts) for wanted in answer.split(","))
|
|
36
44
|
|
|
37
45
|
|
|
38
46
|
def _absolute_url(value: str) -> bool:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|