Skip to content

feat: add dynamic url info#998

Open
Pablu23 wants to merge 9 commits into
mainfrom
feat-add-dynamic-url-info
Open

feat: add dynamic url info#998
Pablu23 wants to merge 9 commits into
mainfrom
feat-add-dynamic-url-info

Conversation

@Pablu23

@Pablu23 Pablu23 commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Description

  • Briefly summarize your changes in a few bullet points (can and should correspond to CHANGELOG.md)
  • Relates to #XXX (insert issue number here), if there is a corresponding GH issue

Assignee

  • The changes adhere to the contribution guidelines
  • I have performed a self-review of my code
  • My changes generate no new warnings (e.g. flake8/mypy/pytest/...) other than deprecations

Documentation

Code Quality

  • Patch test coverage > 95% and does not decrease
  • New code uses correct & specific type hints

How did you verify that the changes work in practice?

  • Ran tests

Reviewer


The rendered docs for this PR can be found here.


The rendered docs for this PR can be found here.


The rendered docs for this PR can be found here.

@Pablu23 Pablu23 self-assigned this Jul 22, 2026
@Pablu23 Pablu23 changed the title Feat add dynamic url info feat: add dynamic url info Jul 22, 2026
Comment thread logprep/abc/getter.py Outdated
Comment thread logprep/processor/generic_adder/processor.py Outdated
Comment thread logprep/abc/getter.py Outdated
super().setup()
for rule in self.rules:
rule = typing.cast(GenericAdderRule, rule)
rule.init_generic_adder(self._job_tag_for_cleanup)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we really need this external init and would like to avoid it. The list_comparison has it, as the list_search_base_path comes from the processor and is relevant for the function of the rule. Here rule could generate it's own job tag, couldn't it?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we really need this external init and would like to avoid it. The list_comparison has it, as the list_search_base_path comes from the processor and is relevant for the function of the rule. Here rule could generate it's own job tag, couldn't it?

Yes we do, I forgot to add the _shut_down function here, this makes it clearer why we need to init here

@mhoff mhoff Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mh, I see where you are coming from. Alright, we can keep this for consistency with other processors (namely list_comparison).

But I still think that the job_tag and managing callbacks is rather an implementation detail of the rule, which it should handle itself. I think it would be consequential, that processor.setup() calls rule.setup() and processor.shut_down() calls rule.shut_down(), whereas managing callbacks is completely handled by the rule.

Currently we are calling an init (which has setup-like semantics) on the rule but basically handle the shutdown on the processor-level, which is inconsistent.

Comment thread logprep/processor/generic_adder/rule.py Outdated
),
converter=lambda x: x if x is None or isinstance(x, list) else [x],
default=None,
eq=False,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the precise use case for eq=False... but we would theoretically also need it for add_from_url as well

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the precise use case for eq=False... but we would theoretically also need it for add_from_url as well

Yeah im unsure about this aswell, would we just want to remove the eq here then?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe remove it and see what happens. I'd figure rule equality might have something to do with the rule tree or the auto rule tester. Might also be a question we could place Monday. I suspect we can remove it, tbh. Either way, remove it completely or add it to both fields - no in between

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe remove it and see what happens. I'd figure rule equality might have something to do with the rule tree or the auto rule tester. Might also be a question we could place Monday. I suspect we can remove it, tbh. Either way, remove it completely or add it to both fields - no in between

Okay I asked ai, and it seems like eq=False says dont check this field when comparing two rules against each other. Why we add that here and nowhere else is because, eager loading of a rule file adds the content to add which DOES get checked for equality. Meaning two rules, one with a static add, and one pointing at a file with the same content, are both evaulating to be the same rule. In that case I would actually leave eq=False for add_from_file only so that logic stays, but that doesnt make sense for add_from_url as we cannot eagerly load that.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand. Please add a comment on the eq=False to explain this and maybe also add eq=True for add_from_url and explain why this is True

