Skip to content

feat: Allow config and functions access in stream maps __alias__ expressions#3606

Open
KaruturiS wants to merge 5 commits into
meltano:mainfrom
KaruturiS:feat/issue-3263-alias-functions
Open

feat: Allow config and functions access in stream maps __alias__ expressions#3606
KaruturiS wants to merge 5 commits into
meltano:mainfrom
KaruturiS:feat/issue-3263-alias-functions

Conversation

@KaruturiS
Copy link
Copy Markdown

@KaruturiS KaruturiS commented Apr 16, 2026

This PR refactors _eval_stream from a static method to an instance method, allowing stream aliases to access self.map_config and functions. This enables dynamic aliasing based on user configuration.

Summary by Sourcery

Allow stream alias expressions to be evaluated with mapper instance context, including configuration and helper functions.

New Features:

  • Enable stream alias expressions to access mapper configuration via a config variable.
  • Expose common helper functions (md5, sha256, datetime, bool, json) to stream alias expressions for dynamic alias generation.

Tests:

  • Add parameterized tests verifying alias expressions can use configuration values and md5 hashing for stream name aliases.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 16, 2026

Reviewer's Guide

Refactors stream alias evaluation to be instance-based so that alias expressions can access mapper configuration and a richer set of helper functions, adds tests for configuration- and function-driven aliasing, and tightens alias evaluation behavior and logging.

Sequence diagram for instance-based stream alias evaluation

sequenceDiagram
    participant PM as PluginMapper
    participant RRS as register_raw_stream_schema
    participant ES as _eval_stream
    participant ME as _MapperEval

    PM->>RRS: register_raw_stream_schema(source_stream, stream_def)
    RRS->>RRS: detect __alias__ in stream_def
    RRS->>ES: _eval_stream(expr, stream_name)

    ES->>ES: build names {__stream_name__, config=map_config}
    ES->>ES: build functions from DEFAULT_FUNCTIONS + md5, sha256, datetime, bool, json
    ES->>ME: create _MapperEval(names, functions)
    ME-->>ES: instance
    ES->>ME: eval(expr)
    alt expression evaluates successfully
        ME-->>ES: result (any type)
        ES->>ES: cast result to str
        ES-->>RRS: alias_str
    else NameNotDefined
        ME-->>ES: raise NameNotDefined
        ES-->>RRS: original expr as str
    else InvalidExpression or SyntaxError
        ME-->>ES: raise error
        ES-->>RRS: propagate MapExpressionError
    end

    RRS->>RRS: update stream_alias with evaluated value
Loading

File-Level Changes

Change Details Files
Refactor stream alias evaluation from a static utility to an instance method that can access mapper state, and extend the evaluation environment with config and helper functions while normalizing outputs to strings.
  • Change uses of _eval_stream in register_raw_stream_schema to call the instance method on self instead of the static method.
  • Convert PluginMapper._eval_stream from a staticmethod to an instance method so it can reference self.map_config.
  • Expose stream_name and config in the evaluation namespace for alias expressions.
  • Define an explicit functions dict for _MapperEval including default simpleeval functions plus md5, sha256, datetime, bool, and json wrappers.
  • Improve debug logging when alias expression evaluation fails, switching to %s-style formatting without extra parameter dicts.
  • Ensure alias evaluation results are always cast to str before returning to guarantee consistent downstream handling.
singer_sdk/mapper.py
Add unit coverage for alias expressions that depend on map configuration and helper functions.
  • Introduce a parametrized test exercising alias expressions that read from config and use the md5 helper.
  • Instantiate PluginMapper in tests with stream_maps and stream_map_config to validate that _eval_stream can see map_config and functions.
  • Assert that evaluating the alias expression via mapper._eval_stream returns the expected alias string for each scenario.
tests/core/test_mapper.py

