atsfill 0.0.6.dev196__tar.gz → 0.0.6.dev197__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.
Files changed (68) hide show
  1. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/PKG-INFO +1 -1
  2. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/pyproject.toml +1 -1
  3. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/pyproject.toml.orig +1 -1
  4. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/phenom.py +3 -0
  5. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/teamtailor.py +11 -1
  6. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/LICENSE +0 -0
  7. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/README.md +0 -0
  8. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/__init__.py +0 -0
  9. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/__init__.py +0 -0
  10. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/adp.py +0 -0
  11. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/adpmyjobs.py +0 -0
  12. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/ashby.py +0 -0
  13. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/bamboohr.py +0 -0
  14. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/breezy.py +0 -0
  15. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/dayforce.py +0 -0
  16. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/eightfold.py +0 -0
  17. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/greenhouse.py +0 -0
  18. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/gupy.py +0 -0
  19. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/hireology.py +0 -0
  20. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/hiringthing.py +0 -0
  21. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/hrmdirect.py +0 -0
  22. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/isolvedhire.py +0 -0
  23. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/jazzhr.py +0 -0
  24. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/jobaffinity.py +0 -0
  25. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/jobstreet.py +0 -0
  26. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/join.py +0 -0
  27. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/lever.py +0 -0
  28. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/manatal.py +0 -0
  29. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/oracle.py +0 -0
  30. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/paycor.py +0 -0
  31. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/paylocity.py +0 -0
  32. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/personio.py +0 -0
  33. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/pinpoint.py +0 -0
  34. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/recruitee.py +0 -0
  35. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/recruiterflow.py +0 -0
  36. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/rippling.py +0 -0
  37. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/smartrecruiters.py +0 -0
  38. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/successfactors.js +0 -0
  39. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/successfactors.py +0 -0
  40. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/taleo.py +0 -0
  41. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/ukg.py +0 -0
  42. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/welcomekit.py +0 -0
  43. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/werecruit.py +0 -0
  44. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/workable.py +0 -0
  45. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/workatastartup.py +0 -0
  46. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/workday.py +0 -0
  47. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/wttj.py +0 -0
  48. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/adapters/zohorecruit.py +0 -0
  49. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/cli.py +0 -0
  50. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/__init__.py +0 -0
  51. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/accounts.py +0 -0
  52. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/adapter.py +0 -0
  53. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/attempts.py +0 -0
  54. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/browser.py +0 -0
  55. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/commands.py +0 -0
  56. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/conditions.py +0 -0
  57. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/countries.py +0 -0
  58. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/html.py +0 -0
  59. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/matching.py +0 -0
  60. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/otp.py +0 -0
  61. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/passwords.py +0 -0
  62. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/paths.py +0 -0
  63. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/registry.py +0 -0
  64. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/results.py +0 -0
  65. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/snapshot.py +0 -0
  66. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/core/telemetry.py +0 -0
  67. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/py.typed +0 -0
  68. {atsfill-0.0.6.dev196 → atsfill-0.0.6.dev197}/src/atsfill/registry.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atsfill
3
- Version: 0.0.6.dev196
3
+ Version: 0.0.6.dev197
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atsfill"
3
- version = "0.0.6.dev196"
3
+ version = "0.0.6.dev197"
4
4
  description = "Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atsfill"
3
- version = "0.0.6.dev196"
3
+ version = "0.0.6.dev197"
4
4
  description = "Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1088,6 +1088,9 @@ class _Config:
1088
1088
 
1089
1089
  def test(self, name: str, test: dict, path: str, props: dict) -> bool | Condition:
1090
1090
  values = [test["const"]] if "const" in test else test.get("enum")
1091
+ if "matchesAnyOf" in test: # Phenom's own keyword, one value a part of an `anyOf`
1092
+ held = test["matchesAnyOf"]
1093
+ values = held if isinstance(held, list) else [held]
1091
1094
  if name == "resumeName": # `isNotEmpty`: a resume given
1092
1095
  return Is(RESUME_ID)
1093
1096
  if (
@@ -27,6 +27,7 @@ FORM = "#job-application-form"
27
27
  QUESTION = ".question, [data-careersite--form-target=qualifyingAnswers]"
28
28
  LOCKED = "[data-careersite--form-target=formContentWrapper].cursor-not-allowed"
29
29
  LOCATION = "candidate[location][query]"
30
+ COOKIES = "dialog[data-controller~='common--cookies--alert']"
30
31
  VIDEO = "[data-controller~='questions--tt-video-answer']"
31
32
  UPLOAD = "forms--inputs--upload" # a drop zone's controller
32
33
  CAREER_SITE = "a[href*='//www.teamtailor.com'], script[src*='teamtailor-cdn.com/assets/careersite']"
@@ -39,6 +40,7 @@ UPLOAD_TIMEOUT_S = 60
39
40
  UPLOAD_TIMEOUT_MS = UPLOAD_TIMEOUT_S * 1000
40
41
  UPLOAD_POLL_MS = 500
41
42
  KEY_DELAY_MS = 30
43
+ SUGGESTION_TIMEOUT_MS = 10_000
42
44
  QUESTION_TYPES = {
43
45
  "text": FieldType.TEXT,
44
46
  "number": FieldType.TEXT,
@@ -65,6 +67,10 @@ NOT_ASKED = re.compile(r"linkedin|referrer|social_image")
65
67
  STANDARD_KINDS = ("text", "email", "tel", "textarea", "checkbox", "choices", "file", "location")
66
68
  ANSWER_KIND = re.compile(r"\]\[(\w+)\](\[\])?$")
67
69
 
70
+ # Declines the optional cookies on a banner open as a modal (a "takeover"): hidden by the
71
+ # blocker, it still leaves the rest of the page inert, deaf to keys and clicks.
72
+ _DECLINE_TAKEOVER = """banners => banners.filter(b => b.matches(":modal"))
73
+ .forEach(b => b.querySelector("[data-action$='#disableAll']")?.click())"""
68
74
  # Whether each custom or qualifying question shows, in the order `_read` lists them.
69
75
  _VISIBLE = "(form, QUESTION) => [...form.querySelectorAll(QUESTION)].map(q => q.checkVisibility())"
70
76
  # What the form will send: each field's value, a choice as its option's text, a file as the
@@ -203,6 +209,7 @@ class Teamtailor:
203
209
 
204
210
  def fill(self, page: Page, answers: dict[str, Answer]) -> None:
205
211
  form = page.locator(FORM)
212
+ page.locator(COOKIES).evaluate_all(_DECLINE_TAKEOVER)
206
213
  self._videos = {}
207
214
  for field_id in [i for i in answers if i in self._qualifying]:
208
215
  self._fill(form, field_id, answers[field_id])
@@ -368,7 +375,10 @@ def _suggest(field: Field, host: Locator, answer: str) -> None:
368
375
  box.fill("")
369
376
  box.press_sequentially(answer, delay=KEY_DELAY_MS)
370
377
  options = host.locator("[role=option]")
371
- options.first.wait_for()
378
+ try:
379
+ options.first.wait_for(timeout=SUGGESTION_TIMEOUT_MS)
380
+ except browser.Timeout:
381
+ raise NotTaken(f"no suggestion for {answer!r} in {field.id}") from None
372
382
  for i in range(options.count()):
373
383
  if matching.matches(field, answer, options.nth(i).get_attribute("data-description") or ""):
374
384
  browser.click(options.nth(i))
File without changes
File without changes