atsfill 0.0.5.dev187__tar.gz → 0.0.5.dev188__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 (66) hide show
  1. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/PKG-INFO +1 -1
  2. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/pyproject.toml +1 -1
  3. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/pyproject.toml.orig +1 -1
  4. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/phenom.py +22 -2
  5. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/paths.py +8 -0
  6. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/LICENSE +0 -0
  7. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/README.md +0 -0
  8. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/__init__.py +0 -0
  9. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/__init__.py +0 -0
  10. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/adp.py +0 -0
  11. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/adpmyjobs.py +0 -0
  12. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/ashby.py +0 -0
  13. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/bamboohr.py +0 -0
  14. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/breezy.py +0 -0
  15. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/dayforce.py +0 -0
  16. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/eightfold.py +0 -0
  17. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/greenhouse.py +0 -0
  18. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/gupy.py +0 -0
  19. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/hireology.py +0 -0
  20. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/hiringthing.py +0 -0
  21. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/hrmdirect.py +0 -0
  22. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/isolvedhire.py +0 -0
  23. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/jazzhr.py +0 -0
  24. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/jobaffinity.py +0 -0
  25. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/jobstreet.py +0 -0
  26. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/join.py +0 -0
  27. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/lever.py +0 -0
  28. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/manatal.py +0 -0
  29. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/oracle.py +0 -0
  30. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/paycor.py +0 -0
  31. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/paylocity.py +0 -0
  32. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/personio.py +0 -0
  33. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/pinpoint.py +0 -0
  34. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/recruitee.py +0 -0
  35. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/recruiterflow.py +0 -0
  36. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/rippling.py +0 -0
  37. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/smartrecruiters.py +0 -0
  38. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/successfactors.js +0 -0
  39. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/successfactors.py +0 -0
  40. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/taleo.py +0 -0
  41. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/teamtailor.py +0 -0
  42. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/ukg.py +0 -0
  43. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/welcomekit.py +0 -0
  44. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/workable.py +0 -0
  45. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/workatastartup.py +0 -0
  46. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/workday.py +0 -0
  47. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/wttj.py +0 -0
  48. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/adapters/zohorecruit.py +0 -0
  49. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/cli.py +0 -0
  50. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/__init__.py +0 -0
  51. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/accounts.py +0 -0
  52. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/adapter.py +0 -0
  53. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/attempts.py +0 -0
  54. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/browser.py +0 -0
  55. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/commands.py +0 -0
  56. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/conditions.py +0 -0
  57. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/html.py +0 -0
  58. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/matching.py +0 -0
  59. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/otp.py +0 -0
  60. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/passwords.py +0 -0
  61. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/registry.py +0 -0
  62. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/results.py +0 -0
  63. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/snapshot.py +0 -0
  64. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/core/telemetry.py +0 -0
  65. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/src/atsfill/py.typed +0 -0
  66. {atsfill-0.0.5.dev187 → atsfill-0.0.5.dev188}/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.dev187
3
+ Version: 0.0.5.dev188
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.5.dev187"
3
+ version = "0.0.5.dev188"
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.5.dev187"
3
+ version = "0.0.5.dev188"
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"
@@ -8,6 +8,7 @@ fields, dependent lists): a request read or a browser read interprets them witho
8
8
  step. Filling walks the wizard on a fresh session, each step's answers read back before Next.