Possibly linked issues

  • #feat: Support functions in stream maps __alias__ expressions: PR changes alias evaluation to instance-based and function-aware, enabling dynamic function-driven alias expressions requested in issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In _eval_stream, the comment mentions self.functions but the evaluator is constructed with only simpleeval.DEFAULT_FUNCTIONS; if PluginMapper can expose custom functions, consider wiring them in (e.g., via getattr(self, "functions", simpleeval.DEFAULT_FUNCTIONS)) or updating the comment for consistency.
  • The new use of self.map_config in _eval_stream assumes it is always defined; if there are code paths where map_config may be absent or None, consider a defensive default (e.g., {}) to avoid unexpected attribute or type errors in alias expressions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_eval_stream`, the comment mentions `self.functions` but the evaluator is constructed with only `simpleeval.DEFAULT_FUNCTIONS`; if `PluginMapper` can expose custom functions, consider wiring them in (e.g., via `getattr(self, "functions", simpleeval.DEFAULT_FUNCTIONS)`) or updating the comment for consistency.
- The new use of `self.map_config` in `_eval_stream` assumes it is always defined; if there are code paths where `map_config` may be absent or `None`, consider a defensive default (e.g., `{}`) to avoid unexpected attribute or type errors in alias expressions.

## Individual Comments

### Comment 1
<location path="singer_sdk/mapper.py" line_range="867-877" />
<code_context>

         try:
-            expr_evaluator = simpleeval.EvalWithCompoundTypes(names=names)
+            # Access self.functions (inherited/available in PluginMapper)
+            # PluginMapper doesn't have a .functions property by default, 
+            # so we'll use a safe fallback or the CustomStreamMap approach.
+            
+            expr_evaluator = simpleeval.EvalWithCompoundTypes(
+                names=names,
+                functions=simpleeval.DEFAULT_FUNCTIONS
+            )
             result = expr_evaluator.eval(expr)
</code_context>
<issue_to_address>
**suggestion:** Use mapper-specific functions instead of hardcoding `DEFAULT_FUNCTIONS`.

Here the evaluator is always initialized with `simpleeval.DEFAULT_FUNCTIONS`, so any mapper-specific functions (e.g., `self.functions` on `PluginMapper` subclasses) will never be used. Consider something like `functions=getattr(self, "functions", simpleeval.DEFAULT_FUNCTIONS)` so alias expressions can use custom helpers while still falling back to the safe default.

```suggestion
        result: str

        try:
            # Use mapper-specific functions when available (e.g., on PluginMapper
            # subclasses), falling back to the safe default set provided by
            # simpleeval.DEFAULT_FUNCTIONS.
            functions = getattr(self, "functions", simpleeval.DEFAULT_FUNCTIONS)

            expr_evaluator = simpleeval.EvalWithCompoundTypes(
                names=names,
                functions=functions,
            )
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread singer_sdk/mapper.py Outdated
@read-the-docs-community
Copy link
Copy Markdown

read-the-docs-community Bot commented Apr 16, 2026

