atsfill 0.0.7.dev208__tar.gz → 0.0.7.dev209__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 (69) hide show
  1. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/PKG-INFO +1 -1
  2. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/pyproject.toml +1 -1
  3. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/pyproject.toml.orig +1 -1
  4. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/join.py +19 -3
  5. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/LICENSE +0 -0
  6. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/README.md +0 -0
  7. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/__init__.py +0 -0
  8. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/__init__.py +0 -0
  9. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/adp.py +0 -0
  10. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/adpmyjobs.py +0 -0
  11. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/ashby.py +0 -0
  12. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/bamboohr.py +0 -0
  13. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/breezy.py +0 -0
  14. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/dayforce.py +0 -0
  15. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/eightfold.py +0 -0
  16. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/greenhouse.py +0 -0
  17. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/gupy.py +0 -0
  18. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/hireology.py +0 -0
  19. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/hiringthing.py +0 -0
  20. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/hrmdirect.py +0 -0
  21. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/isolvedhire.py +0 -0
  22. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/jazzhr.py +0 -0
  23. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/jobaffinity.py +0 -0
  24. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/jobstreet.py +0 -0
  25. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/lever.py +0 -0
  26. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/manatal.py +0 -0
  27. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/oracle.py +0 -0
  28. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/paycor.py +0 -0
  29. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/paylocity.py +0 -0
  30. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/personio.py +0 -0
  31. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/phenom.py +0 -0
  32. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/pinpoint.py +0 -0
  33. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/recruitee.py +0 -0
  34. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/recruiterflow.py +0 -0
  35. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/rippling.py +0 -0
  36. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/smartrecruiters.py +0 -0
  37. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/successfactors.js +0 -0
  38. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/successfactors.py +0 -0
  39. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/taleo.py +0 -0
  40. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/teamtailor.py +0 -0
  41. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/ukg.py +0 -0
  42. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/welcomekit.py +0 -0
  43. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/wellfound.py +0 -0
  44. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/werecruit.py +0 -0
  45. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/workable.py +0 -0
  46. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/workatastartup.py +0 -0
  47. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/workday.py +0 -0
  48. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/wttj.py +0 -0
  49. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/adapters/zohorecruit.py +0 -0
  50. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/cli.py +0 -0
  51. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/__init__.py +0 -0
  52. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/accounts.py +0 -0
  53. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/adapter.py +0 -0
  54. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/attempts.py +0 -0
  55. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/browser.py +0 -0
  56. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/commands.py +0 -0
  57. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/conditions.py +0 -0
  58. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/countries.py +0 -0
  59. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/html.py +0 -0
  60. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/matching.py +0 -0
  61. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/otp.py +0 -0
  62. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/passwords.py +0 -0
  63. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/paths.py +0 -0
  64. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/registry.py +0 -0
  65. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/results.py +0 -0
  66. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/snapshot.py +0 -0
  67. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/core/telemetry.py +0 -0
  68. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/py.typed +0 -0
  69. {atsfill-0.0.7.dev208 → atsfill-0.0.7.dev209}/src/atsfill/registry.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atsfill
3
- Version: 0.0.7.dev208
3
+ Version: 0.0.7.dev209
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.7.dev208"
3
+ version = "0.0.7.dev209"
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.7.dev208"
3
+ version = "0.0.7.dev209"
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"
@@ -12,6 +12,7 @@ import json
12
12
  import re
13
13
  import time
14
14
  from collections.abc import Callable
15
+ from contextlib import suppress
15
16
  from dataclasses import replace
16
17
  from typing import Any
17
18
  from urllib.parse import urlsplit
@@ -50,6 +51,7 @@ OPENING_TIMEOUT_S = 20
50
51
  CONTINUE_TIMEOUT_S = 15
51
52
  ADVANCE_TIMEOUT_MS = 3_000 # how long a clicked card takes to move the wizard on by itself
52
53
  INPUT_TIMEOUT_MS = 3_000 # how long a step takes to show its own input
54
+ ANSWER_SHOWN = 200 # of an answer that is not the expected one, in the error
53
55
  CLICKS = 3 # a click on a step still rendering is lost, on a card or on Continue
54
56
  LEAVE_TIMEOUT_MS = 5_000 # how long Continue takes to leave a step, per click
55
57
  POLL_MS = 250
@@ -80,6 +82,7 @@ LACKS = {
80
82
  "candidate.phoneNumber": "a phone",
81
83
  }
82
84
  PDF = [".pdf", "application/pdf"]
85
+ REMOVE_FILE = "button:has(svg[name=TrashIcon])" # a stored file's Remove file, in any language
83
86
 
84
87
  # The profile form's controls, by its field ids.
85
88
  TEXTS = {
@@ -136,7 +139,11 @@ class Join:
136
139
  side_effects = [
137
140
  SideEffect("sign_up", "hiring_system", "login --create creates the candidate account"),
138
141
  SideEffect("profile", "hiring_system", "profile --answers saves the account profile"),
139
- SideEffect("upload", "hiring_system", "a file is stored as soon as it is chosen"),
142
+ SideEffect(
143
+ "upload",
144
+ "hiring_system",
145
+ "a file is stored as soon as it is chosen, replacing the one the account keeps",
146
+ ),
140
147
  SideEffect("submit", "employer", "the application reaches the employer"),
141
148
  ]
142
149
 
@@ -215,7 +222,10 @@ class Join:
215
222
  "query": QUESTIONS,
216
223
  }
217
224
  answered = page.evaluate(_FETCH, [f"{GRAPHQL}?operationName=JobScreeningQuestions", body])
218
- questions = answered["data"]["jobScreeningQuestions"]["questions"]
225
+ listed = ((answered or {}).get("data") or {}).get("jobScreeningQuestions")
226
+ if not listed:
227
+ raise RuntimeError(f"JobScreeningQuestions answered {str(answered)[:ANSWER_SHOWN]}")
228
+ questions = listed["questions"]
219
229
  self._questions = sorted(questions, key=lambda q: q.get("position") or 0)
220
230
  browser.click(page.locator("[data-testid=ApplyButton]").locator("visible=true").first)
221
231
  page.wait_for_url("**/apply/**", timeout=STEP_TIMEOUT_MS)
@@ -383,7 +393,13 @@ class Join:
383
393
  def _file(self, page: Page, field_id: str, answer: Answer | None) -> None:
384
394
  if not isinstance(answer, str):
385
395
  return
386
- page.locator("input[type=file]").first.set_input_files(answer)
396
+ upload, stored = page.locator("input[type=file]"), page.locator(REMOVE_FILE)
397
+ with suppress(Timeout): # the step before's Remove file stays a moment
398
+ upload.first.wait_for(state="attached", timeout=INPUT_TIMEOUT_MS)
399
+ upload.or_(stored).first.wait_for(state="attached", timeout=STEP_TIMEOUT_MS)
400
+ if not upload.count(): # a file the account keeps: the answer replaces it
401
+ stored.click(timeout=STEP_TIMEOUT_MS)
402
+ upload.first.set_input_files(answer)
387
403
  # once stored, the step shows the file in place of its input
388
404
  page.wait_for_function(
389
405
  "() => !document.querySelector('input[type=file]')", timeout=UPLOAD_TIMEOUT_MS
File without changes
File without changes