-
Notifications
You must be signed in to change notification settings - Fork 96
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
Tidying __init__ #261
base: master
Are you sure you want to change the base?
Tidying __init__ #261
Conversation
Codecov Report
@@ Coverage Diff @@
## master #261 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 7 7
Lines 1706 1706
Branches 310 310
=========================================
Hits 1706 1706 📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
Now that I think of it ... this should be merged ONLY after #260 has been merged. That way, I can test for the behavior of the library when tls_context is being replaced totally. (Current tests are replacing to/from None, and replacing with a new SSLContext object but using the same cert-key pair.) |
94b24db
to
6eaef4e
Compare
e7eff56
to
f146e6b
Compare
Nice. I'm digging at least the idea :) |
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.
Some comments for possibly interesting decisions
# Exposed states | ||
session: Optional[Session] = None | ||
envelope: Optional[Envelope] = None | ||
|
||
# Protected states | ||
_loop: asyncio.AbstractEventLoop = None | ||
_event_handler: Any = None | ||
_smtp_methods: Dict[str, SmtpMethodType] = {} | ||
_handle_hooks: Dict[str, Callable] = None | ||
_ehlo_hook_ver: Optional[str] = None | ||
_handler_coroutine: Optional[asyncio.Task] = None | ||
_timeout_handle: Optional[asyncio.TimerHandle] = None | ||
|
||
_tls_context: Optional[Union[ssl.SSLContext, _Missing]] = MISSING | ||
_req_starttls: bool = False | ||
_tls_handshake_okay: bool = True | ||
_tls_protocol: Optional[sslproto.SSLProtocol] = None | ||
_original_transport: Optional[asyncio.BaseTransport] = None | ||
|
||
_auth_mechs: Dict[str, _AuthMechAttr] = {} | ||
_auth_excludes: Optional[Iterable[str]] = None | ||
_authenticator: Optional[AuthenticatorType] = None | ||
_auth_callback: Optional[AuthCallbackType] = 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.
Extracting all these states greatly simplifies the innards of __init__
, making it much more readable and thus more maintainable.
@property | ||
def loop(self) -> asyncio.AbstractEventLoop: | ||
return self._loop |
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.
self.loop
is now a getter-only property to prevent accidental replacement.
env = dict(os.environ) | ||
env["PYTHON_EXE"] = str(sys.executable) |
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.
Also record the Python executable in the env var dump
def docs_clean(verbose=False): | ||
"""Cleanup build/ to force sphinx-build to rebuild""" | ||
buildpath = Path("build") | ||
if not buildpath.exists(): | ||
print("Docs already cleaned.") | ||
return | ||
print("Removing build/ ...", end="") | ||
deldir(buildpath, verbose) | ||
if verbose: | ||
print(flush=True) |
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.
Quick housekeeping to regen docs
if not self._auth_mechs: | ||
self._auth_mechs = {} |
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.
It is important that we assign a new dict to this attrib, to prevent 'contamination' between SMTP class instantiations. (Which apparently happens for every new connection to the listening port -- something I just figured out after all these months 😅)
self._smtp_methods: Dict[str, Any] = { | ||
m.replace("smtp_", ""): getattr(self, m) | ||
for m in dir(self) | ||
if m.startswith("smtp_") | ||
} |
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.
Moving method scanning outside of init speeds up class instantiation.
# Get hooks & methods to significantly speedup getattr's | ||
self._auth_methods: Dict[str, _AuthMechAttr] = { | ||
getattr( | ||
mfunc, "__auth_mechanism_name__", | ||
mname.replace("auth_", "").replace("__", "-") | ||
): _AuthMechAttr(mfunc, obj is self) | ||
for obj in (self, handler) | ||
for mname, mfunc in inspect.getmembers(obj) | ||
if mname.startswith("auth_") | ||
} |
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.
Moving method scanning outside of init, especially for a scanning as complex as this, speeds up SMTP instantiation.
As soon as test passes on my testing systems, I'm undrafting this and let this marinate. I might not be able to do code changes for several days ahead, due to me transitioning from "being employed" to "being a free agent" and needing to acquire a new laptop. I reckon up to a week of radio silence due to the Easter holidays. Hopefully not as long as that, depending on how soon the store can fulfill my order. |
A side effect is that SMTP instatiation is now faster 😉 |
@pepoluan I'm going to plan on taking a closer look at this one this week, too. Looks like there are a couple of conflicts, if you're able to tackle those. |
Now the common *Controller classes (excluding Base*Controller classes) are all based on mixins.
Too many pragmas to add.
Solve Issue#257
"Live properties" means properties that, when changed, will immediately be effective. A side benefit is that we can move validation/parsing logic out of the oversized __init__. The following attributes have been converted: * `event_handler` -- now on-the-fly event handler replacement is possible. Automatically refreshes methods_auth. * `tls_context` -- now on-the-fly TLS Context replacement (or disablement) is possible. * `require_starttls` -- now automatically adjusts to whether tls_context is set or not. Setting this property only sets the "backing" bool value. * `methods_auth` -- read-only property containing authentication methods, automatically populated from handler.
* Many internal states moved out of __init__ * Type hint for _smtp_methods now more specific: * Define type alias "SmtpMethodType" * Define Protocol "HasSMTPAttribs", used by SmtpMethod * Access to _smtp_methods now via property Slightly slower, but this makes __init__ tidier * Simplify Call Limit mechanism * Now uses only one attrib: _call_limit * Change loop into "read only" property
Fixed the conflicts, rebase, and let's see... |
Oh gosh it blew up! LOL |
Heh seriously at this point I'll just close this PR, but grab the ideas and make a new PR based on the latest master. There are just too many incremental changes since this PR was created, the proposed changes in this PR makes things go awry. |
Depends on #256 , #259 , and #260 ; please merge them first.All deps have been merged. We can go ahead with this one.What do these changes do?
The
__init__
method is very unwieldy. This PR makes it simpler by extracting some logic into properties.This led to several improvements:
event_handler
can be replaced "on-the-fly"tls_context
can be replaced "on-the-fly" and will automagically changerequire_tls
accordinglyAre there changes in behavior for the user?
None.
Users have to be aware though that some properties are more 'noisy' in logging.
Related issue number
Inspired partially by #253
Checklist
NEWS.rst
file