atsfill 0.0.4.dev91__tar.gz → 0.0.4.dev92__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.4.dev91 → atsfill-0.0.4.dev92}/PKG-INFO +1 -1
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/pyproject.toml +1 -1
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/pyproject.toml.orig +1 -1
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/ashby.py +2 -2
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/oracle.py +4 -3
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/pinpoint.py +3 -1
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/core/matching.py +14 -2
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/core/results.py +1 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/LICENSE +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/README.md +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/__init__.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/__init__.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/bamboohr.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/breezy.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/dayforce.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/eightfold.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/greenhouse.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/hireology.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/hiringthing.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/jazzhr.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/lever.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/manatal.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/paycor.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/paylocity.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/personio.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/phenom.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/recruitee.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/rippling.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/smartrecruiters.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/teamtailor.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/workable.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/workday.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/wttj.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/adapters/zohorecruit.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/cli.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/core/__init__.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/core/accounts.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/core/adapter.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/core/attempts.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/core/browser.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/core/commands.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/core/passwords.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/core/paths.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/core/registry.py +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/py.typed +0 -0
- {atsfill-0.0.4.dev91 → atsfill-0.0.4.dev92}/src/atsfill/registry.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atsfill
|
|
3
|
-
Version: 0.0.4.
|
|
3
|
+
Version: 0.0.4.dev92
|
|
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
|
|
@@ -34,7 +34,7 @@ TYPES = {
|
|
|
34
34
|
"String": FieldType.TEXT,
|
|
35
35
|
"Email": FieldType.TEXT,
|
|
36
36
|
"LongText": FieldType.TEXT,
|
|
37
|
-
"Url": FieldType.
|
|
37
|
+
"Url": FieldType.URL,
|
|
38
38
|
"Date": FieldType.DATE,
|
|
39
39
|
"Number": FieldType.TEXT,
|
|
40
40
|
"Phone": FieldType.PHONE,
|
|
@@ -146,7 +146,7 @@ class Ashby:
|
|
|
146
146
|
entry = page.locator(f'[data-field-path="{field_id}"]')
|
|
147
147
|
entry.scroll_into_view_if_needed()
|
|
148
148
|
match fields[field_id].type, answer:
|
|
149
|
-
case FieldType.TEXT | FieldType.DATE | FieldType.PHONE, str():
|
|
149
|
+
case FieldType.TEXT | FieldType.URL | FieldType.DATE | FieldType.PHONE, str():
|
|
150
150
|
box = entry.locator("input, textarea").first
|
|
151
151
|
box.fill(answer)
|
|
152
152
|
if fields[field_id].type is FieldType.DATE:
|
|
@@ -46,6 +46,7 @@ UPLOAD_TIMEOUT_MS = 30_000
|
|
|
46
46
|
CHECKBOX = ["Yes", "No"]
|
|
47
47
|
KINDS = {
|
|
48
48
|
"text": FieldType.TEXT,
|
|
49
|
+
"url": FieldType.URL,
|
|
49
50
|
"phone": FieldType.PHONE,
|
|
50
51
|
"date": FieldType.DATE,
|
|
51
52
|
"pills": FieldType.CHOICE,
|
|
@@ -136,7 +137,7 @@ _READ = """() => {
|
|
|
136
137
|
field.kind = "checks";
|
|
137
138
|
field.options = pills(".apply-flow-input-checkbox__label");
|
|
138
139
|
} else if (input?.matches("textarea, input.input-row__control") && !input.disabled) {
|
|
139
|
-
field.kind =
|
|
140
|
+
field.kind = ["email", "url"].includes(input.type) ? input.type : "text";
|
|
140
141
|
field.value = input.value;
|
|
141
142
|
field.maxLength = input.maxLength > 0 ? input.maxLength : null;
|
|
142
143
|
} else if (input?.disabled || !row.querySelector(CONTROL)) {
|
|
@@ -212,7 +213,7 @@ _READ_BACK = """(ids) => {
|
|
|
212
213
|
const picked = [...row.querySelectorAll("input:checked")]
|
|
213
214
|
.map(i => text(row.querySelector(`label[for="${i.id}"]`)));
|
|
214
215
|
out[id] = kind === "radios" ? picked[0] ?? null : picked;
|
|
215
|
-
} else if (kind === "text" || kind === "email") {
|
|
216
|
+
} else if (kind === "text" || kind === "email" || kind === "url") {
|
|
216
217
|
out[id] = input.value;
|
|
217
218
|
}
|
|
218
219
|
}
|
|
@@ -431,7 +432,7 @@ class Oracle:
|
|
|
431
432
|
return
|
|
432
433
|
row = self._row(page, field_id)
|
|
433
434
|
match kind, answer:
|
|
434
|
-
case "text", str():
|
|
435
|
+
case "text" | "url", str():
|
|
435
436
|
box = row.locator("input, textarea").first
|
|
436
437
|
box.fill(answer)
|
|
437
438
|
if box.get_attribute("role") == "combobox": # an address search, left unpicked
|
|
@@ -40,7 +40,7 @@ SUBMIT_TIMEOUT_MS = 90_000
|
|
|
40
40
|
YES_NO = ["Yes", "No"]
|
|
41
41
|
US = "United States" # the country whose address asks for a city and a state
|
|
42
42
|
JOB_PREFERENCES = "job_preferences"
|
|
43
|
-
TEXT_QUESTIONS = {"Shorttext", "Longtext", "Url", "Numberinput"}
|
|
43
|
+
TEXT_QUESTIONS = {"Shorttext", "Longtext", "Url", "Numberinput"} # Url is a `url` field
|
|
44
44
|
FILE_QUESTIONS = {"Document", "Multipledocuments"}
|
|
45
45
|
NOT_FIELDS = {"New", "Chatbot", "Stagescarousel"} # components without a field, by last name
|
|
46
46
|
VOID = {"input", "br", "img", "hr", "meta", "link", "source", "wbr", "col", "area"}
|
|
@@ -459,6 +459,8 @@ def _component(
|
|
|
459
459
|
yes_no = props.get("translations", {})
|
|
460
460
|
if short in TEXT_QUESTIONS:
|
|
461
461
|
field_type, spec = FieldType.TEXT, _Spec("text", props["name"], root)
|
|
462
|
+
if short == "Url":
|
|
463
|
+
field_type = FieldType.URL
|
|
462
464
|
if short == "Numberinput":
|
|
463
465
|
label += " (a number)"
|
|
464
466
|
elif short == "Boolean":
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import re
|
|
4
4
|
import unicodedata
|
|
5
5
|
from pathlib import Path, PurePath
|
|
6
|
+
from urllib.parse import urlsplit
|
|
6
7
|
|
|
7
8
|
from atsfill.core.results import Answer, Check, Field, FieldType
|
|
8
9
|
|
|
@@ -25,6 +26,11 @@ def _picked_from(answer: str, suggestion: str) -> bool:
|
|
|
25
26
|
return all(any(p == wanted.strip() for p in parts) for wanted in answer.split(","))
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
def _absolute_url(value: str) -> bool:
|
|
30
|
+
parts = urlsplit(_norm(value))
|
|
31
|
+
return parts.scheme in ("http", "https") and bool(parts.netloc)
|
|
32
|
+
|
|
33
|
+
|
|
28
34
|
def matches(field: Field, answer: Answer, read_back: Answer | None) -> bool:
|
|
29
35
|
if read_back is None:
|
|
30
36
|
return False
|
|
@@ -36,7 +42,7 @@ def matches(field: Field, answer: Answer, read_back: Answer | None) -> bool:
|
|
|
36
42
|
if not isinstance(answer, str) or not isinstance(read_back, str):
|
|
37
43
|
return False
|
|
38
44
|
match field.type:
|
|
39
|
-
case FieldType.TEXT:
|
|
45
|
+
case FieldType.TEXT | FieldType.URL:
|
|
40
46
|
return _norm(answer) == _norm(read_back)
|
|
41
47
|
case FieldType.DATE:
|
|
42
48
|
return answer == read_back
|
|
@@ -74,8 +80,14 @@ def _answer_problem(field: Field, answer: object) -> str | None:
|
|
|
74
80
|
if field.type in (FieldType.CHOICE, FieldType.MULTI_CHOICE) and not _in_options(field, values):
|
|
75
81
|
return f"{answer!r} is not one of {field.options}"
|
|
76
82
|
length = len(_norm(values[0]))
|
|
77
|
-
if
|
|
83
|
+
if (
|
|
84
|
+
field.type in (FieldType.TEXT, FieldType.URL)
|
|
85
|
+
and field.max_length
|
|
86
|
+
and length > field.max_length
|
|
87
|
+
):
|
|
78
88
|
return f"{length} characters, over the limit of {field.max_length}"
|
|
89
|
+
if field.type is FieldType.URL and not _absolute_url(values[0]):
|
|
90
|
+
return "a url answer is an absolute http(s) URL"
|
|
79
91
|
if field.type is FieldType.DATE and not re.fullmatch(r"\d{4}-\d{2}-\d{2}", values[0]):
|
|
80
92
|
return "a date answer is YYYY-MM-DD"
|
|
81
93
|
if field.type is FieldType.FILE:
|
|
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
|