edgarrmondragon and others added 4 commits April 23, 2026 03:26
… HTTP request for a stream (meltano#3483)

## Related

- meltano#1606
- MeltanoLabs/tap-dbt#135
- PoC: reservoir-data/tap-bitly#494

## Summary by Sourcery

Introduce a dedicated HTTPRequest abstraction in the REST stream layer
and update tap-dummyjson to use it for request construction and
parameter handling.

New Features:
- Add an HTTPRequest dataclass for REST streams, including customizable
query parameter encoding and reusable request construction via
RESTStream.get_http_request.

Enhancements:
- Refactor RESTStream.prepare_request to build requests from HTTPRequest
instances and standardize URL parameter typing and encoding.
- Simplify DummyJSONAuthenticator to always set the Authorization header
after ensuring a valid token.
- Update tap-dummyjson client pagination to override get_http_request
instead of get_url_params for setting paging parameters.

Tests:
- Add a unit test covering custom HTTPRequest.encode_params behavior
with safe characters in query parameters.

## Summary by Sourcery

Introduce an HTTPRequest abstraction and context for RESTStream to
construct and customize HTTP requests, and migrate pagination and client
patterns to use it instead of get_url_params while deprecating
prepare_request overrides.

New Features:
- Add an HTTPRequest dataclass and HTTPRequestContext for representing
REST stream HTTP requests and their pagination/context.
- Expose RESTStream.get_http_request for building per-request URL,
headers, params, and payload from stream context and paginator state.

Enhancements:
- Refactor RESTStream request preparation to build PreparedRequest
objects from HTTPRequest instances with standardized payload typing and
parameter encoding.
- Update built-in taps (dummyjson, GitLab) and the cookiecutter tap
template to implement pagination by overriding get_http_request instead
of get_url_params.
- Improve paginator typing for RESTStream and BaseAPIPaginator
implementations.
- Emit deprecation warnings when prepare_request is overridden,
including when the override is in an intermediate base class.
- Adjust documentation examples to show customizing pagination by
modifying HTTPRequest rather than URL parameter dicts.

Documentation:
- Add reference stubs for HTTPRequest and HTTPRequestContext and update
pagination and incremental replication guides to document
get_http_request-based pagination patterns.

Tests:
- Add unit tests for HTTPRequest parameter encoding, including custom
safe characters and non-string parameter values.
- Extend REST pagination and metrics tests to cover the new
get_http_request usage and deprecation warnings for prepare_request
overrides.

---------

Signed-off-by: Edgar Ramírez Mondragón <edgarrm358@gmail.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Signed-off-by: Edgar Ramírez Mondragón <edgarrm358@gmail.com>
@edgarrmondragon edgarrmondragon force-pushed the feat/issue-3263-alias-functions branch from 7038c43 to 7163691 Compare April 24, 2026 17:35
@edgarrmondragon edgarrmondragon self-assigned this Apr 24, 2026
@edgarrmondragon edgarrmondragon added the Deferred Delayed for a future version label Apr 24, 2026
Copy link
Copy Markdown
Collaborator

@edgarrmondragon edgarrmondragon left a comment

Choose a reason for hiding this comment

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

stub

@edgarrmondragon edgarrmondragon changed the title Feat/issue 3263 alias functions feat: Allow config and functions access in stream maps __alias__ expressions Apr 24, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 24, 2026

Merging this PR will not alter performance

✅ 8 untouched benchmarks


Comparing KaruturiS:feat/issue-3263-alias-functions (35b35a4) with main (3365ce6)

Open in CodSpeed

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.86%. Comparing base (3365ce6) to head (35b35a4).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3606   +/-   ##
=======================================
  Coverage   93.85%   93.86%           
=======================================
  Files          73       73           
  Lines        5907     5946   +39     
  Branches      725      729    +4     
=======================================
+ Hits         5544     5581   +37     
- Misses        270      271    +1     
- Partials       93       94    +1     
Flag Coverage Δ
core 82.35% <100.00%> (+0.08%) ⬆️
end-to-end 75.27% <73.17%> (-0.16%) ⬇️
optional-components 42.80% <51.21%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@edgarrmondragon
Copy link
Copy Markdown
Collaborator

@sourcery-ai review

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • Consider avoiding re-creating the funcs dictionary on every _eval_stream call by promoting it to a shared constant or helper so alias evaluation reuses the same function set and avoids per-call allocations.
  • Since _eval_stream now relies on self.map_config, it may be worth ensuring this is always a dictionary (or defaulting to {}) to avoid surprises if plugins construct PluginMapper without stream_map_config.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider avoiding re-creating the `funcs` dictionary on every `_eval_stream` call by promoting it to a shared constant or helper so alias evaluation reuses the same function set and avoids per-call allocations.
- Since `_eval_stream` now relies on `self.map_config`, it may be worth ensuring this is always a dictionary (or defaulting to `{}`) to avoid surprises if plugins construct `PluginMapper` without `stream_map_config`.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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

Labels

Deferred Delayed for a future version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants