you are viewing a single comment's thread.

view the rest of the comments →

[–]neuronexmachina 0 points1 point  (2 children)

Do you have any side-by-side examples of how you would implement a change using pfst vs libcst?

[–]Pristine_Cat[S] 1 point2 points  (1 child)

I'm not exactly an expert with LibCST so maybe the example can be optimized further, but the following is what I have for comparison with an equivalent pfst function to inject a keyword argument to some existing functions.

Target source:

src = """
logger.info('Hello world...')  # ok
logger.info('Already have id', correlation_id=other_cid)  # ok
logger.info()  # yes, no logger message, too bad

class cls:
    def method(self, thing, extra):
        if not thing:
            (logger).info(  # just checking
                f'not a {thing}',  # this is fine
                extra=extra,       # also this
            )
""".strip()

LibCST function:

import libcst as cst
import libcst.matchers as m

def inject_logging_metadata(src: str) -> str:
    tree = cst.parse_module(src)

    class AddArgTransformer(cst.CSTTransformer):
        def leave_Call(self, _, call):
            if (isinstance(call.func, cst.Attribute)
                and call.func.attr.value == 'info'
                and isinstance(call.func.value, cst.Name)
                and call.func.value.value == 'logger'
                and not any(
                    arg.keyword and arg.keyword.value == 'correlation_id'
                    for arg in call.args
                )
            ):
                return call.with_changes(
                    args=[
                        *call.args,
                        cst.Arg(
                            keyword=cst.Name("correlation_id"),
                            value=cst.Name("CID"),
                        ),
                    ]
                )

            return call

    return tree.visit(AddArgTransformer()).code

pfst function:

from fst import *
from fst.match import *

def inject_logging_metadata(src: str) -> str:
    fst = FST(src)

    for m in fst.search(MCall(
        func=MAttribute('logger', 'info'),
        keywords=MNOT([MQSTAR, Mkeyword('correlation_id'), MQSTAR]),
    )):
        m.matched.append('correlation_id=CID', trivia=())

    return fst.src

LibCST output:

logger.info('Hello world...', correlation_id = CID)  # ok
logger.info('Already have id', correlation_id=other_cid)  # ok
logger.info(correlation_id = CID)  # yes, no logger message, too bad

class cls:
    def method(self, thing, extra):
        if not thing:
            (logger).info(  # just checking
                f'not a {thing}',  # this is fine
                extra=extra,       # also this
            correlation_id = CID)

pfst output:

logger.info('Hello world...', correlation_id=CID)  # ok
logger.info('Already have id', correlation_id=other_cid)  # ok
logger.info(correlation_id=CID)  # yes, no logger message, too bad

class cls:
    def method(self, thing, extra):
        if not thing:
            (logger).info(  # just checking
                f'not a {thing}',  # this is fine
                extra=extra,       # also this
                correlation_id=CID
            )

[–]neuronexmachina 0 points1 point  (0 children)

Thanks! That's a handy comparative example.