Просмотр исходного кода

Do not use Python code to change format of data sent

jherve 1 год назад
Родитель
Сommit
562df99cc9

+ 2 - 105
native/src/job_search/job_storage.py

@@ -5,7 +5,6 @@ from urllib.parse import urlparse, ParseResult
 from dataclasses import dataclass, field, asdict
 from enum import Enum
 from datetime import date, datetime
-from email.utils import parsedate_to_datetime
 
 
 class JobOfferOrigin(Enum):
@@ -39,108 +38,6 @@ class Flexibility(Enum):
     FULL_REMOTE = "full_remote"
 
 
-def convert_to_parse_result(url):
-    if isinstance(url, str):
-        return urlparse(url)
-    elif isinstance(url, ParseResult):
-        return url
-
-def convert_to_bool(s: str) -> bool:
-    return s == "true" or s == "yes" or s == "1"
-
-
-@dataclass
-class JobOffer:
-    id: str
-    url: str = field(repr=False)
-    title: str
-    company: str
-    origin: JobOfferOrigin
-    location: str
-    application_process: ApplicationProcess | None = None
-    company_url: str = ""
-    description: str = ""
-    company_kind: CompanyKind | None = None
-    company_domain: str = ""
-    comment: str = ""
-    tags: list[str] = field(default_factory=list)
-    skills: list[str] = field(default_factory=list)
-    publication_date: date = None
-    xp_required: int | None = None
-    first_seen_date: datetime | None = None
-    application_considered: bool | None = None
-    application_date: date | None = None
-    application_rejection_date: date | None = None
-    contract_type: ContractType | None = ContractType.CDI
-    flexibility: Flexibility | None = None
-    alternate_url: str = None
-    _url: ParseResult = field(init=False, repr=False)
-    _company_url: ParseResult = field(init=False, repr=False)
-    _alternate_url: ParseResult = field(init=False, repr=False, default=None)
-
-    def __post_init__(self):
-        self._url = convert_to_parse_result(self.url)
-        self.url = self._url.geturl()
-
-        self._company_url = convert_to_parse_result(self.company_url)
-        self.company_url = self._company_url.geturl()
-
-        if self.alternate_url:
-            self._alternate_url = convert_to_parse_result(self.alternate_url)
-            self.alternate_url = self._alternate_url.geturl()
-
-    def to_storage(self):
-        return {
-            k: v
-            for k, v in asdict(self).items()
-            if k not in ["_url", "_company_url", "_alternate_url"]
-        }
-
-    @staticmethod
-    def from_storage(dict: dict):
-        for field, converter in [
-            ("origin", JobOfferOrigin),
-            ("application_process", ApplicationProcess),
-            ("company_kind", CompanyKind),
-            ("contract_type", ContractType),
-            ("flexibility", Flexibility),
-            ("xp_required", int),
-            ("first_seen_date", parsedate_to_datetime),
-            ("publication_date", date.fromisoformat),
-            ("application_considered", convert_to_bool),
-            ("application_date", date.fromisoformat),
-            ("application_rejection_date", date.fromisoformat),
-        ]:
-            if field in dict:
-                dict[field] = converter(dict[field])
-
-        # For now we simply ignore application-related fields
-        # read from the storage.
-        for k in [
-            "application_first_seen_date",
-            "application_first_response_date",
-            "application_cv_version",
-            "application_appointments",
-            "application_message",
-            "application_questions",
-            "application_url",
-            "application_contacts",
-            "application_text",
-        ]:
-            try:
-                del dict[k]
-            except KeyError:
-                pass
-
-        return JobOffer(**dict)
-
-
-def remove_whitespace(s):
-    s = re.sub(r"[^\w\s]", "", s)
-    s = re.sub(r"\s+", "_", s)
-    return s
-
-
 @dataclass
 class JobStorage:
     base_dir: Path
@@ -175,8 +72,8 @@ class JobStorage:
                 f.write("%type: first_seen_date date\n")
                 f.write("%auto: first_seen_date\n")
 