9
9
  """
10
10
 
11
+ import hashlib
11
12
  import http.cookiejar
12
13
  import json
13
14
  import re
@@ -23,7 +24,7 @@ from pathlib import PurePath
23
24
  from typing import Any
24
25
  from urllib.parse import parse_qs, urlsplit
25
26
 
26
- from atsfill.core import browser, matching
27
+ from atsfill.core import browser, matching, paths
27
28
  from atsfill.core.adapter import Gap, NotTaken, Unsupported, known_gap
28
29
  from atsfill.core.browser import HTTP_TIMEOUT_S, BrowserError, Locator, Overlaid, Page, Timeout
29
30
  from atsfill.core.results import (
@@ -67,6 +68,7 @@ PICK_TRIES = 3
67
68
  MAX_STEPS = 12
68
69
  OPTIONS_TIMEOUT_S = 8
69
70
  LOADERS_AT_ONCE = 8 # a loader request a country: 245 on one site
71
+ COUNTRIES_KEPT_S = 24 * 3600 # a site's country fields, kept between reads
70
72
  YES_NO = ["Yes", "No"]
71
73
  NO_YES = ["No", "Yes"]
72
74
  MONTH = " (MM/YYYY)"
@@ -1005,7 +1007,7 @@ class _Config:
1005
1007
  values, texts = self.listed.get(path) or ([], [])
1006
1008
  held, _ = self.prefilled(path)
1007
1009
  codes = sorted(values, key=lambda c: c != held)
1008
- parts = self.calls(payload, [{"countryCode": c} for c in codes])
1010
+ parts = self.country_parts(payload, codes)
1009
1011
  by_id: dict[str, dict[Any, dict[str, Any]]] = {}
1010
1012
  for code, part in zip(codes, parts, strict=True):
1011
1013
  part = get(part, "applyGetReferences.data")
@@ -1017,6 +1019,24 @@ class _Config:
1017
1019
  shown = dict(zip(values, texts, strict=False))
1018
1020
  return [_by_country(path, rows, codes, shown) for rows in by_id.values()]
1019
1021
 
1022
+ def country_parts(self, payload: str, codes: list[Any]) -> list[Any]:
1023
+ """Each country's loader answer, kept a day for the site: a read posts one a country,
1024
+ and two reads in a row get `/applySubmit` refused (403) on one site."""
1025
+ key = hashlib.sha1(f"{self.ph.get('locale')} {payload}".encode()).hexdigest()[:12]
1026
+ path = paths.cache_dir() / "phenom" / f"{self.ph.get('refNum')}-{key}.json"
1027
+ kept: dict[str, Any] = {}
1028
+ with suppress(OSError, ValueError):
1029
+ if time.time() - path.stat().st_mtime < COUNTRIES_KEPT_S:
1030
+ kept = json.loads(path.read_text())
1031
+ missing = [c for c in codes if str(c) not in kept]
1032
+ if missing:
1033
+ parts = self.calls(payload, [{"countryCode": c} for c in missing])
1034
+ kept |= {str(c): p for c, p in zip(missing, parts, strict=True) if p is not None}
1035
+ with suppress(OSError):
1036
+ path.parent.mkdir(parents=True, exist_ok=True)
1037
+ path.write_text(json.dumps(kept))
1038
+ return [kept.get(str(c)) for c in codes]
1039
+
1020
1040
  def date_range(
1021
1041
  self, p: dict, u: dict, path: str, nav: Any, when: Condition | None
1022
1042
  ) -> list[dict[str, Any]]:
@@ -60,6 +60,14 @@ def _move_to_default(data: Path) -> None:
60
60
  (data / name).rename(default / name)
61
61
 
62
62
 
63
+ def cache_dir() -> Path:
64
+ """Public data atsfill can fetch again, kept to spare a hiring system the requests:
65
+ `$XDG_CACHE_HOME/atsfill` on Linux (`~/.cache/atsfill`), `~/.atsfill/cache` elsewhere."""
66
+ if xdg := os.environ.get("XDG_CACHE_HOME"):
67
+ return Path(xdg) / "atsfill"
68
+ return Path.home() / (".cache/atsfill" if LINUX else ".atsfill/cache")
69
+
70
+
63
71
  def config_file() -> Path:
64
72
  if xdg := os.environ.get("XDG_CONFIG_HOME"):
65
73
  return Path(xdg) / "atsfill/config.toml"
File without changes
File without changes