messages.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import re
  2. from datetime import date
  3. from dataclasses import dataclass, asdict
  4. from enum import Enum
  5. from typing import Optional, Any
  6. from job_search.job_storage import (
  7. JobOffer,
  8. ApplicationProcess,
  9. JobOfferOrigin,
  10. Flexibility,
  11. )
  12. def to_snake_case(string):
  13. return "".join("_" + c.lower() if c.isupper() else c for c in string)
  14. class Message:
  15. ...
  16. class BackgroundScriptMessage(Message):
  17. @staticmethod
  18. def interpret(message):
  19. if not isinstance(message, dict):
  20. raise TypeError(f"message should be a dict, got {type(message)}")
  21. try:
  22. tag = message.pop("tag")
  23. except KeyError:
  24. raise ValueError("message should contain a tag")
  25. message = {to_snake_case(k): v for k, v in message.items()}
  26. match tag:
  27. case "visited_linkedin_job_page":
  28. return VisitedLinkedInJobPageMessage(**message)
  29. case "initial_configuration":
  30. return InitialConfigurationMessage(**message)
  31. case _:
  32. raise ValueError(f"Got message with unknown tag {tag}")
  33. class NativeMessage(Message):
  34. def serialize(self):
  35. if isinstance(self, JobOfferListMessage):
  36. tag = "job_offer_list"
  37. elif isinstance(self, JobAddedMessage):
  38. tag = "job_added"
  39. elif isinstance(self, JobAlreadyExistsMessage):
  40. tag = "job_already_exists"
  41. elif isinstance(self, LogMessage):
  42. tag = "log_message"
  43. else:
  44. raise TypeError(f"No tag was associated to {type(self)} for serialization")
  45. return asdict(self) | {"tag": tag}
  46. @dataclass
  47. class VisitedLinkedInJobPageMessage(BackgroundScriptMessage):
  48. url: str
  49. job_title: str
  50. page_title: str
  51. company: str
  52. location: str
  53. has_simplified_process: bool
  54. company_url: str
  55. flexibility: Optional[str] = None
  56. company_domain: Optional[str] = None
  57. def extract_job_offer(self):
  58. application_process = (
  59. ApplicationProcess.LINKED_IN_SIMPLIFIED
  60. if self.has_simplified_process
  61. else ApplicationProcess.REGULAR
  62. )
  63. if isinstance(self.flexibility, str):
  64. flexibility = Flexibility(self.flexibility)
  65. elif self.flexibility is None:
  66. flexibility = None
  67. return JobOffer(
  68. url=self.url,
  69. title=self.job_title,
  70. company=self.company,
  71. origin=JobOfferOrigin.LINKED_IN,
  72. application_process=application_process,
  73. location=self.location,
  74. company_domain=self.company_domain,
  75. company_url=self.company_url,
  76. flexibility=flexibility,
  77. )
  78. @dataclass
  79. class InitialConfigurationMessage(BackgroundScriptMessage):
  80. jobs_path: str
  81. @dataclass
  82. class JobOfferListMessage(NativeMessage):
  83. job_offers: list[JobOffer]
  84. @dataclass
  85. class JobAddedMessage(NativeMessage):
  86. job: JobOffer
  87. @dataclass
  88. class JobAlreadyExistsMessage(NativeMessage):
  89. job_id: str
  90. class LogLevel(Enum):
  91. DEBUG = "debug"
  92. INFO = "info"
  93. ERROR = "error"
  94. @dataclass
  95. class LogMessage(NativeMessage):
  96. level: LogLevel
  97. content: Any
  98. @staticmethod
  99. def debug(**kwargs):
  100. return LogMessage(level=LogLevel.DEBUG, **kwargs)
  101. @staticmethod
  102. def info(**kwargs):
  103. return LogMessage(level=LogLevel.INFO, **kwargs)
  104. @staticmethod
  105. def error(**kwargs):
  106. return LogMessage(level=LogLevel.ERROR, **kwargs)