The initial Access object created previously doesn't decompose some inner elements in the nine fields that comprise an access log line. We'll parse those items separately from the overall decomposition into high-level fields. Doing these parsing operations separately makes each stage of processing simpler. It also allows us to replace one small part of the overall process without breaking the general approach to analyzing logs.
The resulting object from the next stage of parsing will be a NamedTuple subclass, AccessDetails, which wraps the original Access tuple. It will have some additional fields for the details parsed separately:
from typing import NamedTuple, Optionalimport datetimeimport urllib.parse ...