How to set log level with structlog when not using event parameter? - python-3.x

The idiomatic way (I think) to create a logger in structlog that only prints up to a certain log level is to use the following:
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
This works fine, but it breaks with the following pattern:
l = logger.bind(event="get_tar", key=value)
l.info(status="download_start")
buf = f.read()
l.info(status="download_finish")
by default, when using the logfmt format -- structlog will print the "message" as the event key, so I just like to set it directly.
Anyways, this breaks though b/c under the hood make_filtering_bound_logger calls this:
def make_method(level: int) -> Callable[..., Any]:
if level < min_level:
return _nop
name = _LEVEL_TO_NAME[level]
def meth(self: Any, event: str, **kw: Any) -> Any:
return self._proxy_to_logger(name, event, **kw)
meth.__name__ = name
return meth
which requires an event kwarg to exist. Is there a workaround?

event is the only (reasonable :)) key that cannot be bound – it’s always the log message. That’s not a matter of make_… but all structlog internals.
You can get something similar-ish by renaming a key-value pair using the EventRenamer processor.
See also https://github.com/hynek/structlog/issues/35
It’s good you brought this up, I’m currently rewriting the docs and am looking for common recipes.

Related

Simplifying Init Method Python

Is there a better way of doing this?
def __init__(self,**kwargs):
self.ServiceNo = kwargs["ServiceNo"]
self.Operator = kwargs["Operator"]
self.NextBus = kwargs["NextBus"]
self.NextBus2 = kwargs["NextBus2"]
self.NextBus3 = kwargs["NextBus3"]
The attributes (ServiceNo,Operator,...) always exist
That depends on what you mean by "simpler".
For example, is what you wrote simpler than what I would write, namely
def __init__(self,ServiceNo, Operator, NextBus, NextBus2, NextBus3):
self.ServiceNo = ServiceNo
self.Operator = Operator
self.NextBus = NextBus
self.NextBus2 = NextBus2
self.NextBus3 = NextBus3
True, I've repeated each attribute name an additional time, but I've made it much clearer which arguments are legal for __init__. The caller is not free to add any additional keyword argument they like, only to see it silently ignored.
Of course, there's a lot of boilerplate here; that's something a dataclass can address:
from dataclasses import dataclass
#dataclass
class Foo:
ServiceNo: int
Operator: str
NextBus: Bus
NextBus2: Bus
NextBus3: Bus
(Adjust the types as necessary.)
Now each attribute is mentioned once, and you get the __init__ method shown above for free.
Better how? You don’t really describe what problem you’re trying to solve.
If it’s error handling, you can use the dictionary .get() method in the event that key doesn’t exist.
If you just want a more succinct way of initializing variables, you could remove the ** and have the dictionary as a variable itself, then use it elsewhere in your code, but that depends on what your other methods are doing.
A hacky solution available since the attributes and the argument names match exactly is to directly copy from the kwargs dict to the instance's dict, then check that you got all the keys you expected, e.g.:
def __init__(self,**kwargs):
vars(self).update(kwargs)
if vars(self).keys() != {"ServiceNo", "Operator", "NextBus", "NextBus2", "NextBus3"}:
raise TypeError(f"{type(self).__name__} missing required arguments")
I don't recommend this; chepner's options are all superior to this sort of hackery, and they're more reliable (for example, this solution fails if you use __slots__ to prevent autovivication of attributes, as the instance won't having a backing dict you can pull with vars).

Python function return type hints with fixed dictionary keys

I have a function which returns a dictionary that always has the same keys (send over network and "stringified" with json). And basically my function looks like this:
def getTemps(self) -> dict:
"""
get room and cpu temperature in °C as well as humidity in %
"""
# send temperature request to server
msg = {'type':'req', 'reqType':'temps'}
self.send(msg)
res = self.recieve() # get response
return res
and the dictionary you get from this function always looks something like that:
{'Room':float, 'CPU':float, 'hum':float}
so I was wondering if there was a way to specify the return type of the function so you know what keys the dictionary has:
def getTemps(self) -> Dict['Room':float, 'CPU':float, 'hum':float]
but that didn't work as it only showed Dict[slice, slice, slice] when hovering over the function (I am using vscode).
I don't think this is something that is very useful, but something that makes your code look better and also be easier to use for someone else. So if someone knows if this is possible and how, I would be very grateful to get a response!
consider this:
class Message:
def __init__(self,room,cpu,hum):
self.Room = room
self.CPU = cpu
self.hum = hum
and return Message(room,cpu,hum) or define a method to convert this to dict in the class if necessary.
This may be the only method.
Update: This is neither the only nor the best method, see comments.

