Git Product home page Git Product logo

loguru-mypy's Introduction

loguru-mypy

A fancy plugin to boost up your logging with loguru

GitHub Workflow Status (branch) Open Source Helpers PyPI PyPI - Downloads time tracker Checked with mypy

mypy compatibility

logoru-mypy should be compatible with mypy>=0.770. Currently there is no limit as far as the compatibility can go. If you are interested to see how far loguru_mypy goes you can visit CI/CD action and check its matrix.

Installation

Simply execute:

pip install loguru-mypy

And later on modify your mypy.ini configuration file with

[mypy]
plugins = loguru_mypy

That is all, your code is now ready to be linted.

What is included?

loguru-mypy is obviously a mypy plugin that allows to avoid some of those little runtime trickeries :). Here is a short attempt to list some of those:

Lazy loggers

logger.opt(lazy=True) in facts returns a logger that we call lazy. Lazy loggers accept only typing.Callable[[], t.Any] in place of positional or named arguments. Passing a callable that accepts even a single argument thus results in runtime error. loguru-mypy detects that fact and lets you know before your runtime reaches that portion of a code.

loguru-mypy's People

Contributors

dependabot[bot] avatar github-actions[bot] avatar kornicameister avatar thibaultlemaire avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

loguru-mypy's Issues

Cannot import loguru.Writable

I'm trying to write a simplified type hint for what defines a sink like this:

LoguruSink = Union[TextIO, loguru.Writable, Callable[[loguru.Message], None], logging.Handler]

When I do this, I get the following error:

  File "...myproj/config/logging.py", line 109, in <module>
    LoguruSink = Union[TextIO, loguru.Writable, Callable[[loguru.Message], None], logging.Handler]
AttributeError: module 'loguru' has no attribute 'Writable'

I am doing this because I have a dynamic expression like this to define a sink:

def some_other_sink(message: loguru.Message) -> None:
    ...

default_sink = sys.stdout if settings.is_local_dev else some_other_sink

logger.add(
    default_sink, level=settings.log_level.upper(), format=log_fmt
)

When I lint this with Mypy though, it does not quite see the correct type of default_sink, admittedly a bit of a mypy limitation IMO:

myproj/config/logging.py:129: error: No overload variant of "add" of "Logger"
matches argument types "object", "str", "str", "Dict[str, str]"  [call-overload]
        logger.add(
        ^
myproj/config/logging.py:129: note: Possible overload variants:
myproj/config/logging.py:129: note:     def add(self, sink: Union[TextIO, Writable, Callable[[Message], None], Handler], *, level: Union[str, int] = ..., format: Union[str, Callable[[Record], str]] = ..., filter: Union[str, Callable[[Record], bool], Dict[Optional[str], Union[str, int, bool]], None] = ..., colorize: Optional[bool] = ..., serialize: bool = ..., backtrace: bool = ..., diagnose: bool = ..., enqueue: bool = ..., catch: bool = ...) -> int
myproj/config/logging.py:129: note:     def add(self, sink: Callable[[Message], Awaitable[None]], *, level: Union[str, int] = ..., format: Union[str, Callable[[Record], str]] = ..., filter: Union[str, Callable[[Record], bool], Dict[Optional[str], Union[str, int, bool]], None] = ..., colorize: Optional[bool] = ..., serialize: bool = ..., backtrace: bool = ..., diagnose: bool = ..., enqueue: bool = ..., catch: bool = ..., loop: Optional[AbstractEventLoop] = ...) -> int
myproj/config/logging.py:129: note:     def add(self, sink: Union[str, _PathLike[str]], *, level: Union[str, int] = ..., format: Union[str, Callable[[Record], str]] = ..., filter: Union[str, Callable[[Record], bool], Dict[Optional[str], Union[str, int, bool]], None] = ..., colorize: Optional[bool] = ..., serialize: bool = ..., backtrace: bool = ..., diagnose: bool = ..., enqueue: bool = ..., catch: bool = ..., rotation: Union[str, int, time, timedelta, Callable[[Message, TextIO], bool], None] = ..., retention: Union[str, int, timedelta, Callable[[List[str]], None], None] = ..., compression: Union[str, Callable[[str], None], None] = ..., delay: bool = ..., mode: str = ..., buffering: int = ..., encoding: str = ..., **kwargs: Any) -> int
Found 1 error in 1 file (checked 122 source files)
make: *** [lint] Error 1

But if I type hint default_sink like this, mypy is happy with no errors:

def some_other_sink(message: loguru.Message) -> None:
    ...

default_sink: default_sink: Union[TextIO, Callable[[loguru.Message], None]] = sys.stdout if settings.is_local_dev else some_other_sink

logger.add(
    default_sink, level=settings.log_level.upper(), format=log_fmt
)

Buuuut.... my completionist brain is not happy. So 2 questions:

  1. Is there a reason that loguru.Writable is not exposed?
  2. If you want to keep loguru.Writable un-importable, would you consider exposing a new Sink type that I could use like loguru.Sink?

FWIW I am already using loguru-mypy.

Thanks for the great library! I have dug in pretty deep to it and appreciate the very intentional design decisions that have been made.

False positive on `extra` kwargs

Consider following piece of code:

logger.info(
    'Booting up worker',
    extra={'queue': queue_name},
)

It is obviously flagged as:

[mypy] Not all arguments converted during string formatting  [str-format] [E] 

Ideas for further checks

Here's a list of runtime thingies that caught us when working with loguru ;-).
Thanks to @Delgan support we know two of those being #18 and #7 . I bet there is more. This collective issue aims to bring this to our attention and have them implemented.

ToDo:

  • [ ]

"Internal error" while logging non-string messages

Hi! :)

