diff --git a/src/kleinanzeigen_bot/__init__.py b/src/kleinanzeigen_bot/__init__.py index 765e46f..f94db26 100644 --- a/src/kleinanzeigen_bot/__init__.py +++ b/src/kleinanzeigen_bot/__init__.py @@ -386,14 +386,6 @@ class KleinanzeigenBot(WebScrapingMixin): if not should_include: continue - # Get description with prefix/suffix from ad config if present, otherwise use defaults - description = self.__get_description_with_affixes(ad_cfg) - - # Validate total length - ensure(len(description) <= MAX_DESCRIPTION_LENGTH, - f"Length of ad description including prefix and suffix exceeds 4000 chars. " - f"Description length: {len(description)} chars. @ {ad_file}.") - def assert_one_of(path:str, allowed:Iterable[str]) -> None: # ruff: noqa: B023 function-uses-loop-variable ensure(dicts.safe_get(ad_cfg, *path.split(".")) in allowed, f"-> property [{path}] must be one of: {allowed} @ [{ad_file}]") @@ -408,7 +400,8 @@ class KleinanzeigenBot(WebScrapingMixin): assert_one_of("type", {"OFFER", "WANTED"}) assert_min_len("title", 10) - assert_has_value("description") + ensure(self.__get_description(ad_cfg, with_affixes = False), f"-> property [description] not specified @ [{ad_file}]") + self.__get_description(ad_cfg, with_affixes = True) # validates complete description assert_one_of("price_type", {"FIXED", "NEGOTIABLE", "GIVE_AWAY", "NOT_APPLICABLE"}) if ad_cfg["price_type"] == "GIVE_AWAY": ensure(not dicts.safe_get(ad_cfg, "price"), f"-> [price] must not be specified for GIVE_AWAY ad @ [{ad_file}]") @@ -707,7 +700,7 @@ class KleinanzeigenBot(WebScrapingMixin): ############################# # set description ############################# - description = self.__get_description_with_affixes(ad_cfg) + description = self.__get_description(ad_cfg, with_affixes = True) await self.web_execute("document.querySelector('#pstad-descrptn').value = `" + description.replace("`", "'") + "`") ############################# @@ -1104,8 +1097,8 @@ class KleinanzeigenBot(WebScrapingMixin): else: LOG.error("The page with the id %d does not exist!", ad_id) - def __get_description_with_affixes(self, ad_cfg:dict[str, Any]) -> str: - """Get the complete description with prefix and suffix applied. + def __get_description(self, ad_cfg:dict[str, Any], *, with_affixes:bool) -> str: + """Get the ad description optionally with prefix and suffix applied. Precedence (highest to lowest): 1. Direct ad-level affixes (description_prefix/suffix) @@ -1117,7 +1110,7 @@ class KleinanzeigenBot(WebScrapingMixin): ad_cfg: The ad configuration dictionary Returns: - The complete description with prefix and suffix applied + The raw or complete description with prefix and suffix applied """ # Get the main description text description_text = "" @@ -1126,33 +1119,36 @@ class KleinanzeigenBot(WebScrapingMixin): elif isinstance(ad_cfg.get("description"), str): description_text = ad_cfg["description"] - # Get prefix with precedence - prefix = ( - # 1. Direct ad-level prefix - ad_cfg.get("description_prefix") if ad_cfg.get("description_prefix") is not None - # 2. Legacy nested ad-level prefix - else dicts.safe_get(ad_cfg, "description", "prefix") - if dicts.safe_get(ad_cfg, "description", "prefix") is not None - # 3. Global prefix from config - else get_description_affixes(self.config, prefix = True) - or "" # Default to empty string if all sources are None - ) + if with_affixes: + # Get prefix with precedence + prefix = ( + # 1. Direct ad-level prefix + ad_cfg.get("description_prefix") if ad_cfg.get("description_prefix") is not None + # 2. Legacy nested ad-level prefix + else dicts.safe_get(ad_cfg, "description", "prefix") + if dicts.safe_get(ad_cfg, "description", "prefix") is not None + # 3. Global prefix from config + else get_description_affixes(self.config, prefix = True) + or "" # Default to empty string if all sources are None + ) - # Get suffix with precedence - suffix = ( - # 1. Direct ad-level suffix - ad_cfg.get("description_suffix") if ad_cfg.get("description_suffix") is not None - # 2. Legacy nested ad-level suffix - else dicts.safe_get(ad_cfg, "description", "suffix") - if dicts.safe_get(ad_cfg, "description", "suffix") is not None - # 3. Global suffix from config - else get_description_affixes(self.config, prefix = False) - or "" # Default to empty string if all sources are None - ) + # Get suffix with precedence + suffix = ( + # 1. Direct ad-level suffix + ad_cfg.get("description_suffix") if ad_cfg.get("description_suffix") is not None + # 2. Legacy nested ad-level suffix + else dicts.safe_get(ad_cfg, "description", "suffix") + if dicts.safe_get(ad_cfg, "description", "suffix") is not None + # 3. Global suffix from config + else get_description_affixes(self.config, prefix = False) + or "" # Default to empty string if all sources are None + ) - # Combine the parts and replace @ with (at) - final_description = str(prefix) + str(description_text) + str(suffix) - final_description = final_description.replace("@", "(at)") + # Combine the parts and replace @ with (at) + final_description = str(prefix) + str(description_text) + str(suffix) + final_description = final_description.replace("@", "(at)") + else: + final_description = description_text # Validate length ensure(len(final_description) <= MAX_DESCRIPTION_LENGTH, diff --git a/tests/unit/test_init.py b/tests/unit/test_init.py index d8687bc..3d2e5d9 100644 --- a/tests/unit/test_init.py +++ b/tests/unit/test_init.py @@ -1045,7 +1045,7 @@ class TestKleinanzeigenBotPrefixSuffix: test_bot.config = config ad_cfg = {"description": raw_description, "active": True} # Access private method using the correct name mangling - description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg) + description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True) assert description == expected_description def test_description_length_validation(self, test_bot:KleinanzeigenBot) -> None: @@ -1062,7 +1062,7 @@ class TestKleinanzeigenBotPrefixSuffix: } with pytest.raises(AssertionError) as exc_info: - getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg) + getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True) assert "Length of ad description including prefix and suffix exceeds 4000 chars" in str(exc_info.value) assert "Description length: 4001" in str(exc_info.value) @@ -1087,7 +1087,7 @@ class TestKleinanzeigenBotDescriptionHandling: } # The description should be returned as-is without any prefix/suffix - description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg) + description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True) assert description == "Test Description" def test_description_with_only_new_format_affixes(self, test_bot:KleinanzeigenBot) -> None: @@ -1104,7 +1104,7 @@ class TestKleinanzeigenBotDescriptionHandling: "active": True } - description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg) + description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True) assert description == "Prefix: Test Description :Suffix" def test_description_with_mixed_config_formats(self, test_bot:KleinanzeigenBot) -> None: @@ -1125,7 +1125,7 @@ class TestKleinanzeigenBotDescriptionHandling: "active": True } - description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg) + description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True) assert description == "New Prefix: Test Description :New Suffix" def test_description_with_ad_level_affixes(self, test_bot:KleinanzeigenBot) -> None: @@ -1144,7 +1144,7 @@ class TestKleinanzeigenBotDescriptionHandling: "active": True } - description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg) + description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True) assert description == "Ad Prefix: Test Description :Ad Suffix" def test_description_with_none_values(self, test_bot:KleinanzeigenBot) -> None: @@ -1165,7 +1165,7 @@ class TestKleinanzeigenBotDescriptionHandling: "active": True } - description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg) + description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True) assert description == "Test Description" def test_description_with_email_replacement(self, test_bot:KleinanzeigenBot) -> None: @@ -1179,7 +1179,7 @@ class TestKleinanzeigenBotDescriptionHandling: "active": True } - description = getattr(test_bot, "_KleinanzeigenBot__get_description_with_affixes")(ad_cfg) + description = getattr(test_bot, "_KleinanzeigenBot__get_description")(ad_cfg, with_affixes = True) assert description == "Contact: test(at)example.com"