New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Logging package names, file sizes, and signatures #669
Logging package names, file sizes, and signatures #669
Conversation
7ded9e4
to
f3c3806
Compare
7ac8f47
to
1ac3fe3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great start, thank you! Seeing the implementation raises a few questions which hadn't occurred to me. With that in mind, I'd wait for @sigmavirus24 to give a review before proceeding.
verbose: bool = False, | ||
verbose: int = 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like an API change to a public class, which we've avoided in the time that I've been contributing to Twine. Now, it might be fine, thanks to duck typing, but it makes me wonder:
-
Do we actually need multiple verbosity levels and the complexity that brings? IE, can we leave it as a boolean in this PR, and iterate towards multiple if/when necessary?
-
If we do want multiple levels right away, does this imply the next version is Twine 4.0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we add verbosity
and deprecate verbose
then we could keep verboes around and use verbosity
if it's an int and not None
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could schedule verbose
for removal in 4.0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, that's helpful roadmap, but it still makes me wonder if supporting -vv
and -vvv
at this point provides enough value to Twine's users to warrant the maintenance overhead.
In my opinion, it doesn't, and I'd like to stick with one -v
that remains a bool
, unless @sigmavirus24 and @di feel strongly otherwise.
:param bool verbose: | ||
:param int verbosity: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't match the keyword argument.
@@ -65,9 +67,9 @@ def _make_package( | |||
|
|||
if upload_settings.verbose: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think one of the benefits of using logging
is that we don't need to check the setting each time; we can just log at the appropriate level, and let the logging configuration handle the filtering.
if upload_settings.verbose: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly right, @bhrutledge. Good catch
if verbose: | ||
print("Content received from server:\n{}".format(response.text)) | ||
logger.info("Content received from server:\n{}".format(response.text)) | ||
else: | ||
print("NOTE: Try --verbose to see response content.") | ||
logger.warning("NOTE: Try --verbose to see response content.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same note as above re: not checking verbose
and letting logging
do the work.
logger.info("Content received from server:\n{}".format(response.text))
logger.warning("NOTE: Try --verbose to see response content.")
However, this makes the verbose
argument to the function unnecessary, and I'm not sure how to deal with that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do this then the "NOTE: Try --verbose to see response content" will be printed even if the user is using the --verbose
option as a warning
level log will be logged even if the log level is set to info
. I think leaving verbose
as a parameter for the function makes sense here as it would allow for selectively printing the "NOTE: Try --verbose to see response content" message without having to print conditionally on log level(which may be confusing to read) but that would also work.
_MAX_VERBOSITY = 5 | ||
|
||
_VERBOSITY_TO_LOG_LEVEL = { | ||
0: logging.WARNING, | ||
1: logging.INFO, | ||
2: logging.DEBUG, | ||
3: _MAX_VERBOSITY, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the flake8 implementation, it seems like this is missing a logging.addLevelName
call, as well as an explanation of why _MAX_VERBOSITY
is 5.
However, again I wonder if we really need _MAX_VERBOSITY
(or anything beyond logging.INFO
for now).
args_verbosity = 3 | ||
|
||
log_level = _VERBOSITY_TO_LOG_LEVEL[args_verbosity] | ||
logger = logging.getLogger("LOGGER") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure the logger name should reflect the root package name:
logger = logging.getLogger("LOGGER") | |
logger = logging.getLogger("twine") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct.
handler.setLevel(log_level) | ||
logger.addHandler(handler) | ||
# Setting a level of verbosity for the logger | ||
logger.setLevel(log_level) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we only need one setLevel
call, and the method name is sufficiently descriptive to not need a comment:
handler.setLevel(log_level) | |
logger.addHandler(handler) | |
# Setting a level of verbosity for the logger | |
logger.setLevel(log_level) | |
logger.addHandler(handler) | |
logger.setLevel(log_level) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing this is cribbed liberally from Flake8 and I do think that I needed to call to setLevel
but that may have been a 2/3 compatibility issue. We should test which combination of set levels is actually appropriate rather than guessing
action="store_true", | ||
help="Show verbose output.", | ||
action="count", | ||
help="Show verbose output. -v to -vvv in increasing verbosity.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
help="Show verbose output. -v to -vvv in increasing verbosity.", | |
help="Show more output. Can be used up to 3 times.", |
# Setting up logging before the config is loaded | ||
utils.setup_logging(verbose) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure utils
is the right place for setup_logging
. If --verbose
were a global option (similar to --no-color
), I might suggest moving it __init__.py
ala flake8, or a dedicated twine.logging
module.
However, since --verbose
only applies to commands that use Settings
(i.e. upload
and register
, but not check
), I'd tempted to move the logging configuration to this class, so that this becomes:
# Setting up logging before the config is loaded | |
utils.setup_logging(verbose) | |
self._setup_logging() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't know that it matters if setup_logging
is in utils. I could see users of the the porcelain API wanting to also turn off logging regardless of what's passed into the config and using this to try to do so (and thus wanting to use twine.utils)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, given our suggestion to define a twine.utils.logger
module attribute, I think that might be confusing with the current implementation, which also defines logger
as a local variable in setup_logging
. It looks like flake8 manages this by setting up logging in the package's __init__.py
. @sigmavirus24 what do you think about following that pattern?
# twine/__init__.py
import logging
logger = logging.getLogger(__name__)
def setup_logging(verbose):
# ...
logger.setLevel(log_level)
# twine/settings.py
import twine
class Settings:
def __init__(self, ...):
# ...
self.verbose = verbose
twine.setup_logging(verbose)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bhrutledge I think your example will result in a circular dependency. I think doing
root_logger = logging.getLogger("twine")
is acceptable for altering the logger. I don't think we need to rely too heavily on setup_logging
if we move that into twine
. I'm also worried that setup_logging
being at the top-level module will be confusing for folks looking at the API who aren't familiar with logging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, I tried out the twine/__init__.py
approach (plus some of the other suggested changes) in df46b35, and it doesn't seem to result in a circular dependency.
Given that it's package-level configuration, __init__.py
feels reasonable, but I see how it might not be obvious. That said, I've recently become wary of the utils.py
pattern of unrelated behaviors. Maybe cli.py
would be a more natural location? Or, how about a function in settings.py
?
import logging
def _setup_logging(verbose):
# ...
root_logger = logging.getLogger("twine")
class Settings:
def __init__(self, ...):
# ...
self.verbose = verbose
_setup_logging(verbose)
Here, I made it private, to avoid prematurely adding another public API.
@@ -56,6 +57,7 @@ def _make_package( | |||
) -> package_file.PackageFile: | |||
"""Create and sign a package, based off of filename, signatures and settings.""" | |||
package = package_file.PackageFile.from_filename(filename, upload_settings.comment) | |||
logger = logging.getLogger("LOGGER") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think by changing the setup_logging
to use twine
instead of LOGGER
, you could follow the typical convention of using the module name (i.e. twine.commands.upload
) as the logger name.
logger = logging.getLogger("LOGGER") | |
logger = logging.getLogger(__name__) |
Also, I think this should be at the top of the module, after the imports, so that any function can use logger
without needing to call getLogger
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct @bhrutledge
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To correctly setup the logger(in _setup_logging
), we need the name of the logger to be consistent(so that we have the right handlers and log_level
in place). __name__
wouldn't work as logger name as __name__
would be "twine.settings" in _setup_logging
and twine.commands.upload
in upload.py
so the two files would refer to different loggers. Using "twine" presents no issue. Additionally we need to setup the loggerin
settings.py` because we plan on logging the config file path & config so we cannot wait until the command is called to setup the logger.
@@ -56,6 +57,7 @@ def _make_package( | |||
) -> package_file.PackageFile: | |||
"""Create and sign a package, based off of filename, signatures and settings.""" | |||
package = package_file.PackageFile.from_filename(filename, upload_settings.comment) | |||
logger = logging.getLogger("LOGGER") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct @bhrutledge
# Setting up logging before the config is loaded | ||
utils.setup_logging(verbose) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't know that it matters if setup_logging
is in utils. I could see users of the the porcelain API wanting to also turn off logging regardless of what's passed into the config and using this to try to do so (and thus wanting to use twine.utils)
"""Generate a helpful message based on the response from the repository. | ||
|
||
Raise a custom exception for recognized errors. Otherwise, print the | ||
response content (based on the verbose option) before re-raising the | ||
HTTPError. | ||
""" | ||
logger = logging.getLogger("LOGGER") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again there should be one global logger = logging.getLogger(__name__)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, "global" here means at the top of the module, after the imports, same as https://github.com/pypa/twine/pull/669/files#r446512223.
args_verbosity = 3 | ||
|
||
log_level = _VERBOSITY_TO_LOG_LEVEL[args_verbosity] | ||
logger = logging.getLogger("LOGGER") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct.
handler.setLevel(log_level) | ||
logger.addHandler(handler) | ||
# Setting a level of verbosity for the logger | ||
logger.setLevel(log_level) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing this is cribbed liberally from Flake8 and I do think that I needed to call to setLevel
but that may have been a 2/3 compatibility issue. We should test which combination of set levels is actually appropriate rather than guessing
"""Generate a helpful message based on the response from the repository. | ||
|
||
Raise a custom exception for recognized errors. Otherwise, print the | ||
response content (based on the verbose option) before re-raising the | ||
HTTPError. | ||
""" | ||
logger = logging.getLogger("LOGGER") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, "global" here means at the top of the module, after the imports, same as https://github.com/pypa/twine/pull/669/files#r446512223.
# Setting up logging before the config is loaded | ||
utils.setup_logging(verbose) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, given our suggestion to define a twine.utils.logger
module attribute, I think that might be confusing with the current implementation, which also defines logger
as a local variable in setup_logging
. It looks like flake8 manages this by setting up logging in the package's __init__.py
. @sigmavirus24 what do you think about following that pattern?
# twine/__init__.py
import logging
logger = logging.getLogger(__name__)
def setup_logging(verbose):
# ...
logger.setLevel(log_level)
# twine/settings.py
import twine
class Settings:
def __init__(self, ...):
# ...
self.verbose = verbose
twine.setup_logging(verbose)
verbose: bool = False, | ||
verbose: int = 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, that's helpful roadmap, but it still makes me wonder if supporting -vv
and -vvv
at this point provides enough value to Twine's users to warrant the maintenance overhead.
In my opinion, it doesn't, and I'd like to stick with one -v
that remains a bool
, unless @sigmavirus24 and @di feel strongly otherwise.
Thanks for the feedback @bhrutledge & @sigmavirus24. I will go ahead and make these changes.
Some of the additional information we planned to log(as mentioned in #381) were only going to be outputted at higher verbosity levels( |
We could do either of those things, or we could decide to add support for With that in mind, and given all of the other feedback, I might consider closing this and starting with a fresh branch. But if you prefer to work off this one, that's fine, too. My apologies for not suggesting this sooner. Thanks for sticking with it. |
@bhrutledge YAGNI here isn't helpful. Users already want the ability to increase verbosity, so we do need it. We have an existence proof for users that will use this. I agree smaller PRs are nice, and we can break this work up into smaller PRs if necessary but you haven't defined what "smaller" is. At the moment this is +105,-41 which is plenty reviewable. I'd argue that based on scientific research we could grow this PR an extra few hundred lines without it out-growing "small". This feels like you're moving the goal posts on @VikramJayanthi17 though and that doesn't seem fair to me. |
@sigmavirus24 Some quick thoughts to keep things moving along; I'm opting for direct language here, which I realize can read as defensive/antagonistic, which is not my intent.
I feel bad about that, and have attempted to convey that in my comments. However, I think it's okay to reconsider the desired outcome when the implementation reveals unexpected complexities, which I believe this PR has done. FWIW, I attempted to discuss this in #669 (comment) and #669 (comment), but didn't get the response that I hoped. Maybe we can have a discussion elsewhere about how to resolve these sorts of things.
The ask, as I understand it from #381, is to make
Do you mean verbose output in general? I agree. However, if you mean multiple levels of verbosity, I don't see the proof of that.
That was my intention in #669 (comment):
How could I have been more clear?
I'm more interested in the scope of the changes than the number of lines changed. Again, my concern is that by adding the extra support for |
I closed this PR and opened a new one(with a new branch) here : #670 |
As mentioned in #381 I used the
stdlib
logging library to log packages being uploaded, file sizes, and signatures. These are already logged using the current--verbose
pattern. This PR potentially serves as the start of the transition from the current--verbose
pattern and output to a one based on logging usingstdlib
and has multiple verbosity levels.The logger is setup in
utils.py
and utilized throughout the upload command. The--verbose
field inSettings
is now an integer(not a boolean) from 0(default) to 3. The log level is determined byverbose
's value and the higher the verbosity, the lower(more encompassing) the log level is. I used 1 custom log level(for-vvv
) and 2 existing levels.The additional information we want to output, as outlined in #381(comment) will be added in subsequent PRs after resolving any concerns/issue/concerns/problems in this PR.