Skip to content

Commit 76967ab

Browse files
committed
Public access to zhmcclient.Session password
Note that there are legitimate reasons to get the session password in a Python program, including the zhmcclient package itself. Details: * Changed the protected '_password' attribute in the 'zhmcclient.Session' class to be available via a new public 'password' attribute, which is gettable and settable. * Access via the protected attribute '_password' is still possible, but that attribute has been deprecated and getting or setting it now causes an according Python DeprecationWarning to be issued. * Adjusted the naming rules in the pylint config file to allow for names that begin with two underscores. This was needed to support the internal '__password' attribute. Signed-off-by: Andreas Maier <maiera@de.ibm.com>
1 parent 9e62de5 commit 76967ab

8 files changed

Lines changed: 164 additions & 23 deletions

File tree

‎.pylintrc‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ argument-rgx=([a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{1,29})_{0,1}$
528528
# Note: The expression below allows "lower_case_with_underscores" and
529529
# "mixedCaseWithoutUnderscores"; leading underscores for privacy are allowed.
530530
# Names matching this expression are compatible with PEP-8.
531-
attr-rgx=(_{0,1}([a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{1,39})|__[a-z_]+__)$
531+
attr-rgx=(_{0,2}([a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{1,39})|__[a-z_]+__)$
532532

533533
# Bad variable names which should always be refused, separated by a comma.
534534
bad-names=foo,
@@ -574,7 +574,7 @@ class-attribute-rgx=_{0,2}([A-Z][A-Z0-9_]{0,38}[A-Z0-9]|[a-z][a-z0-9_]{0,38}[a-z
574574
# allows that class names for callable classes can use function name syntax.
575575
# This is not reflected in this regexp; callable classes that follow that
576576
# convention should disable PyLint message C0103.
577-
class-rgx=_{0,1}[A-Z][a-zA-Z0-9]{1,39}$
577+
class-rgx=_{0,2}[A-Z][a-zA-Z0-9]{1,39}$
578578

579579
# Naming style matching correct constant names.
580580
#const-naming-style=UPPER_CASE
@@ -585,7 +585,7 @@ class-rgx=_{0,1}[A-Z][a-zA-Z0-9]{1,39}$
585585
# Note: The expression below allows "UPPER_CASE_WITH_UNDERSCORES"; leading
586586
# underscores for privacy are allowed.
587587
# Names matching this expression are compatible with PEP-8.
588-
const-rgx=(_{0,1}[A-Z][A-Z0-9_]{0,48}[A-Z0-9]|__[a-z_]+__)$
588+
const-rgx=(_{0,2}[A-Z][A-Z0-9_]{0,48}[A-Z0-9]|__[a-z_]+__)$
589589

590590
# Minimum line length for functions/classes that require docstrings, shorter
591591
# ones are exempt.
@@ -601,7 +601,7 @@ docstring-min-length=-1
601601
# "mixedCaseWithoutUnderscores"; leading underscores for privacy as well as
602602
# double trailing underscores are allowed.
603603
# Names matching this expression are compatible with PEP-8.
604-
function-rgx=(_{0,1}([a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{1,39})|__[a-z_]+__)$
604+
function-rgx=(_{0,2}([a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{1,39})|__[a-z_]+__)$
605605
# TODO AM: Verify whether two leading underscores are allowed / supported with name mangling.
606606

607607
# Good variable names which should always be accepted, separated by a comma.
@@ -622,7 +622,7 @@ include-naming-hint=no
622622
# with the set naming style.
623623
# Note: The expression below allows "lower_case_with_underscores" and
624624
# "mixedCaseWithoutUnderscores"; leading underscores for privacy are allowed.
625-
inlinevar-rgx=_{0,1}([a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{0,39})$
625+
inlinevar-rgx=_{0,2}([a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{0,39})$
626626

627627
# Naming style matching correct method names.
628628
#method-naming-style=snake_case
@@ -633,15 +633,15 @@ inlinevar-rgx=_{0,1}([a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{0,39})$
633633
# "mixedCaseWithoutUnderscores"; leading underscores for privacy as well as
634634
# double trailing underscores are allowed.
635635
# Names matching this expression are compatible with PEP-8.
636-
method-rgx=(_{0,1}([a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{1,39})|__[a-z_]+__)$
636+
method-rgx=(_{0,2}([a-z][a-z0-9_]{0,38}[a-z0-9]|[a-z][a-zA-Z0-9]{1,39})|__[a-z_]+__)$
637637

638638
# Naming style matching correct module names.
639639
#module-naming-style=snake_case
640640

641641
# Regular expression matching correct module names. Overrides module-naming-
642642
# style. If left empty, module names will be checked with the set naming style.
643643
# Note: Names matching this expression are compatible with PEP-8.
644-
module-rgx=(_{0,1}[a-z][a-z0-9_]{0,38}[a-z0-9]|__[a-z_]+__)$
644+
module-rgx=(_{0,2}[a-z][a-z0-9_]{0,38}[a-z0-9]|__[a-z_]+__)$
645645

646646
# Colon-delimited sets of names that determine each other's naming style when
647647
# the name regexes allow several styles.
@@ -671,4 +671,4 @@ property-classes=abc.abstractproperty
671671
# other variables; one leading underscore is allowed, and one trailing
672672
# underscore is allowed to avoid conflists with built-in names.
673673
# Names matching this expression are compatible with PEP-8.
674-
variable-rgx=_{0,1}([A-Z]([A-Z0-9_]{0,38}[A-Z0-9])?|[a-z]([a-z0-9_]{0,38}[a-z0-9])?|[a-z]([a-zA-Z0-9]{1,39})?)_{0,1}$
674+
variable-rgx=_{0,2}([A-Z]([A-Z0-9_]{0,38}[A-Z0-9])?|[a-z]([a-z0-9_]{0,38}[a-z0-9])?|[a-z]([a-zA-Z0-9]{1,39})?)_{0,1}$

‎changes/2304.deprecation.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Getting or setting the password in a 'zhmcclient.Session' object via its
2+
protected '_password' attribute is deprecated. Doing that will cause an
3+
according Python 'DeprecationWarning' to be issued. Use the new public
4+
'password' attribute instead. Note that there are legitimate reasons to get
5+
the session password in a Python program, including the zhmcclient package
6+
itself.

‎changes/2304.feature.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Changed the protected '_password' attribute in the 'zhmcclient.Session' class
2+
to be available via a new public 'password' attribute, which is gettable and
3+
settable.
4+
Access via the protected attribute '_password' is still possible, but that
5+
attribute has been deprecated and getting or setting it now causes an according
6+
Python DeprecationWarning to be issued.

‎examples/async_operation_notification.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,8 @@ def main():
9494
print("Creating a notification receiver for topic "
9595
f"{job_topic_name} ...")
9696
try:
97-
# pylint: disable=protected-access
9897
receiver = zhmcclient.NotificationReceiver(
99-
job_topic_name, host, session.userid, session._password,
98+
job_topic_name, host, session.userid, session.password,
10099
verify_cert=session.verify_cert)
101100
except zhmcclient.Error as exc:
102101
print(f"Error: Cannot create notification receiver: {exc}")

‎examples/show_os_messages.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,8 @@ def main():
8484

8585
print(f"Creating a notification receiver for topic {msg_topic} ...")
8686
try:
87-
# pylint: disable=protected-access
8887
receiver = zhmcclient.NotificationReceiver(
89-
msg_topic, host, session.userid, session._password,
88+
msg_topic, host, session.userid, session.password,
9089
verify_cert=session.verify_cert)
9190
except zhmcclient.Error as exc:
9291
print(f"Error: Cannot create notification receiver: {exc}")

‎tests/unit/zhmcclient/test_session.py‎

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import json
2222
import re
2323
from unittest import mock
24+
import warnings
2425
import requests
2526
import requests_mock
2627
import pytest
@@ -90,8 +91,7 @@ def get_password(host, userid):
9091

9192
assert session.host == host
9293
assert session.userid == userid
93-
# pylint: disable=protected-access
94-
assert session._password == password
94+
assert session.password == password
9595
assert session.session_id == session_id
9696
assert session.get_password == get_password
9797
assert session.port == kwargs.get('port', DEFAULT_HMC_PORT)
@@ -130,6 +130,98 @@ def test_session_repr():
130130
repr_str)
131131

132132

133+
def test_password_get_no_deprecation():
134+
"""
135+
Verify that accessing the session password via the new 'password'
136+
attribute issues no deprecation warning.
137+
"""
138+
pw1 = "mypw1"
139+
session = Session("myhost", "myuser", pw1)
140+
141+
with warnings.catch_warnings():
142+
warnings.simplefilter("error", DeprecationWarning)
143+
144+
# The code to be tested
145+
pw = session.password
146+
147+
# Verify that the access works
148+
assert pw == pw1
149+
150+
151+
def test_password_set_no_deprecation():
152+
"""
153+
Verify that setting the session password via the new 'password'
154+
attribute issues no deprecation warning.
155+
"""
156+
pw1 = "mypw1"
157+
pw2 = "mypw2"
158+
session = Session("myhost", "myuser", pw1)
159+
160+
with warnings.catch_warnings():
161+
warnings.simplefilter("error", DeprecationWarning)
162+
163+
# The code to be tested
164+
session.password = pw2
165+
166+
# Verify that the setting works
167+
assert session.password == pw2
168+
169+
170+
def test_password_get_deprecation():
171+
"""
172+
Verify that accessing the session password via the deprecated '_password'
173+
attribute issues a deprecation warning.
174+
"""
175+
pw1 = "mypw1"
176+
session = Session("myhost", "myuser", pw1)
177+
178+
with pytest.warns(DeprecationWarning) as record:
179+
180+
# The code to be tested
181+
pw = session._password # pylint: disable=protected-access
182+
183+
# Verify that the access works
184+
assert pw == pw1
185+
186+
# Verify that one DeprecationWarning was issued
187+
assert len(record) == 1
188+
189+
# Verify the message in the DeprecationWarning
190+
msg = str(record[0].message)
191+
assert re.search(r"protected '_password' attribute is deprecated", msg)
192+
193+
# Verify that stacklevel points at the caller
194+
assert record[0].filename == __file__
195+
196+
197+
def test_password_set_deprecation():
198+
"""
199+
Verify that setting the session password via the deprecated '_password'
200+
attribute issues a deprecation warning.
201+
"""
202+
pw1 = "mypw1"
203+
pw2 = "mypw2"
204+
session = Session("myhost", "myuser", pw1)
205+
206+
with pytest.warns(DeprecationWarning) as record:
207+
208+
# The code to be tested
209+
session._password = pw2 # pylint: disable=protected-access
210+
211+
# Verify that the setting works
212+
assert session.password == pw2
213+
214+
# Verify that one DeprecationWarning was issued
215+
assert len(record) == 1
216+
217+
# Verify the message in the DeprecationWarning
218+
msg = str(record[0].message)
219+
assert re.search(r"protected '_password' attribute is deprecated", msg)
220+
221+
# Verify that stacklevel points at the caller
222+
assert record[0].filename == __file__
223+
224+
133225
@pytest.mark.parametrize(
134226
"host, userid, password, use_get_password, exp_exc", [
135227
('fake-host', None, None, False, ClientAuthError),
@@ -187,8 +279,7 @@ def test_session_logon(
187279
if get_password:
188280
if password is None:
189281
get_password.assert_called_with(host, userid)
190-
# pylint: disable=protected-access
191-
assert session._password == get_password.return_value
282+
assert session.password == get_password.return_value
192283
else:
193284
get_password.assert_not_called()
194285

‎zhmcclient/_auto_updater.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,7 @@ def open(self):
205205
try:
206206
# wait=True causes the connection to be retried for some times
207207
# and finally raises stomp.ConnectFailedException
208-
# pylint: disable=protected-access
209-
self._conn.connect(self._session.userid, self._session._password,
208+
self._conn.connect(self._session.userid, self._session.password,
210209
wait=True)
211210
except (self._stomp.exception.StompException, OSError) as exc:
212211
msg = f"STOMP connection failed: {exc.__class__.__name__}: {exc}"

‎zhmcclient/_session.py‎

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import time
2222
import re
2323
import logging
24+
import warnings
2425
from copy import copy
2526
from collections.abc import Iterable
2627
import requests
@@ -429,7 +430,7 @@ def __init__(self, host, userid=None, password=None, session_id=None,
429430
assert len(self._hosts) >= 1
430431
self._port = port
431432
self._userid = userid
432-
self._password = password
433+
self.__password = password
433434
self._verify_cert = verify_cert
434435
self._get_password = get_password
435436
self._retry_timeout_config = self.default_rt_config.override_with(
@@ -466,7 +467,7 @@ def __repr__(self):
466467
"""
467468
headers = _headers_for_logging(self.headers)
468469
blanked_session_id = BLANKED_OUT_STRING if self._session_id else None
469-
blanked_password = BLANKED_OUT_STRING if self._password else None
470+
blanked_password = BLANKED_OUT_STRING if self.__password else None
470471
ret = (
471472
f"{repr_obj_id(self)} (\n"
472473
f" _hosts={self._hosts!r},\n"
@@ -544,6 +545,46 @@ def userid(self):
544545
"""
545546
return self._userid
546547

548+
@property
549+
def password(self):
550+
"""
551+
:term:`string`: HMC password that is used for this session.
552+
553+
If `None`, only operations that do not require authentication, can be
554+
performed.
555+
556+
This property is gettable and settable.
557+
"""
558+
return self.__password
559+
560+
@password.setter
561+
def password(self, value):
562+
# We do not want documentation generated for the setter, since it is
563+
# described in the getter method.
564+
self.__password = value
565+
566+
@property
567+
def _password(self):
568+
# For backwards compatibility, we still support access via the
569+
# protected attribute '_password', but don't want to have that show up
570+
# in the documentation.
571+
warnings.warn(
572+
"Accessing the zhmcclient.Session password via the protected "
573+
"'_password' attribute is deprecated; Use the public 'password' "
574+
"attribute instead", DeprecationWarning, stacklevel=2)
575+
return self.__password
576+
577+
@_password.setter
578+
def _password(self, value):
579+
# For backwards compatibility, we still support modification via the
580+
# protected attribute '_password', but don't want to have that show up
581+
# in the documentation.
582+
warnings.warn(
583+
"Setting the zhmcclient.Session password via the protected "
584+
"'_password' attribute is deprecated; Use the public 'password' "
585+
"attribute instead", DeprecationWarning, stacklevel=2)
586+
self.__password = value
587+
547588
@property
548589
def verify_cert(self):
549590
"""
@@ -828,9 +869,9 @@ def _do_logon(self):
828869
if self._userid is None:
829870
raise ClientAuthError("Userid is not provided.")
830871

831-
if self._password is None:
872+
if self.__password is None:
832873
if self._get_password:
833-
self._password = \
874+
self.__password = \
834875
self._get_password(self._actual_host, self._userid)
835876
elif self._session_id:
836877
raise ClientAuthError(
@@ -843,7 +884,7 @@ def _do_logon(self):
843884
logon_uri = '/api/sessions'
844885
logon_body = {
845886
'userid': self._userid,
846-
'password': self._password
887+
'password': self.__password
847888
}
848889
self._headers.pop('X-API-Session', None) # Just in case
849890
self._session = self._new_session(self.retry_timeout_config)

0 commit comments

Comments
 (0)