It seems that some expressions are not recognized and generate an internal error in the plugin. It happens due to the assert here:

assert isinstance(log_msg_expr, StrExpr), type(log_msg_expr)

Actually, the argument does not necessarily needs to be string. Here are some examples that generate an error.

from loguru import logger

logger.info(123)
from loguru import logger

logger.info("foo" + "bar")
from loguru import logger

foo = "bar"
logger.info(foo)
from loguru import logger

logger.info(f"{foobar}")

Which results in:

a.py:3: error: INTERNAL ERROR -- Please try using mypy master on Github:
https://mypy.rtfd.io/en/latest/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 0.790
Traceback (most recent call last):
  File "mypy/checkexpr.py", line 3766, in accept
  File "mypy/checkexpr.py", line 263, in visit_call_expr
  File "mypy/checkexpr.py", line 340, in visit_call_expr_inner
  File "mypy/checkexpr.py", line 817, in check_call_expr_with_callee_type
  File "mypy/checkexpr.py", line 880, in check_call
  File "mypy/checkexpr.py", line 1532, in check_overload_call
  File "mypy/checkexpr.py", line 1675, in infer_overload_return_type
  File "mypy/checkexpr.py", line 876, in check_call
  File "mypy/checkexpr.py", line 988, in check_callable_call
  File "mypy/checkexpr.py", line 725, in apply_function_plugin
  File "/home/delgan/Programmation/loguru-mypy/loguru_mypy/__init__.py", line 99, in _loguru_logger_call_handler
    assert isinstance(log_msg_expr, StrExpr), type(log_msg_expr)
AssertionError: <class 'mypy.nodes.IntExpr'>
a.py:3: : note: use --pdb to drop into pdb

Implement handling of `OpExpr`

Taken from: #49

Code example:

from loguru import logger
logger.info("foo" + "bar")

Expected result:

loguru-mypy correctly detect what will be that final result of said operation and is able to say that this log call is valid.

Add catching of `record=True`

Ref: #6
Ref: python/mypy#9010

This is basically an extension of what was implemented for #6. With record=True user can access special dictionary inside of a template. record=False basically means opposite so user should be told that he is doing the wrong thing for both of cases;

logger.info('{record["msg"]}') # bad
logger.opt(record=True).info('{record["msg"]}') # ok

Figure out possibility of tracking logger objects

Consider two examples

from loguru import logger

def a_foo(a: int) -> int:
    return a

# case 1
logger.debug('Should work because not lazy=True {}', a_foo)
# case 2
logger.opt(lazy=True).debug('Should fail, because callable {} has args', a_foo)

They key difference between these two calls is having one logger defined with lazy=True and the other without it. Loggers that are lazy will in fact try to call a function supplied as arg or kwarg but will succeed only if function has no arguments whatsoever. In the same time loggers that are not lazy will simple print out i.e. str(a_foo) of a function.

That said:

  • case 1 is *not something loguru-mypy should flag
  • case 2 is something loguru-mypy should flag

Support attribute references of positional/keyword arguments with string formatting

from loguru import logger

class Foo:
  bar = "baz"

foo = Foo()

# Both the following are valid syntax, but mypy flags them
logger.debug('The bar is "{0.bar}"', foo)  # note: Expected 0 but found 1 positional arguments for log message
logger.debug('The bar is "{my_foo.bar}"', my_foo=foo)  # error: my_foo.bar keyword argument is missing
❯ pip freeze | grep loguru
loguru==0.5.2
loguru-mypy==0.0.2

Implement handling of `CallExpr`

Taken from: #49

Code examples:

    logger.debug(f'This is a test')           # 1
    logger.trace(f'Call from {__file__}')   # 2

Expected result:
If loguru-mypy detects that it is dealing with CallExpr we should try and figure a final value, i.e. after applying f-string machinery.

Support new mypy 1.0 version specifiers

Error:

[2023-03-06T13:50:00.891Z] Traceback (most recent call last):
[2023-03-06T13:50:00.891Z]   File "/usr/app/.venv/bin/mypy", line 8, in <module>
[2023-03-06T13:50:00.891Z]     sys.exit(console_entry())
[2023-03-06T13:50:00.891Z]   File "/usr/app/.venv/lib/python3.10/site-packages/mypy/__main__.py", line 15, in console_entry
[2023-03-06T13:50:00.891Z]     main()
[2023-03-06T13:50:00.891Z]   File "mypy/main.py", line 95, in main
[2023-03-06T13:50:00.891Z]   File "mypy/main.py", line 174, in run_build
[2023-03-06T13:50:00.891Z]   File "mypy/build.py", line 194, in build
[2023-03-06T13:50:00.891Z]   File "mypy/build.py", line 249, in _build
[2023-03-06T13:50:00.891Z]   File "mypy/build.py", line 510, in load_plugins
[2023-03-06T13:50:00.891Z]   File "mypy/build.py", line 474, in load_plugins_from_config
[2023-03-06T13:50:00.891Z]   File "/usr/app/.venv/lib/python3.10/site-packages/loguru_mypy/__init__.py", line 246, in plugin
[2023-03-06T13:50:00.891Z]     raise UnsupportedMypyVersion(version)
[2023-03-06T13:50:00.891Z] loguru_mypy.UnsupportedMypyVersion: Mypy 1.0.1 is not supported

If I look at the intent from the exception, it looks like this doesn't handle the fact that mypy changed the versioning structure, e.g. 1.0.1:

def plugin(version: str) -> t.Type[LoguruPlugin]:
minor = int(version.split('.')[1].replace('+dev', ''))
if minor < 770:
raise UnsupportedMypyVersion(version)
return LoguruPlugin

So now the minor version is resolved as 0 and this line fails.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.