-    def read_all(self) -> dict[str, JobOffer]:
-        return {r["id"]: JobOffer.from_storage(r) for r in self.select_all("job_offer")}
+    def read_all(self) -> dict[str, dict]:
+        return {r["id"]: r for r in self.select_all("job_offer")}
 
     def insert_record(self, type_, fields):
         cmd_args = [

+ 1 - 2
native/src/job_search/messages.py

@@ -4,7 +4,6 @@ from dataclasses import dataclass, asdict
 from enum import Enum
 from typing import Optional, Any
 from job_search.job_storage import (
-    JobOffer,
     ApplicationProcess,
     JobOfferOrigin,
     Flexibility,
@@ -87,7 +86,7 @@ class InitialConfigurationMessage(BackgroundScriptMessage):
 
 @dataclass
 class JobOfferListMessage(NativeMessage):
-    job_offers: list[JobOffer]
+    job_offers: list[dict]
 
     def serialize(self):
         return {"tag": "NativeMessageJobOfferList", "values": [self.job_offers]}

+ 26 - 13
native/tests/test_storage.py

@@ -1,14 +1,16 @@
 import pytest
 from datetime import date, datetime
+from email.utils import parsedate_to_datetime
 from job_search.job_storage import (
     JobStorage,
-    JobOffer,
     JobOfferOrigin,
     ApplicationProcess,
     CompanyKind,
     ContractType,
     Flexibility,
 )
+from job_search.read_write import EnhancedJSONEncoder
+import json
 
 
 @pytest.fixture(params=[JobStorage])
@@ -18,7 +20,7 @@ def job_storage(request, tmp_path):
 
 @pytest.fixture(
     params=[
-        JobOffer(
+        dict(
             id="linked_in_3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             title="Job title",
@@ -30,7 +32,7 @@ def job_storage(request, tmp_path):
             company_url="https://www.linkedin.com/company/the-company/life",
             publication_date=date.today(),
         ),
-        JobOffer(
+        dict(
             id="linked_in_3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             title="Job title",
@@ -53,7 +55,7 @@ def job_storage(request, tmp_path):
             application_date=date.today(),
             application_rejection_date=date.today(),
         ),
-        JobOffer(
+        dict(
             id="linked_in_3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             title="Job title",
@@ -81,23 +83,34 @@ class TestJobStorage:
         assert job_storage.read_all() == {}
 
     def test_job_addition(self, job_storage, linked_in_job_offer):
-        job_storage.insert_record("job_offer", linked_in_job_offer.to_storage())
+        job_storage.insert_record("job_offer", linked_in_job_offer)
 
         all_items = job_storage.read_all().items()
         assert len(all_items) == 1
 
         [(id, stored_job)] = all_items
-        assert isinstance(stored_job.first_seen_date, datetime)
-        assert id == stored_job.id
+        # Check that a "first seen date" field has been set, that reads as a datetime
+        assert isinstance(parsedate_to_datetime(stored_job["first_seen_date"]), datetime)
+        assert id == stored_job["id"]
+    
+        # Job offers should look as if encoded in JSON and then decoded ...
+        expected_read = json.loads(json.dumps(linked_in_job_offer, cls=EnhancedJSONEncoder))
+        # ... with some fields added
+        expected_read["skills"] = expected_read["skills"] if "skills" in expected_read else []
+        expected_read["tags"] = expected_read["tags"] if "tags" in expected_read else []
+        # ... and the integers won't be properly readback
+        if "xp_required" in expected_read:
+            expected_read["xp_required"] = str(expected_read["xp_required"])
 
-        # Reset the first_seen_date to None, for comparison with the non-stored version
-        stored_job.first_seen_date = None
-        assert stored_job == linked_in_job_offer
+        # Remove automatically-added first_seen_date for comparison with the non-stored version
+        del stored_job["first_seen_date"]
+
+        assert stored_job == expected_read
 
     def test_job_duplicate_addition(self, job_storage, linked_in_job_offer):
-        job_storage.insert_record("job_offer", linked_in_job_offer.to_storage())
+        job_storage.insert_record("job_offer", linked_in_job_offer)
 
         with pytest.raises(FileExistsError) as excinfo:
-            job_storage.insert_record("job_offer", linked_in_job_offer.to_storage())
+            job_storage.insert_record("job_offer", linked_in_job_offer)
 
-        assert linked_in_job_offer.id in str(excinfo.value)
+        assert linked_in_job_offer["id"] in str(excinfo.value)

+ 18 - 2
src/NativeMessage.purs

@@ -7,8 +7,9 @@ import Browser.WebExt.Message (Message, mkMessage, unwrapMessage)
 import Browser.WebExt.Port (Port, onDisconnectAddListener, onMessageAddListener)
 import Browser.WebExt.Port as Port
 import Browser.WebExt.Runtime (Application, connectNative)
+import Control.Alt ((<|>))
 import Data.Argonaut.Decode (class DecodeJson, JsonDecodeError(..), printJsonDecodeError)
-import Data.Argonaut.Decode.Decoders (decodeString)
+import Data.Argonaut.Decode.Decoders (decodeBoolean, decodeString)
 import Data.Argonaut.Decode.Generic (genericDecodeJson)
 import Data.Argonaut.Encode (class EncodeJson, encodeJson)
 import Data.Argonaut.Encode.Generic (genericEncodeJson)
@@ -55,6 +56,21 @@ instance DecodeJson ApplicationProcess where
     Right "spurious" -> Right ApplicationProcessSpurious
     _ -> Left $ UnexpectedValue json
 
+newtype BooleanStr = BooleanStr Boolean
+derive instance Generic BooleanStr _
+instance Show BooleanStr where show = genericShow
+instance EncodeJson BooleanStr where encodeJson (BooleanStr bool) = encodeJson bool
+
+instance DecodeJson BooleanStr where
+  decodeJson json = decodedAsBoolean <|> decodedAsString
+    where
+      decodedAsBoolean = map (\b -> BooleanStr b) $ decodeBoolean json
+      decodedAsString =
+        case decodeString json of
+          Right "true" -> Right (BooleanStr true)
+          Right "false" -> Right (BooleanStr false)
+          _ -> Left $ UnexpectedValue json
+
 type NativePythonJobOffer = {
   id :: String,
   origin :: String,
@@ -68,7 +84,7 @@ type NativePythonJobOffer = {
   flexibility :: Maybe JobFlexibility,
   comment :: Maybe String,
   application_process :: Maybe ApplicationProcess,
-  application_considered :: Maybe Boolean,
+  application_considered :: Maybe BooleanStr,
   application_date :: Maybe String,
   application_rejection_date :: Maybe String
 }