Comment thread logprep/processor/generic_adder/rule.py Outdated
Comment thread logprep/processor/generic_adder/rule.py Outdated
Comment thread tests/unit/processor/generic_adder/test_generic_adder.py Outdated
Comment thread logprep/processor/generic_adder/rule.py
Comment thread logprep/processor/generic_adder/rule.py Outdated
from logprep.util.defaults import ENV_NAME_LOGPREP_GETTER_CONFIG
from logprep.util.getter import HttpGetter


Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_cases and failure_test_cases would be nice here as well. I figure that this is a scope creep; so probably we drop it for this PR

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_cases and failure_test_cases would be nice here as well. I figure that this is a scope creep; so probably we drop it for this PR

Yes thats right, but I would rather have this be in another PR / task of itself

@Pablu23
Pablu23 force-pushed the feat-add-dynamic-url-info branch from e0b8071 to 1ab8eeb Compare July 23, 2026 12:06
Comment thread logprep/abc/getter.py
Comment on lines +161 to +166
def get_list(self, content_field: str | None = None) -> list:
"""Gets list and fails otherwise"""

content = self._resolve_content_by_content_type()

content = Getter._apply_content_field(content, content_field)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add this to get_dict as well? Not related to your PR, but it would make sense to add support to all three high-level interface methods in the same way.

Also, my personal preference would be:

Suggested change
def get_list(self, content_field: str | None = None) -> list:
"""Gets list and fails otherwise"""
content = self._resolve_content_by_content_type()
content = Getter._apply_content_field(content, content_field)
def get_list(self, content_field: str | None = None) -> list:
"""Gets list and fails otherwise"""
content = self._resolve_content_by_content_type()
content = Getter._apply_content_field(content, content_field)

(so without the line break)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure ill fix the line breaks, but adding this to get_dict somehow feels wrong to me, because we just have to do this here and for collection because we want to return a list, but in the dict, we can just get the content_field value like you would normally in a dict. Whats your opinion on this?

"""

import typing
from typing import Sequence

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from typing import Sequence
from collections.abc import Sequence

"""

import typing
from typing import Sequence

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from typing import Sequence
from collections.abc import Sequence

Comment on lines +43 to +46
@property
def _rules(self) -> Sequence[GenericAdderRule]:
"""Returns all rules"""
return typing.cast(Sequence[GenericAdderRule], self.rules)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not "rules"?

Suggested change
@property
def _rules(self) -> Sequence[GenericAdderRule]:
"""Returns all rules"""
return typing.cast(Sequence[GenericAdderRule], self.rules)
@property
def rules(self) -> Sequence[GenericAdderRule]:
"""Returns all rules"""
return typing.cast(Sequence[GenericAdderRule], super().rules)

like in

    @property
    def rules(self) -> Sequence[PseudonymizerRule]:
        """Returns all rules"""
        return typing.cast(Sequence[PseudonymizerRule], super().rules)

and other sites

Comment on lines +111 to +125
url: str = field(
validator=[validators.instance_of(str), validators.matches_re(r"^https?://.+")]
)

target_field: str | None = field(
default=None, validator=validators.optional(validators.instance_of(str))
)