QCheckbox issue [duplicate]

I am struggling to get this working.
I tried to transpose from a c++ post into python with no joy:
QMessageBox with a "Do not show this again" checkbox
my rough code goes like:
from PyQt5 import QtWidgets as qtw
...
mb = qtw.QMessageBox
cb = qtw.QCheckBox
# following 3 lines to get over runtime errors
# trying to pass the types it was asking for
# and surely messing up
mb.setCheckBox(mb(), cb())
cb.setText(cb(), "Don't show this message again")
cb.show(cb())
ret = mb.question(self,
'Close application',
'Do you really want to quit?',
mb.Yes | mb.No )
if ret == mb.No:
return
self.close()
the above executes with no errors but the checkbox ain't showing (the message box does).
consider that I am genetically stupid... and slow, very slow.
so please go easy on my learning curve
When trying to "port" code, it's important to know the basis of the source language and have a deeper knowledge of the target.
For instance, taking the first lines of your code and the referenced question:
QCheckBox *cb = new QCheckBox("Okay I understand");
The line above in C++ means that a new object (cb) of type QCheckBox is being created, and it's assigned the result of QCheckBox(...), which returns an instance of that class. To clarify how objects are declared, here's how a simple integer variable is created:
int mynumber = 10
This is because C++, like many languages, requires the object type for its declaration.
In Python, which is a dynamic typing language, this is not required (but it is possible since Python 3.6), but you still need to create the instance, and this is achieved by using the parentheses on the class (which results in calling it and causes both calling __new__ and then __init__). The first two lines of your code then should be:
mb = qtw.QMessageBox()
cb = qtw.QCheckBox()
Then, the problem is that you're calling the other methods with new instances of the above classes everytime.
An instance method (such as setCheckBox) is implicitly called with the instance as first argument, commonly known as self.
checkboxInstance = QCheckBox()
checkboxInstance.setText('My checkbox')
# is actually the result of:
QCheckBox.setText(checkboxInstance, 'My checkbox')
The last line means, more or less: call the setText function of the class QCheckBox, using the instance and the text as its arguments.
In fact, if QCheckBox was an actual python class, setText() would look like this:
class QCheckBox:
def setText(self, text):
self.text = text
When you did cb = qtw.QCheckBox you only created another reference to the class, and everytime you do cb() you create a new instance; the same happens for mb, since you created another reference to the message box class.
The following line:
mb.setCheckBox(mb(), cb())
is the same as:
QMessageBox.setCheckBox(QMessageBox(), QCheckBox())
Since you're creating new instances every time, the result is absolutely nothing: there's no reference to the new instances, and they will get immediately discarded ("garbage collected", aka, deleted) after that line is processed.
This is how the above should actually be done:
mb = qtw.QMessageBox()
cb = qtw.QCheckBox()
mb.setCheckBox(cb)
cb.setText("Don't show this message again")
Now, there's a fundamental flaw in your code: question() is a static method (actually, for Python, it's more of a class method). Static and class methods are functions that don't act on an instance, but only on/for a class. Static methods of QMessageBox like question or warning create a new instance of QMessageBox using the provided arguments, so everything you've done before on the instance you created is completely ignored.
These methods are convenience functions that allow simple creation of message boxes without the need to write too much code. Since those methods only allow customization based on their arguments (which don't include adding a check box), you obviously cannot use them, and you must code what they do "under the hood" explicitly.
Here is how the final code should look:
# create the dialog with a parent, which will make it *modal*
mb = qtw.QMessageBox(self)
mb.setWindowTitle('Close application')
mb.setText('Do you really want to quit?')
# you can set the text on a checkbox directly from its constructor
cb = qtw.QCheckBox("Don't show this message again")
mb.setCheckBox(cb)
mb.setStandardButtons(mb.Yes | mb.No)
ret = mb.exec_()
# call some function that stores the checkbox state
self.storeCloseWarning(cb.isChecked())
if ret == mb.No:
return
self.close()

python3/logging: Optionally write to more than one stream

I'm successfully using the logging module in my python3 program to send log messages to a log file, for example, /var/log/myprogram.log. In certain cases, I want a subset of those messages to also go to stdout, with them formatted through my logging.Logger instance in the same way that they are formatted when they go to the log file.
Assuming that my logger instance is called loginstance, I'd like to put some sort of wrapper around loginstance.log(level, msg) to let me choose whether the message only goes to /var/log/myprogram.log, or whether it goes there and also to stdout, as follows:
# Assume `loginstance` has already been instantiated
# as a global, and that it knows to send logging info
# to `/var/log/myprogram.log` by default.
def mylogger(level, msg, with_stdout=False):
if with_stdout:
# Somehow send `msg` through `loginstance` so
# that it goes BOTH to `/var/log/myprogram.log`
# AND to `stdout`, with identical formatting.
else:
# Send only to `/var/log/myprogram.log` by default.
loginstance.log(level, msg)
I'd like to manage this with one, single logging.Logger instance, so that if I want to change the format or other logging behavior, I only have to do this in one place.
I'm guessing that this involves subclassing logging.Logger and/or logging.Formatter, but I haven't figured out how to do this.
Thank you in advance for any suggestions.
I figured out how to do it. It simply requires that I use a FileHandler subclass and pass an extra argument to log() ...
class MyFileHandler(logging.FileHandler):
def emit(self, record):
super().emit(record)
also_use_stdout = getattr(record, 'also_use_stdout', False)
if also_use_stdout:
savestream = self.stream
self.stream = sys.stdout
try:
super().emit(record)
finally:
self.stream = savestream
When instantiating my logger, I do this ...
logger = logging.getLogger('myprogram')
logger.addHandler(MyFileHandler('/var/log/myprogram.log'))
Then, the mylogger function that I described above will look like this:
def mylogger(level, msg, with_stdout=False):
loginstance.log(level, msg, extra={'also_use_stdout': with_stdout})
This works because anything passed to the log function within the optional extra dictionary becomes an attribute of the record object that ultimately gets passed to emit.

Creating custom slots and signals old style

I'm trying to create my own custom signal but for whatever reason it's not going into the function. Any ideas where I went wrong?
def __init__(self):
...
self.connect( self, QtCore.SIGNAL( 'dagEvent(type, *args)' ), self.OnDagEvent)
def someFunc(self, ...):
...
self.emit(QtCore.SIGNAL('dagEvent()'), type, *args)
def OnDagEvent( self, eventType, eventData ):
print 'Test'
The problem is with the way you are creating the signature for you custom signal. There are a few ways you are allow to do it, but the way you have it now isn't one of them.
When you define your connection, the way you are doing it should be causing an error to be raised:
# not a valid type of signature
QtCore.SIGNAL( 'dagEvent(type, *args)' )
And even if that were allowed to be created, when you emit later, you are not referencing the same signature anyways:
# if it were even allowed, would have to be: dagEvent(type, *args)
self.emit(QtCore.SIGNAL('dagEvent()'), type, *args)
Old-style Signal and Slot Support
The easiest way to create a custom signal from PyQt is to simply use the callable name only:
self.connect(self, QtCore.SIGNAL('dagEvent'), self.OnDagEvent)
...
# "type" is a builtin. I renamed it to type_
self.emit(QtCore.SIGNAL('dagEvent'), type_, *args)
This approach does not care what args you decide to pass it. You can pass anything you want.
If you want to specifically control the signature, you can define builtin types:
self.connect(self, QtCore.SIGNAL('dagEvent(int,str,int)'), self.OnDagEvent)
...
self.emit(QtCore.SIGNAL('dagEvent(int,str,int)'), i1, s2, i3)
If you fail to use the right signature in the emit, it will not be called, and passing the wrong types when emitting will raise an error.
Now if you want to somewhat define a signature, but not limit it to any basic type, and allow any python object, you can do this:
self.connect(self, QtCore.SIGNAL('dagEvent(PyQt_PyObject)'), self.OnDagEvent)
...
self.emit(QtCore.SIGNAL('dagEvent(PyQt_PyObject)'), foo)
This will allow any single python object to be passed, but specifically says it expects 1 argument.

Resources