target_field_mapping: dict[str, str] = field(
validator=validators.deep_mapping(
key_validator=validators.instance_of(str),
value_validator=validators.instance_of(str),
),
factory=dict,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing pydoc for all three

Comment on lines +127 to +129
def __attrs_post_init__(self) -> None:
if not self.target_field and not self.target_field_mapping:
raise ValueError("adding values from url requires target_field or target_field_mapping")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is does not have a super class, but we need to ensure all derived classes actually call super attrs_post_init. I wonder if we can just always have it (even for non-derived classes), but I have not tried it and out this might throw an error. Try it out if you feel like it, otherwise resolve as a no-op.

Suggested change
def __attrs_post_init__(self) -> None:
if not self.target_field and not self.target_field_mapping:
raise ValueError("adding values from url requires target_field or target_field_mapping")
def __attrs_post_init__(self) -> None:
super().__attrs_post_init__()
if not self.target_field and not self.target_field_mapping:
raise ValueError("adding values from url requires target_field or target_field_mapping")

default=None,
validator=validators.optional(validators.instance_of(AddFromUrlConfig)),
converter=_convert_add_from_url,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pydoc missing

@@ -160,20 +213,47 @@ def _refresh_add(self):
def __attrs_post_init__(self):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super needs to be called here

Comment on lines +248 to +249
# TODO: This never gets cleaned up, Memory leak on a lot of new generic adders / generic resolvers
getter.add_callback(f"generic_adder:{self.id}:{add_file}", self._refresh_add)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the job_tag now and can resolve this properly

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not here we dont, because that is the rule config and gets loaded on config time, we dont pass in the job tag, but I guess I could just change that with a private _callback_tag on the config, pass that into the rule from the processor and then also remove the init because now the rule knows the callback tag?

)

# Eagerly loaded from file
for add_file in self.add_from_file: # pylint: disable=not-an-iterable

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for add_file in self.add_from_file: # pylint: disable=not-an-iterable
for add_file in self.add_from_file:

It's an iterable, right?

self._dynamic_template = resolved_template
self._dynamic_identifiers = tuple(resolved_template.get_identifiers())

if len(self._dynamic_identifiers) > 0:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if len(self._dynamic_identifiers) > 0:
if self._dynamic_identifiers:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could even go all fancy with

Suggested change
if len(self._dynamic_identifiers) > 0:
self._is_dynamic = bool(self._dynamic_identifiers)

but I think the if makes sense.

However, you can simply merge this if with the check afterwards


self._static_uri = static_uri

def _dynamic_add_from_url(self, event: dict) -> dict[str, FieldValue]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typing missing on dict

self._static_uri = static_uri

def _dynamic_add_from_url(self, event: dict) -> dict[str, FieldValue]:
config = typing.cast(GenericAdderRule.Config, self._config)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the standard casted config property for this


def add(self, event: dict) -> dict:
"""Returns the fields to add"""
config = typing.cast(GenericAdderRule.Config, self._config)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, casted property

Comment on lines +423 to +424
if config.add_from_file or config.add:
return config.add

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the logic?

  • if config.add_from_file => config.add should already be populated through attrs_post_init
  • if config.add => config.add already preloaded through attrs_post_init or static http

Shouldn't the check then be simply if config.add?

@mhoff mhoff Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I confused config.add with self.add. But you want to return self.add here, right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct, I initially didnt understand that add_from_file populates add automatically

Comment on lines +426 to +428
if not self._is_dynamic:
assert self._static_uri
return self._content_to_items_to_add(self._dynamic_content[self._static_uri])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads weird.

if not dynamic: ... get dynamic content ...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are right, I might have to change the name of _dynamic_content, as that is just a cache

def _content_to_items_to_add(self, content: FieldValue):
items_to_add: dict[str, FieldValue] = {}

config = typing.cast(GenericAdderRule.Config, self._config)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

property

assert config.add_from_url.target_field_mapping is not None

if not isinstance(content, dict):
raise ValueError("add_from_url.target_field_mapping requires a mapping response")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise ValueError("add_from_url.target_field_mapping requires a mapping response")
raise ValueError(f"add_from_url.target_field_mapping requires a mapping response, got: {content}")

item = get_dotted_field_value_with_missing(content, mapping_source_field)
if item is MISSING:
logger.warning(
"could not add source_field: %s for target_field: %s because was missing",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"could not add source_field: %s for target_field: %s because was missing",
"could not add source_field %s for target_field %s because it is missing",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it reads weird with the colons. Rather "source_field=%s" or "source_field %s" or "source_field '%s'" I'd say

mapping_source_field,
mapping_target_field,
)
continue

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we stick to this, this might be a case for a processor warning

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants