~dricottone/digestion

de6aac89edaeb2ceda7412983ac035e0f9912a25 — Dominic Ricottone 4 years ago
Initial commit
A  => ingest/__init__.py +0 -0
A  => ingest/__main__.py +11 -0
@@ 1,11 @@
#!/usr/bin/env python3

from . import parse

for msg in parse.split_messages(parse.read_input())[1:]:
    try:
        parse.parse_message(msg)
    except:
        raise



A  => ingest/__pycache__/__init__.cpython-38.pyc +0 -0
A  => ingest/__pycache__/__main__.cpython-38.pyc +0 -0
A  => ingest/__pycache__/message.cpython-38.pyc +0 -0
A  => ingest/__pycache__/parse.cpython-38.pyc +0 -0
A  => ingest/message.py +201 -0
@@ 1,201 @@
#!/usr/bin/env python3

"""The message object and API."""

from typing import Optional

class Message(object):
    """Container for a message and metadata."""
    def __init__(
        self,
        *,
        hdr_subject: Optional[str] = None,
        hdr_date: Optional[str] = None,
        hdr_from: Optional[str] = None,
        hdr_to: Optional[str] = None,
        hdr_cc: Optional[str] = None,
        hdr_message_id: Optional[str] = None,
        content_type: Optional[str] = None,
        content: Optional[str] = None,
    ) -> None:
        self._subject = hdr_subject
        self._date = hdr_date
        self._from = hdr_from
        self._to = hdr_to
        self._cc = hdr_cc
        self._message_id = hdr_message_id
        self._content_type = content_type
        self._content = content
        self._last_hdr = None

    def __str__(self) -> str:
        return (
            f"Subject: {self._subject}\n"
            f"Date: {self._date}\n"
            f"To: {self._to}\n"
            f"From: {self._from}\n"
            f"Cc: {self._cc}\n"
            f"Message-ID: {self._message_id}\n"
            f"Content-Type: {self._content_type}\n"
        )

    @property
    def hdr_subject(self) -> str:
        if self._subject is not None:
            return self._subject
        else:
            raise ValueError("no header `subject' set") from None
    @hdr_subject.setter
    def hdr_subject(self, value: str):
        if self._subject is None:
            self._subject = value
            self._last_hdr = "_subject"
        else:
            raise ValueError("header `subject' already set") from None

    @property
    def hdr_date(self) -> str:
        if self._date is not None:
            return self._date
        else:
            raise ValueError("no header `date' set") from None
    @hdr_date.setter
    def hdr_date(self, value: str):
        if self._date is None:
            self._date = value
            self._last_hdr = "_date"
        else:
            raise ValueError("header `date' already set") from None

    @property
    def hdr_from(self) -> str:
        if self._from is not None:
            return self._from
        else:
            raise ValueError("no header `from' set") from None
    @hdr_from.setter
    def hdr_from(self, value: str):
        if self._from is None:
            self._from = value
            self._last_hdr = "_from"
        else:
            raise ValueError("header `from' already set") from None

    @property
    def hdr_to(self) -> str:
        if self._to is not None:
            return self._to
        else:
            raise ValueError("no header `to' set") from None
    @hdr_to.setter
    def hdr_to(self, value: str):
        if self._to is None:
            self._to = value
            self._last_hdr = "_to"
        else:
            raise ValueError("header `to' already set") from None

    @property
    def hdr_cc(self) -> str:
        if self._cc is not None:
            return self._cc
        else:
            raise ValueError("no header `cc' set") from None
    @hdr_cc.setter
    def hdr_cc(self, value: str):
        if self._cc is None:
            self._cc = value
            self._last_hdr = "_cc"
        else:
            raise ValueError("header `cc' already set") from None

    @property
    def hdr_message_id(self) -> str:
        if self._message_id is not None:
            return self._message_id
        else:
            raise ValueError("no header `message_id' set") from None
    @hdr_message_id.setter
    def hdr_message_id(self, value: str):
        if self._message_id is None:
            self._message_id = value
            self._last_hdr = "_message_id"
        else:
            raise ValueError("header `message_id' already set") from None

    @property
    def content_type(self) -> str:
        if self._content_type is not None:
            return self._content_type
        else:
            raise ValueError("no `content_type' set") from None
    @content_type.setter
    def content_type(self, value: str):
        if self._content_type is None:
            self._content_type = value
            self._last_hdr = "_content_type"
        else:
            raise ValueError("`content_type' already set") from None

    def append_last(self, value: str):
        if self._last_hdr is not None:
            old = getattr(self, self._last_hdr)
            try:
                new = old + value
            except:
                # test for bad encoding
                raise
            setattr(self, self._last_hdr, new)
        else:
            raise ValueError("no header set") from None

    def into_multipart(self):
        return MultipartMessage(
            hdr_subject=self._subject,
            hdr_date=self._date,
            hdr_from=self._from,
            hdr_to=self._to,
            hdr_cc=self._cc,
            hdr_message_id=self._message_id,
            content_type=self._content_type,
            content=self._content,
        )

class MultipartMessage(Message):
    """Container for a multi-part message and metadata."""
    def __init__(
        self,
        *,
        hdr_subject: Optional[str] = None,
        hdr_date: Optional[str] = None,
        hdr_from: Optional[str] = None,
        hdr_to: Optional[str] = None,
        hdr_cc: Optional[str] = None,
        hdr_message_id: Optional[str] = None,
        content_type: Optional[str] = None,
        content: Optional[str] = None,
    ) -> None:
        self._subject = hdr_subject
        self._date = hdr_date
        self._from = hdr_from
        self._to = hdr_to
        self._cc = hdr_cc
        self._message_id = hdr_message_id
        self._content_type = content_type
        self._content = content
        self._last_hdr = None

        self._parts = list()

    def __str__(self) -> str:
        return (
            f"Subject: {self._subject}\n"
            f"Date: {self._date}\n"
            f"To: {self._to}\n"
            f"From: {self._from}\n"
            f"Cc: {self._cc}\n"
            f"Message-ID: {self._message_id}\n"
            f"Content-Type: {self._content_type}\n"
            f"Parts: {len(self._parts)}\n"
        )


A  => ingest/parse.py +155 -0
@@ 1,155 @@
#!/usr/bin/env python3

import sys
import re
from typing import List

from . import message

RE_MESSAGE_BREAK = re.compile(r"^-* *$")
RE_HEADER_LINE =   re.compile(r"^(?:Date|From|Subject|To|Cc|Message-ID|Content-Type):")
RE_BLANK_LINE =    re.compile(r"^ *$")
RE_SUBJECT_LINE =  re.compile(r"^Subject: *(.*) *$")
RE_DATE_LINE =     re.compile(r"^Date: *(.*) *$")
RE_FROM_LINE =     re.compile(r"^From: *(.*) *$")
RE_TO_LINE =       re.compile(r"^To: *(.*) *$")
RE_CC_LINE =       re.compile(r"^Cc: *(.*) *$")
RE_ID_LINE =       re.compile(r"^Message-ID: *(.*) *$")
RE_CONTENT_LINE =  re.compile(r"^Content-Type: *(.*) *$")
RE_RUNON =         re.compile(r"^[ \t]+(.*) *$")
RE_BOUNDARY =      re.compile(r'.*boundary="(.*)".*')

def split_messages(blob: List[bytes]) -> List[List[bytes]]:
    """Split a blob into messages."""
    message_breaks = list()
    message_start = 0
    messages = list()

    # Find probable message breaks
    for index, bytes_line in enumerate(blob):
        try:
            line = str(bytes_line)
        except:
            # test for bad encodings
            raise
        if RE_MESSAGE_BREAK.match(line):
            message_breaks.append(index)

    # Validate message breaks and copy text into split messages
    # NOTE: message breaks are validated by checking for...
    #  1) A blank line following the message break
    #  2) A header line following the blank line
    for index in message_breaks:
        try:
            line1 = str(blob[index+1])
            line2 = str(blob[index+2])
        except:
            # test for bad encodings
            raise

        # If fails validation, skip to next probable break
        if not RE_BLANK_LINE.match(line1):
            continue
        elif not RE_HEADER_LINE.match(line2):
            continue

        # Message spans from known start to line before break
        messages.append(blob[message_start:index - 1])

        # Next message starts on first header line
        message_start = index + 2

    # Handle remainder
    messages.append(blob[message_start:])

    return messages

def split_message_parts(
    blob: List[bytes],
    boundary: str,
) -> List[List[bytes]]:
    """Split a blob into message parts."""
    part_breaks = list()
    parts = list()
    part_start = 0

    # NOTE: can use `in' operator with bytes and strings
    for index, line in enumerate(blob):
        if boundary in line:
            part_breaks.append(index)

    for index in part_breaks:
        parts.append(blob[part_start:index - 1])
        part_start = index + 1

    parts.append(blob[part_start:])

    return parts





def parse_message(blob: List[bytes]):
    """Parse a message blob for metadata and parts."""
    msg = message.Message()
    header_end = 0

    # Parse the header
    for bytes_line in blob:
        header_end += 1
        try:
            line = str(bytes_line)
        except:
            # test for bad encodings
            raise

        if RE_BLANK_LINE.match(line):
            break
        elif match := RE_RUNON.match(line):
            msg.append_last(line)

        elif match := RE_SUBJECT_LINE.match(line):
            msg.hdr_subject = match.group(1)
        elif match := RE_DATE_LINE.match(line):
            msg.hdr_date = match.group(1)
        elif match := RE_FROM_LINE.match(line):
            msg.hdr_from = match.group(1)
        elif match := RE_TO_LINE.match(line):
            msg.hdr_to = match.group(1)
        elif match := RE_CC_LINE.match(line):
            msg.hdr_cc = match.group(1)
        elif match := RE_ID_LINE.match(line):
            msg.hdr_message_id = match.group(1)
        elif match := RE_CONTENT_LINE.match(line):
            msg.content_type = match.group(1)

    # store content
    msg._content = blob[header_end + 1:]

    # parse for parts
    try:
        if "multipart/" in msg.content_type:
            msg = msg.into_multipart()
    except:
        pass
    if isinstance(msg,message.MultipartMessage):
        if match := RE_BOUNDARY.match(msg.content_type):
            boundary = "".join(match.group(1).split())
        else:
            raise ValueError("no boundary for multipart content") from None
        msg._parts = split_message_parts(msg._content, boundary)

    print(msg)

def read_input() -> List[bytes]:
    """Read STDIN into a blob."""
    textblob = list()
    for line in sys.stdin:
        try:
            textblob.append(line.rstrip())
        except:
            # test for bad encoding
            raise
    return textblob


A  => tests/static/python_dev_mail_01.txt +294 -0
@@ 1,294 @@
Send Python-Dev mailing list submissions to
	python-dev@python.org

To subscribe or unsubscribe via the World Wide Web, visit
	https://mail.python.org/mailman3/lists/python-dev.python.org/
or, via email, send a message with subject or body 'help' to
	python-dev-request@python.org

You can reach the person managing the list at
	python-dev-owner@python.org

When replying, please edit your Subject line so it is more specific
than "Re: Contents of Python-Dev digest..."

Today's Topics:

   1. Re: PEP 626: Precise line numbers for debugging and other tools.
      (Inada Naoki)
   2. Re: How to customize CPython to a minimal set (Huang, Yang)


----------------------------------------------------------------------

Date: Wed, 22 Jul 2020 22:56:55 +0900
From: Inada Naoki <songofacandy@gmail.com>
Subject: [Python-Dev] Re: PEP 626: Precise line numbers for debugging
	and other tools.
To: Antoine Pitrou <antoine@python.org>
Cc: Python-Dev <python-dev@python.org>
Message-ID:
	<CAEfz+TyyPL8iut0YXHeUYLxjz40nVxPu9V_LYVqw0abqLXEHow@mail.gmail.com>
Content-Type: text/plain; charset="UTF-8"

On Wed, Jul 22, 2020 at 10:53 PM Antoine Pitrou <antoine@python.org> wrote:
>
>
> Le 22/07/2020 à 15:48, Inada Naoki a écrit :
> > On Wed, Jul 22, 2020 at 8:51 PM Antoine Pitrou <solipsis@pitrou.net> wrote:
> >>
> >>>
> >>> I don't think all attempts are failed.  Note that current CPython includes
> >>> some optimization already.
> >>
> >> The set of compile-time optimizations has almost not changed since at
> >> least 15 years ago.
> >>
> >
> > Constant folding is rewritten and unused constants are removed from co_consts.
> > That's one of what Victor did his project.
>
> Constant folding is not a new optimization, so this does not contradict
> what I said.  Also, constant folding is not precluded by Mark's
> proposal, AFAIK.
>

Yes, this is tooooo off topic. Please stop it.

-- 
Inada Naoki  <songofacandy@gmail.com>

------------------------------

Date: Wed, 22 Jul 2020 13:31:18 +0000
From: "Huang, Yang" <yang.huang@intel.com>
Subject: [Python-Dev] Re: How to customize CPython to a minimal set
To: "guido@python.org" <guido@python.org>, Steve Dower
	<steve.dower@python.org>, MRAB <python@mrabarnett.plus.com>
Cc: "python-dev@python.org" <python-dev@python.org>
Message-ID: <CY4PR11MB1894D722452F4E01EBDDC61E85790@CY4PR11MB1894.namp
	rd11.prod.outlook.com>
Content-Type: multipart/alternative;    boundary="_000_CY4PR11MB1894D7
	22452F4E01EBDDC61E85790CY4PR11MB1894namp_"

--_000_CY4PR11MB1894D722452F4E01EBDDC61E85790CY4PR11MB1894namp_
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64

VGhhbmsgeW91IGZvciBhbGwgeW91ciBjb21tZW50cy4NCkkgY2Fubm90IGFncmVlIGFueSBtb3Jl
LiBJIGRpZCB0cnkgYnV0IHRoZXJlIHdlcmUgc28gbWFueSBkZXBlbmRlbmNpZXMuIE9uZSBjaGFu
Z2UgYWxsIGNoYW5nZS4NCg0KTWljcm9weXRob24gaXMgYSBjaG9pY2UuIEJ1dCBub3Qgc3VyZSBp
ZiBudW1weSBhbmQgc3FsaXRlMyBjYW4gYmUgc3VwcG9ydGVkIHdlbGwuIEFuZCB3aGF04oCZcyB0
aGUgY29tcGF0aWJpbGl0eSBvZiB0aGUgbGlicyBpbiBQeXBpLg0KDQoNCkZyb206IEd1aWRvIHZh
biBSb3NzdW0gPGd1aWRvQHB5dGhvbi5vcmc+DQpTZW50OiBUdWVzZGF5LCBKdWx5IDIxLCAyMDIw
IDEwOjU3IFBNDQpUbzogSHVhbmcsIFlhbmcgPHlhbmcuaHVhbmdAaW50ZWwuY29tPg0KQ2M6IHB5
dGhvbi1kZXZAcHl0aG9uLm9yZw0KU3ViamVjdDogUmU6IFtQeXRob24tRGV2XSBIb3cgdG8gY3Vz
dG9taXplIENQeXRob24gdG8gYSBtaW5pbWFsIHNldA0KDQpJIGV4cGVjdCBpdCB3aWxsIGJlIHVu
ZmVhc2libGUgdG8gc3RyaXAgQ1B5dGhvbi4gSWYgeW91IGRpc2FncmVlLCB0cnkgaXQuIDstKQ0K
DQpPbiBNb24sIEp1bCAyMCwgMjAyMCBhdCAyMjozNSBIdWFuZywgWWFuZyA8eWFuZy5odWFuZ0Bp
bnRlbC5jb208bWFpbHRvOnlhbmcuaHVhbmdAaW50ZWwuY29tPj4gd3JvdGU6DQpIaSwgR3VpZG8N
Cg0KWWVzLiBNaWNyb3B5aHRvbiBpcyBhbHNvIGluIGNvbnNpZGVyYXRpb24uDQpCdXQgc3FsaXRl
MyBpcyB0aGUgZmlyc3QgdXNhZ2UuIFRoZXJlIHNob3VsZCBiZSBzb21lIGFkZGl0aW9uYWwgZmVh
dHVyZXMgbGlrZSBudW1weSwgc2NpcHkuLi4gTm90IHN1cmUgaWYgbWljcm9weXRob24gc3VwcG9y
dHMgd2VsbD8NCg0KT3IgaXMgdGhlcmUgYSBmZWFzaWJsZSB3YXkgdG8gc3RyaXAgQ1B5dGhvbiA/
DQoNClRoYW5rcy4NCg0KRnJvbTogR3VpZG8gdmFuIFJvc3N1bSA8Z3VpZG9AcHl0aG9uLm9yZzxt
YWlsdG86Z3VpZG9AcHl0aG9uLm9yZz4+DQpTZW50OiBNb25kYXksIEp1bHkgMjAsIDIwMjAgMTA6
NDUgUE0NClRvOiBIdWFuZywgWWFuZyA8eWFuZy5odWFuZ0BpbnRlbC5jb208bWFpbHRvOnlhbmcu
aHVhbmdAaW50ZWwuY29tPj4NCkNjOiBweXRob24tZGV2QHB5dGhvbi5vcmc8bWFpbHRvOnB5dGhv
bi1kZXZAcHl0aG9uLm9yZz4NClN1YmplY3Q6IFJlOiBbUHl0aG9uLURldl0gSG93IHRvIGN1c3Rv
bWl6ZSBDUHl0aG9uIHRvIGEgbWluaW1hbCBzZXQNCg0KSGF2ZSB5b3UgY29uc2lkZXJlZCBzdGFy
dGluZyB3aXRoIG1pY3JvcHl0aG9uPyBJdOKAmXMgbWFkZSBmb3IgZW1iZWRkZWQgc3lzdGVtcyBh
bmQgZnVsbHkgc3VwcG9ydHMgUHl0aG9uIDMgc3ludGF4LiBBZGRpbmcgc3FsaXRlMyBzdXBwb3J0
IHRvIGl0IHdpbGwgYmUgbGVzcyB3b3JrIHRoYW4gc3RyaXBwaW5nIGFsbCB0aGUgSS9PIGZyb20g
Q1B5dGhvbi4NCg0K4oCUR3VpZG8NCg0KT24gTW9uLCBKdWwgMjAsIDIwMjAgYXQgMDY6NDggSHVh
bmcsIFlhbmcgPHlhbmcuaHVhbmdAaW50ZWwuY29tPG1haWx0bzp5YW5nLmh1YW5nQGludGVsLmNv
bT4+IHdyb3RlOg0KDQpIaSwgYWxsDQoNClRoZXJlIGlzIGEgcmVxdWVzdCB0byBydW4gcHl0aG9u
IGluIGEgTGludXgtYmFzZWQgZW1iZWRkZWQgcmVzb3VyY2UgY29uc3RyYWluZWQgc3lzdGVtIHdp
dGggc3FsaXRlMyBzdXBwb3J0Lg0KDQpTbyBtYW55IGZlYXR1cmVzIGFyZSBub3QgcmVxdWlyZWQs
IGxpa2UgcG9zaXhtb2R1bGUsIHNpZ25hbG1vZHVsZSwgaGFzaHRhYmxlIC4uLg0KQnV0IHNlZW1z
IHRoZXJlIGFyZSBzb21lIGRlcGVuZGVuY2llcyBhbW9uZyB0aGUgTW9kdWxlcy9QYXJzZXIvUHl0
aG9uL09iamVjdHMvUHJvZ3JhbXMuLi4NCg0KSXMgdGhlcmUgYSB3YXkgdG8gdGFpbG9yIENQeXRo
b24gMyB0byBhIG1pbmltYWwgc2V0IHdpdGggc3FsaXRlMyAodGhlIGxlc3Mgc3lzY2FsbHMgdGhl
IGJldHRlcikgPw0KSXMgaXQgcG9zc2libGUgdG8gZG8gdGhhdD8NCg0KVGhhbmsgeW91Lg0KX19f
X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18NClB5dGhvbi1EZXYg
bWFpbGluZyBsaXN0IC0tIHB5dGhvbi1kZXZAcHl0aG9uLm9yZzxtYWlsdG86cHl0aG9uLWRldkBw
eXRob24ub3JnPg0KVG8gdW5zdWJzY3JpYmUgc2VuZCBhbiBlbWFpbCB0byBweXRob24tZGV2LWxl
YXZlQHB5dGhvbi5vcmc8bWFpbHRvOnB5dGhvbi1kZXYtbGVhdmVAcHl0aG9uLm9yZz4NCmh0dHBz
Oi8vbWFpbC5weXRob24ub3JnL21haWxtYW4zL2xpc3RzL3B5dGhvbi1kZXYucHl0aG9uLm9yZy8N
Ck1lc3NhZ2UgYXJjaGl2ZWQgYXQgaHR0cHM6Ly9tYWlsLnB5dGhvbi5vcmcvYXJjaGl2ZXMvbGlz
dC9weXRob24tZGV2QHB5dGhvbi5vcmcvbWVzc2FnZS9FQ1BMS1hRNDJWTkxIRDVEUDNSRzU3TDNR
VEo3N0ZVVC8NCkNvZGUgb2YgQ29uZHVjdDogaHR0cDovL3B5dGhvbi5vcmcvcHNmL2NvZGVvZmNv
bmR1Y3QvDQotLQ0KLS1HdWlkbyAobW9iaWxlKQ0KLS0NCi0tR3VpZG8gKG1vYmlsZSkNCg==

--_000_CY4PR11MB1894D722452F4E01EBDDC61E85790CY4PR11MB1894namp_
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: base64

PGh0bWwgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVy
bjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSIgeG1sbnM6dz0idXJuOnNjaGVt
YXMtbWljcm9zb2Z0LWNvbTpvZmZpY2U6d29yZCIgeG1sbnM6bT0iaHR0cDovL3NjaGVtYXMubWlj
cm9zb2Z0LmNvbS9vZmZpY2UvMjAwNC8xMi9vbW1sIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcv
VFIvUkVDLWh0bWw0MCI+DQo8aGVhZD4NCjxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIg
Y29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04Ij4NCjxtZXRhIG5hbWU9IkdlbmVyYXRv
ciIgY29udGVudD0iTWljcm9zb2Z0IFdvcmQgMTUgKGZpbHRlcmVkIG1lZGl1bSkiPg0KPHN0eWxl
PjwhLS0NCi8qIEZvbnQgRGVmaW5pdGlvbnMgKi8NCkBmb250LWZhY2UNCgl7Zm9udC1mYW1pbHk6
IkNhbWJyaWEgTWF0aCI7DQoJcGFub3NlLTE6MiA0IDUgMyA1IDQgNiAzIDIgNDt9DQpAZm9udC1m
YWNlDQoJe2ZvbnQtZmFtaWx5OkRlbmdYaWFuOw0KCXBhbm9zZS0xOjIgMSA2IDAgMyAxIDEgMSAx
IDE7fQ0KQGZvbnQtZmFjZQ0KCXtmb250LWZhbWlseTpDYWxpYnJpOw0KCXBhbm9zZS0xOjIgMTUg
NSAyIDIgMiA0IDMgMiA0O30NCkBmb250LWZhY2UNCgl7Zm9udC1mYW1pbHk6IlxARGVuZ1hpYW4i
Ow0KCXBhbm9zZS0xOjIgMSA2IDAgMyAxIDEgMSAxIDE7fQ0KLyogU3R5bGUgRGVmaW5pdGlvbnMg
Ki8NCnAuTXNvTm9ybWFsLCBsaS5Nc29Ob3JtYWwsIGRpdi5Nc29Ob3JtYWwNCgl7bWFyZ2luOjBp
bjsNCgltYXJnaW4tYm90dG9tOi4wMDAxcHQ7DQoJZm9udC1zaXplOjExLjBwdDsNCglmb250LWZh
bWlseToiQ2FsaWJyaSIsc2Fucy1zZXJpZjt9DQphOmxpbmssIHNwYW4uTXNvSHlwZXJsaW5rDQoJ
e21zby1zdHlsZS1wcmlvcml0eTo5OTsNCgljb2xvcjpibHVlOw0KCXRleHQtZGVjb3JhdGlvbjp1
bmRlcmxpbmU7fQ0KYTp2aXNpdGVkLCBzcGFuLk1zb0h5cGVybGlua0ZvbGxvd2VkDQoJe21zby1z
dHlsZS1wcmlvcml0eTo5OTsNCgljb2xvcjpwdXJwbGU7DQoJdGV4dC1kZWNvcmF0aW9uOnVuZGVy
bGluZTt9DQpwLm1zb25vcm1hbDAsIGxpLm1zb25vcm1hbDAsIGRpdi5tc29ub3JtYWwwDQoJe21z
by1zdHlsZS1uYW1lOm1zb25vcm1hbDsNCgltc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzsNCgltYXJn
aW4tcmlnaHQ6MGluOw0KCW1zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvOw0KCW1hcmdpbi1sZWZ0
OjBpbjsNCglmb250LXNpemU6MTEuMHB0Ow0KCWZvbnQtZmFtaWx5OiJDYWxpYnJpIixzYW5zLXNl
cmlmO30NCnNwYW4uRW1haWxTdHlsZTE4DQoJe21zby1zdHlsZS10eXBlOnBlcnNvbmFsLXJlcGx5
Ow0KCWZvbnQtZmFtaWx5OiJDYWxpYnJpIixzYW5zLXNlcmlmOw0KCWNvbG9yOndpbmRvd3RleHQ7
fQ0KLk1zb0NocERlZmF1bHQNCgl7bXNvLXN0eWxlLXR5cGU6ZXhwb3J0LW9ubHk7DQoJZm9udC1z
aXplOjEwLjBwdDsNCglmb250LWZhbWlseToiQ2FsaWJyaSIsc2Fucy1zZXJpZjt9DQpAcGFnZSBX
b3JkU2VjdGlvbjENCgl7c2l6ZTo4LjVpbiAxMS4waW47DQoJbWFyZ2luOjEuMGluIDEuMGluIDEu
MGluIDEuMGluO30NCmRpdi5Xb3JkU2VjdGlvbjENCgl7cGFnZTpXb3JkU2VjdGlvbjE7fQ0KLS0+
PC9zdHlsZT48IS0tW2lmIGd0ZSBtc28gOV0+PHhtbD4NCjxvOnNoYXBlZGVmYXVsdHMgdjpleHQ9
ImVkaXQiIHNwaWRtYXg9IjEwMjYiIC8+DQo8L3htbD48IVtlbmRpZl0tLT48IS0tW2lmIGd0ZSBt
c28gOV0+PHhtbD4NCjxvOnNoYXBlbGF5b3V0IHY6ZXh0PSJlZGl0Ij4NCjxvOmlkbWFwIHY6ZXh0
PSJlZGl0IiBkYXRhPSIxIiAvPg0KPC9vOnNoYXBlbGF5b3V0PjwveG1sPjwhW2VuZGlmXS0tPg0K
PC9oZWFkPg0KPGJvZHkgbGFuZz0iRU4tVVMiIGxpbms9ImJsdWUiIHZsaW5rPSJwdXJwbGUiPg0K
PGRpdiBjbGFzcz0iV29yZFNlY3Rpb24xIj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPlRoYW5rIHlv
dSBmb3IgYWxsIHlvdXIgY29tbWVudHMuIDxvOnA+PC9vOnA+PC9wPg0KPHAgY2xhc3M9Ik1zb05v
cm1hbCI+SSBjYW5ub3QgYWdyZWUgYW55IG1vcmUuIEkgZGlkIHRyeSBidXQgdGhlcmUgd2VyZSBz
byBtYW55IGRlcGVuZGVuY2llcy4gT25lIGNoYW5nZSBhbGwgY2hhbmdlLjxvOnA+PC9vOnA+PC9w
Pg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI+PG86cD4mbmJzcDs8L286cD48L3A+DQo8cCBjbGFzcz0i
TXNvTm9ybWFsIj5NaWNyb3B5dGhvbiBpcyBhIGNob2ljZS4gQnV0IG5vdCBzdXJlIGlmIG51bXB5
IGFuZCBzcWxpdGUzIGNhbiBiZSBzdXBwb3J0ZWQgd2VsbC4gQW5kIHdoYXTigJlzIHRoZSBjb21w
YXRpYmlsaXR5IG9mIHRoZSBsaWJzIGluIFB5cGkuICZuYnNwOzxvOnA+PC9vOnA+PC9wPg0KPHAg
Y2xhc3M9Ik1zb05vcm1hbCI+PG86cD4mbmJzcDs8L286cD48L3A+DQo8cCBjbGFzcz0iTXNvTm9y
bWFsIj48bzpwPiZuYnNwOzwvbzpwPjwvcD4NCjxkaXYgc3R5bGU9ImJvcmRlcjpub25lO2JvcmRl
ci1sZWZ0OnNvbGlkIGJsdWUgMS41cHQ7cGFkZGluZzowaW4gMGluIDBpbiA0LjBwdCI+DQo8ZGl2
Pg0KPGRpdiBzdHlsZT0iYm9yZGVyOm5vbmU7Ym9yZGVyLXRvcDpzb2xpZCAjRTFFMUUxIDEuMHB0
O3BhZGRpbmc6My4wcHQgMGluIDBpbiAwaW4iPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCI+PGI+RnJv
bTo8L2I+IEd1aWRvIHZhbiBSb3NzdW0gJmx0O2d1aWRvQHB5dGhvbi5vcmcmZ3Q7IDxicj4NCjxi
PlNlbnQ6PC9iPiBUdWVzZGF5LCBKdWx5IDIxLCAyMDIwIDEwOjU3IFBNPGJyPg0KPGI+VG86PC9i
PiBIdWFuZywgWWFuZyAmbHQ7eWFuZy5odWFuZ0BpbnRlbC5jb20mZ3Q7PGJyPg0KPGI+Q2M6PC9i
PiBweXRob24tZGV2QHB5dGhvbi5vcmc8YnI+DQo8Yj5TdWJqZWN0OjwvYj4gUmU6IFtQeXRob24t
RGV2XSBIb3cgdG8gY3VzdG9taXplIENQeXRob24gdG8gYSBtaW5pbWFsIHNldDxvOnA+PC9vOnA+
PC9wPg0KPC9kaXY+DQo8L2Rpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiPjxvOnA+Jm5ic3A7PC9v
OnA+PC9wPg0KPGRpdj4NCjxkaXY+DQo8cCBjbGFzcz0iTXNvTm9ybWFsIj5JIGV4cGVjdCBpdCB3
aWxsIGJlIHVuZmVhc2libGUgdG8gc3RyaXAgQ1B5dGhvbi4gSWYgeW91IGRpc2FncmVlLCB0cnkg
aXQuIDstKTxvOnA+PC9vOnA+PC9wPg0KPC9kaXY+DQo8L2Rpdj4NCjxkaXY+DQo8cCBjbGFzcz0i
TXNvTm9ybWFsIj48bzpwPiZuYnNwOzwvbzpwPjwvcD4NCjxkaXY+DQo8ZGl2Pg0KPHAgY2xhc3M9
Ik1zb05vcm1hbCI+T24gTW9uLCBKdWwgMjAsIDIwMjAgYXQgMjI6MzUgSHVhbmcsIFlhbmcgJmx0
OzxhIGhyZWY9Im1haWx0bzp5YW5nLmh1YW5nQGludGVsLmNvbSI+eWFuZy5odWFuZ0BpbnRlbC5j
b208L2E+Jmd0OyB3cm90ZTo8bzpwPjwvbzpwPjwvcD4NCjwvZGl2Pg0KPGJsb2NrcXVvdGUgc3R5
bGU9ImJvcmRlcjpub25lO2JvcmRlci1sZWZ0OnNvbGlkICNDQ0NDQ0MgMS4wcHQ7cGFkZGluZzow
aW4gMGluIDBpbiA2LjBwdDttYXJnaW4tbGVmdDo0LjhwdDttYXJnaW4tdG9wOjUuMHB0O21hcmdp
bi1yaWdodDowaW47bWFyZ2luLWJvdHRvbTo1LjBwdCI+DQo8ZGl2Pg0KPGRpdj4NCjxwIGNsYXNz
PSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJv
dHRvbS1hbHQ6YXV0byI+SGksIEd1aWRvPG86cD48L286cD48L3A+DQo8cCBjbGFzcz0iTXNvTm9y
bWFsIiBzdHlsZT0ibXNvLW1hcmdpbi10b3AtYWx0OmF1dG87bXNvLW1hcmdpbi1ib3R0b20tYWx0
OmF1dG8iPiZuYnNwOzxvOnA+PC9vOnA+PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9
Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj5ZZXMu
IE1pY3JvcHlodG9uIGlzIGFsc28gaW4gY29uc2lkZXJhdGlvbi4NCjxvOnA+PC9vOnA+PC9wPg0K
PHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1t
YXJnaW4tYm90dG9tLWFsdDphdXRvIj5CdXQgc3FsaXRlMyBpcyB0aGUgZmlyc3QgdXNhZ2UuIFRo
ZXJlIHNob3VsZCBiZSBzb21lIGFkZGl0aW9uYWwgZmVhdHVyZXMgbGlrZSBudW1weSwgc2NpcHku
Li4gTm90IHN1cmUgaWYgbWljcm9weXRob24gc3VwcG9ydHMgd2VsbD8NCjxvOnA+PC9vOnA+PC9w
Pg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21z
by1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj4mbmJzcDs8bzpwPjwvbzpwPjwvcD4NCjxwIGNsYXNz
PSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJv
dHRvbS1hbHQ6YXV0byI+T3IgaXMgdGhlcmUgYSBmZWFzaWJsZSB3YXkgdG8gc3RyaXAgQ1B5dGhv
biA/PG86cD48L286cD48L3A+DQo8cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibXNvLW1hcmdp
bi10b3AtYWx0OmF1dG87bXNvLW1hcmdpbi1ib3R0b20tYWx0OmF1dG8iPiZuYnNwOzxvOnA+PC9v
OnA+PC9wPg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDph
dXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj5UaGFua3MuDQo8bzpwPjwvbzpwPjwvcD4N
CjwvZGl2Pg0KPC9kaXY+DQo8ZGl2Pg0KPGRpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxl
PSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI+Jm5i
c3A7PG86cD48L286cD48L3A+DQo8ZGl2IHN0eWxlPSJib3JkZXI6bm9uZTtib3JkZXItbGVmdDpz
b2xpZCBibHVlIDEuNXB0O3BhZGRpbmc6MGluIDBpbiAwaW4gNC4wcHQiPg0KPGRpdj4NCjxkaXYg
c3R5bGU9ImJvcmRlcjpub25lO2JvcmRlci10b3A6c29saWQgI0UxRTFFMSAxLjBwdDtwYWRkaW5n
OjMuMHB0IDBpbiAwaW4gMGluIj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFy
Z2luLXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI+PGI+RnJvbTo8L2I+
IEd1aWRvIHZhbiBSb3NzdW0gJmx0OzxhIGhyZWY9Im1haWx0bzpndWlkb0BweXRob24ub3JnIiB0
YXJnZXQ9Il9ibGFuayI+Z3VpZG9AcHl0aG9uLm9yZzwvYT4mZ3Q7DQo8YnI+DQo8Yj5TZW50Ojwv
Yj4gTW9uZGF5LCBKdWx5IDIwLCAyMDIwIDEwOjQ1IFBNPGJyPg0KPGI+VG86PC9iPiBIdWFuZywg
WWFuZyAmbHQ7PGEgaHJlZj0ibWFpbHRvOnlhbmcuaHVhbmdAaW50ZWwuY29tIiB0YXJnZXQ9Il9i
bGFuayI+eWFuZy5odWFuZ0BpbnRlbC5jb208L2E+Jmd0Ozxicj4NCjxiPkNjOjwvYj4gPGEgaHJl
Zj0ibWFpbHRvOnB5dGhvbi1kZXZAcHl0aG9uLm9yZyIgdGFyZ2V0PSJfYmxhbmsiPnB5dGhvbi1k
ZXZAcHl0aG9uLm9yZzwvYT48YnI+DQo8Yj5TdWJqZWN0OjwvYj4gUmU6IFtQeXRob24tRGV2XSBI
b3cgdG8gY3VzdG9taXplIENQeXRob24gdG8gYSBtaW5pbWFsIHNldDxvOnA+PC9vOnA+PC9wPg0K
PC9kaXY+DQo8L2Rpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRv
cC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI+Jm5ic3A7PG86cD48L286cD48
L3A+DQo8ZGl2Pg0KPGRpdj4NCjxwIGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2lu
LXRvcC1hbHQ6YXV0bzttc28tbWFyZ2luLWJvdHRvbS1hbHQ6YXV0byI+SGF2ZSB5b3UgY29uc2lk
ZXJlZCBzdGFydGluZyB3aXRoIG1pY3JvcHl0aG9uPyBJdOKAmXMgbWFkZSBmb3IgZW1iZWRkZWQg
c3lzdGVtcyBhbmQgZnVsbHkgc3VwcG9ydHMgUHl0aG9uIDMgc3ludGF4LiBBZGRpbmcgc3FsaXRl
MyBzdXBwb3J0IHRvIGl0IHdpbGwgYmUgbGVzcyB3b3JrIHRoYW4gc3RyaXBwaW5nIGFsbA0KIHRo
ZSBJL08gZnJvbSBDUHl0aG9uLjxvOnA+PC9vOnA+PC9wPg0KPC9kaXY+DQo8ZGl2Pg0KPHAgY2xh
c3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4t
Ym90dG9tLWFsdDphdXRvIj4mbmJzcDs8bzpwPjwvbzpwPjwvcD4NCjwvZGl2Pg0KPGRpdj4NCjxw
IGNsYXNzPSJNc29Ob3JtYWwiIHN0eWxlPSJtc28tbWFyZ2luLXRvcC1hbHQ6YXV0bzttc28tbWFy
Z2luLWJvdHRvbS1hbHQ6YXV0byI+4oCUR3VpZG88bzpwPjwvbzpwPjwvcD4NCjwvZGl2Pg0KPC9k
aXY+DQo8ZGl2Pg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFs
dDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj4mbmJzcDs8bzpwPjwvbzpwPjwvcD4N
CjxkaXY+DQo8ZGl2Pg0KPHAgY2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9w
LWFsdDphdXRvO21zby1tYXJnaW4tYm90dG9tLWFsdDphdXRvIj5PbiBNb24sIEp1bCAyMCwgMjAy
MCBhdCAwNjo0OCBIdWFuZywgWWFuZyAmbHQ7PGEgaHJlZj0ibWFpbHRvOnlhbmcuaHVhbmdAaW50
ZWwuY29tIiB0YXJnZXQ9Il9ibGFuayI+eWFuZy5odWFuZ0BpbnRlbC5jb208L2E+Jmd0OyB3cm90
ZTo8bzpwPjwvbzpwPjwvcD4NCjwvZGl2Pg0KPGJsb2NrcXVvdGUgc3R5bGU9ImJvcmRlcjpub25l
O2JvcmRlci1sZWZ0OnNvbGlkICNDQ0NDQ0MgMS4wcHQ7cGFkZGluZzowaW4gMGluIDBpbiA2LjBw
dDttYXJnaW4tbGVmdDo0LjhwdDttYXJnaW4tdG9wOjUuMHB0O21hcmdpbi1yaWdodDowaW47bWFy
Z2luLWJvdHRvbTo1LjBwdCI+DQo8cCBjbGFzcz0iTXNvTm9ybWFsIiBzdHlsZT0ibXNvLW1hcmdp
bi10b3AtYWx0OmF1dG87bXNvLW1hcmdpbi1ib3R0b20tYWx0OmF1dG8iPjxicj4NCkhpLCBhbGw8
YnI+DQo8YnI+DQpUaGVyZSBpcyBhIHJlcXVlc3QgdG8gcnVuIHB5dGhvbiBpbiBhIExpbnV4LWJh
c2VkIGVtYmVkZGVkIHJlc291cmNlIGNvbnN0cmFpbmVkIHN5c3RlbSB3aXRoIHNxbGl0ZTMgc3Vw
cG9ydC48YnI+DQo8YnI+DQpTbyBtYW55IGZlYXR1cmVzIGFyZSBub3QgcmVxdWlyZWQsIGxpa2Ug
cG9zaXhtb2R1bGUsIHNpZ25hbG1vZHVsZSwgaGFzaHRhYmxlIC4uLjxicj4NCkJ1dCBzZWVtcyB0
aGVyZSBhcmUgc29tZSBkZXBlbmRlbmNpZXMgYW1vbmcgdGhlIE1vZHVsZXMvUGFyc2VyL1B5dGhv
bi9PYmplY3RzL1Byb2dyYW1zLi4uPGJyPg0KPGJyPg0KSXMgdGhlcmUgYSB3YXkgdG8gdGFpbG9y
IENQeXRob24gMyB0byBhIG1pbmltYWwgc2V0IHdpdGggc3FsaXRlMyAodGhlIGxlc3Mgc3lzY2Fs
bHMgdGhlIGJldHRlcikgPw0KPGJyPg0KSXMgaXQgcG9zc2libGUgdG8gZG8gdGhhdD88YnI+DQo8
YnI+DQpUaGFuayB5b3UuPGJyPg0KX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f
X19fX19fX19fX188YnI+DQpQeXRob24tRGV2IG1haWxpbmcgbGlzdCAtLSA8YSBocmVmPSJtYWls
dG86cHl0aG9uLWRldkBweXRob24ub3JnIiB0YXJnZXQ9Il9ibGFuayI+DQpweXRob24tZGV2QHB5
dGhvbi5vcmc8L2E+PGJyPg0KVG8gdW5zdWJzY3JpYmUgc2VuZCBhbiBlbWFpbCB0byA8YSBocmVm
PSJtYWlsdG86cHl0aG9uLWRldi1sZWF2ZUBweXRob24ub3JnIiB0YXJnZXQ9Il9ibGFuayI+DQpw
eXRob24tZGV2LWxlYXZlQHB5dGhvbi5vcmc8L2E+PGJyPg0KPGEgaHJlZj0iaHR0cHM6Ly9tYWls
LnB5dGhvbi5vcmcvbWFpbG1hbjMvbGlzdHMvcHl0aG9uLWRldi5weXRob24ub3JnLyIgdGFyZ2V0
PSJfYmxhbmsiPmh0dHBzOi8vbWFpbC5weXRob24ub3JnL21haWxtYW4zL2xpc3RzL3B5dGhvbi1k
ZXYucHl0aG9uLm9yZy88L2E+PGJyPg0KTWVzc2FnZSBhcmNoaXZlZCBhdCA8YSBocmVmPSJodHRw
czovL21haWwucHl0aG9uLm9yZy9hcmNoaXZlcy9saXN0L3B5dGhvbi1kZXZAcHl0aG9uLm9yZy9t
ZXNzYWdlL0VDUExLWFE0MlZOTEhENURQM1JHNTdMM1FUSjc3RlVULyIgdGFyZ2V0PSJfYmxhbmsi
Pg0KaHR0cHM6Ly9tYWlsLnB5dGhvbi5vcmcvYXJjaGl2ZXMvbGlzdC9weXRob24tZGV2QHB5dGhv
bi5vcmcvbWVzc2FnZS9FQ1BMS1hRNDJWTkxIRDVEUDNSRzU3TDNRVEo3N0ZVVC88L2E+PGJyPg0K
Q29kZSBvZiBDb25kdWN0OiA8YSBocmVmPSJodHRwOi8vcHl0aG9uLm9yZy9wc2YvY29kZW9mY29u
ZHVjdC8iIHRhcmdldD0iX2JsYW5rIj5odHRwOi8vcHl0aG9uLm9yZy9wc2YvY29kZW9mY29uZHVj
dC88L2E+PG86cD48L286cD48L3A+DQo8L2Jsb2NrcXVvdGU+DQo8L2Rpdj4NCjwvZGl2Pg0KPHAg
Y2xhc3M9Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJn
aW4tYm90dG9tLWFsdDphdXRvIj4tLQ0KPG86cD48L286cD48L3A+DQo8ZGl2Pg0KPHAgY2xhc3M9
Ik1zb05vcm1hbCIgc3R5bGU9Im1zby1tYXJnaW4tdG9wLWFsdDphdXRvO21zby1tYXJnaW4tYm90
dG9tLWFsdDphdXRvIj4tLUd1aWRvIChtb2JpbGUpPG86cD48L286cD48L3A+DQo8L2Rpdj4NCjwv
ZGl2Pg0KPC9kaXY+DQo8L2Rpdj4NCjwvYmxvY2txdW90ZT4NCjwvZGl2Pg0KPC9kaXY+DQo8cCBj
bGFzcz0iTXNvTm9ybWFsIj4tLSA8bzpwPjwvbzpwPjwvcD4NCjxkaXY+DQo8cCBjbGFzcz0iTXNv
Tm9ybWFsIj4tLUd1aWRvIChtb2JpbGUpPG86cD48L286cD48L3A+DQo8L2Rpdj4NCjwvZGl2Pg0K
PC9kaXY+DQo8L2JvZHk+DQo8L2h0bWw+DQo=

--_000_CY4PR11MB1894D722452F4E01EBDDC61E85790CY4PR11MB1894namp_--

------------------------------

Subject: Digest Footer

_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/


------------------------------

End of Python-Dev Digest, Vol 204, Issue 123
********************************************

A  => tests/static/python_dev_mail_02.txt +480 -0
@@ 1,480 @@
Send Python-Dev mailing list submissions to
	python-dev@python.org

To subscribe or unsubscribe via the World Wide Web, visit
	https://mail.python.org/mailman3/lists/python-dev.python.org/
or, via email, send a message with subject or body 'help' to
	python-dev-request@python.org

You can reach the person managing the list at
	python-dev-owner@python.org

When replying, please edit your Subject line so it is more specific
than "Re: Contents of Python-Dev digest..."

Today's Topics:

   1. Re: PEP 626: Precise line numbers for debugging and other tools.
      (Guido van Rossum)
   2. Add mimetypes.mimesniff (Dong-hee Na)
   3. Re: How to customize CPython to a minimal set (Matthias Klose)


----------------------------------------------------------------------

Date: Wed, 22 Jul 2020 18:15:49 -0700
From: Guido van Rossum <guido@python.org>
Subject: [Python-Dev] Re: PEP 626: Precise line numbers for debugging
	and other tools.
To: "Gregory P. Smith" <greg@krypto.org>
Cc: Python-Dev <python-dev@python.org>
Message-ID:
	<CAP7+vJKeKeQr3suo3YK7NoRgLtaOF2u0K=CVn+FQvXBmrsjycQ@mail.gmail.com>
Content-Type: multipart/alternative;
	boundary="00000000000075ecc005ab119986"

--00000000000075ecc005ab119986
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

But on which line is the RETURN opcode if there is more than a docstring?
Doesn=E2=80=99t it make sense to have it attached to the last line of the b=
ody?
(Too bad about pytype, that kind of change happens =E2=80=94 we had this ki=
nd of
thing for mypy too, when line numbers in the AST were fixed.)

On Wed, Jul 22, 2020 at 17:29 Gregory P. Smith <greg@krypto.org> wrote:

>
>
> On Wed, Jul 22, 2020 at 5:19 AM Mark Shannon <mark@hotpy.org> wrote:
>
>>
>>
>> On 21/07/2020 9:46 pm, Gregory P. Smith wrote:
>> >
>> >
>> > On Fri, Jul 17, 2020 at 8:41 AM Ned Batchelder <ned@nedbatchelder.com
>> > <mailto:ned@nedbatchelder.com>> wrote:
>> >
>> >     https://www.python.org/dev/peps/pep-0626/ :)
>> >
>> >     --Ned.
>> >
>> >     On 7/17/20 10:48 AM, Mark Shannon wrote:
>> >      > Hi all,
>> >      >
>> >      > I'd like to announce a new PEP.
>> >      >
>> >      > It is mainly codifying that Python should do what you probably
>> >     already
>> >      > thought it did :)
>> >      >
>> >      > Should be uncontroversial, but all comments are welcome.
>> >      >
>> >      > Cheers,
>> >      > Mark.
>> >
>> >
>> > """When a frame object is created, the f_lineno will be set to the lin=
e
>> > at which the function or class is defined. For modules it will be set
>> to
>> > zero."""
>> >
>> > Within this PEP it'd be good for us to be very pedantic.  f_lineno is =
a
>> > single number.  So which number is it given many class and function
>> > definition statements can span multiple lines.
>> >
>> > Is it the line containing the class or def keyword?  Or is it the line
>> > containing the trailing :?
>>
>> The line of the `def`/`class`. It wouldn't change for the current
>> behavior. I'll add that to the PEP.
>>
>> >
>> > Q: Why can't we have the information about the entire span of lines
>> > rather than consider a definition to be a "line"?
>>
>> Pretty much every profiler, coverage tool, and debugger ever expects
>> lines to be natural numbers, not ranges of numbers.
>> A lot of tooling would need to be changed.
>>
>> >
>> > I think that question applies to later sections as well.  Anywhere we
>> > refer to a "line", it could actually mean a span of lines. (especially
>> > when you consider \ continuation in situations you might not otherwise
>> > think could span lines)
>>
>> Let's take an example:
>> ```
>> x =3D (
>>      a,
>>      b,
>> )
>> ```
>>
>> You would want the BUILD_TUPLE instruction to have a of span lines 1 to
>> 4 (inclusive), rather just line 1?
>> If you wanted to break on the BUILD_TUPLE where you tell pdb to break?
>>
>> I don't see that it would add much value, but it would add a lot of
>> complexity.
>>
>
> We should have the data about the range at bytecode compilation time,
> correct?  So why not keep it?  sure, most existing tooling would just use
> the start of the range as the line number as it always has.  but some
> tooling could find the range useful (ex: semantic code indexing for use i=
n
> display, search, editors, IDEs. Rendering lint errors more accurately
> instead of just claiming a single line or resorting to parsing hacks to
> come up with a range, etc.).  The downside is that we'd be storing a seco=
nd
> number in bytecode making it slightly larger.  Though it could be stored
> efficiently as a prefixed delta so it'd likely average out as less than 2
> bytes per line number stored.  (i don't have a feeling for our current
> format to know if that is significant or not - if it is, maybe this idea
> just gets nixed)
>
> The reason the range concept was on my mind is due to something not quite
> related but involving a changed idea of a line number in our current syst=
em
> that we recently ran into with pytype during a Python upgrade.
>
> """in 3.7, if a function body is a plain docstring, the line number of th=
e
> RETURN_VALUE opcode corresponds to the docstring, whereas in 3.6 it
> corresponds to the function definition.""" (Thanks, Martin & Rebecca!)
>
> ```python
> def no_op():
>   """docstring instead of pass."""
> ```
>
> so the location of what *was* originally an end of line `# pytype:
> disable=3Dbad-return-type` comment (to work around an issue not relevant
> here) turned awkward and version dependent.  pytype is bytecode based, th=
us
> that is where its line numbers come from.  metadata comments in source ca=
n
> only be tied to bytecode via line numbers.  making end of line directives
> occasionally hard to match up.
>
> When there is no return statement, this opcode still exists.  what line
> number does it belong to?  3.6's answer made sense to me.  3.7's seems
> wrong - a docstring isn't responsible for a return opcode.  I didn't chec=
k
> what 3.8 and 3.9 do.  An alternate answer after this PEP is that it
> wouldn't have a line number when there is no return statement (pedantical=
ly
> correct, I approve! #win).
>
> -gps
>
>
>>
>> Cheers,
>> Mark.
>>
>> >
>> > -gps
>>
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-dev@python.org/message/H3YBK=
275SUSCR5EHWHYBTJBF655UK7JG/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
--=20
--Guido (mobile)

--00000000000075ecc005ab119986
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div><div><div dir=3D"auto">But on which line is the RETURN opcode if there=
 is more than a docstring? Doesn=E2=80=99t it make sense to have it attache=
d to the last line of the body? (Too bad about pytype, that kind of change =
happens =E2=80=94 we had this kind of thing for mypy too, when line numbers=
 in the AST were fixed.)</div></div></div><div><div><br><div class=3D"gmail=
_quote"><div dir=3D"ltr" class=3D"gmail_attr">On Wed, Jul 22, 2020 at 17:29=
 Gregory P. Smith &lt;<a href=3D"mailto:greg@krypto.org" target=3D"_blank">=
greg@krypto.org</a>&gt; wrote:<br></div><blockquote class=3D"gmail_quote" s=
tyle=3D"margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:so=
lid;padding-left:1ex;border-left-color:rgb(204,204,204)"><div dir=3D"ltr"><=
div dir=3D"ltr"><br></div><br><div class=3D"gmail_quote"></div></div><div d=
ir=3D"ltr"><div class=3D"gmail_quote"><div dir=3D"ltr" class=3D"gmail_attr"=
>On Wed, Jul 22, 2020 at 5:19 AM Mark Shannon &lt;<a href=3D"mailto:mark@ho=
tpy.org" target=3D"_blank">mark@hotpy.org</a>&gt; wrote:<br></div><blockquo=
te class=3D"gmail_quote" style=3D"margin:0px 0px 0px 0.8ex;border-left-widt=
h:1px;border-left-style:solid;padding-left:1ex;border-left-color:rgb(204,20=
4,204)"><br>
<br>
On 21/07/2020 9:46 pm, Gregory P. Smith wrote:<br>
&gt; <br>
&gt; <br>
&gt; On Fri, Jul 17, 2020 at 8:41 AM Ned Batchelder &lt;<a href=3D"mailto:n=
ed@nedbatchelder.com" target=3D"_blank">ned@nedbatchelder.com</a> <br>
&gt; &lt;mailto:<a href=3D"mailto:ned@nedbatchelder.com" target=3D"_blank">=
ned@nedbatchelder.com</a>&gt;&gt; wrote:<br>
&gt; <br>
&gt;=C2=A0 =C2=A0 =C2=A0<a href=3D"https://www.python.org/dev/peps/pep-0626=
/" rel=3D"noreferrer" target=3D"_blank">https://www.python.org/dev/peps/pep=
-0626/</a> :)<br>
&gt; <br>
&gt;=C2=A0 =C2=A0 =C2=A0--Ned.<br>
&gt; <br>
&gt;=C2=A0 =C2=A0 =C2=A0On 7/17/20 10:48 AM, Mark Shannon wrote:<br>
&gt;=C2=A0 =C2=A0 =C2=A0 &gt; Hi all,<br>
&gt;=C2=A0 =C2=A0 =C2=A0 &gt;<br>
&gt;=C2=A0 =C2=A0 =C2=A0 &gt; I&#39;d like to announce a new PEP.<br>
&gt;=C2=A0 =C2=A0 =C2=A0 &gt;<br>
&gt;=C2=A0 =C2=A0 =C2=A0 &gt; It is mainly codifying that Python should do =
what you probably<br>
&gt;=C2=A0 =C2=A0 =C2=A0already<br>
&gt;=C2=A0 =C2=A0 =C2=A0 &gt; thought it did :)<br>
&gt;=C2=A0 =C2=A0 =C2=A0 &gt;<br>
&gt;=C2=A0 =C2=A0 =C2=A0 &gt; Should be uncontroversial, but all comments a=
re welcome.<br>
&gt;=C2=A0 =C2=A0 =C2=A0 &gt;<br>
&gt;=C2=A0 =C2=A0 =C2=A0 &gt; Cheers,<br>
&gt;=C2=A0 =C2=A0 =C2=A0 &gt; Mark.<br>
&gt; <br>
&gt; <br>
&gt; &quot;&quot;&quot;When a frame object is created, the f_lineno will be=
 set to the line <br>
&gt; at which the function or class is defined. For modules it will be set =
to <br>
&gt; zero.&quot;&quot;&quot;<br>
&gt; <br>
&gt; Within this PEP it&#39;d be good for us to be very pedantic.=C2=A0 f_l=
ineno is a <br>
&gt; single number.=C2=A0 So which number is it given many class and functi=
on <br>
&gt; definition statements can span multiple lines.<br>
&gt; <br>
&gt; Is it the line containing the class or def keyword?=C2=A0 Or is it the=
 line <br>
&gt; containing the trailing :?<br>
<br>
The line of the `def`/`class`. It wouldn&#39;t change for the current <br>
behavior. I&#39;ll add that to the PEP.<br>
<br>
&gt; <br>
&gt; Q: Why can&#39;t we have the information about the entire span of line=
s <br>
&gt; rather than consider a definition to be a &quot;line&quot;?<br>
<br>
Pretty much every profiler, coverage tool, and debugger ever expects <br>
lines to be natural numbers, not ranges of numbers.<br>
A lot of tooling would need to be changed.<br>
<br>
&gt; <br>
&gt; I think that question applies to later sections as well.=C2=A0 Anywher=
e we <br>
&gt; refer to a &quot;line&quot;, it could actually mean a span of=C2=A0lin=
es. (especially <br>
&gt; when you consider \ continuation in situations you might not otherwise=
 <br>
&gt; think could span lines)<br>
<br>
Let&#39;s take an example:<br>
```<br>
x =3D (<br>
=C2=A0 =C2=A0 =C2=A0a,<br>
=C2=A0 =C2=A0 =C2=A0b,<br>
)<br>
```<br>
<br>
You would want the BUILD_TUPLE instruction to have a of span lines 1 to <br=
>
4 (inclusive), rather just line 1?<br>
If you wanted to break on the BUILD_TUPLE where you tell pdb to break?<br>
<br>
I don&#39;t see that it would add much value, but it would add a lot of <br=
>
complexity.<br></blockquote><div><br></div></div></div><div dir=3D"ltr"><di=
v class=3D"gmail_quote"><div><div>We should have the data about the range a=
t bytecode compilation time, correct?=C2=A0 So why not keep it?=C2=A0 sure,=
 most existing tooling would just use the start of the range as the line nu=
mber as it always has.=C2=A0 but some tooling could find the range useful (=
ex: semantic code indexing for use in display, search, editors, IDEs. Rende=
ring lint errors more accurately instead of just claiming a single line or =
resorting to parsing hacks to come up with a range, etc.).=C2=A0 The downsi=
de is that we&#39;d be storing a second number in bytecode making it slight=
ly larger.=C2=A0 Though it could be stored efficiently as a prefixed delta =
so it&#39;d likely average out as less than 2 bytes per line number stored.=
=C2=A0 (i don&#39;t have a feeling for our current format to know if that i=
s significant or not - if it is, maybe this idea just gets nixed)</div><div=
><br></div></div><div>The reason the range concept was on my mind is due to=
 something not quite related but involving a changed idea of a line number =
in our current system that we recently ran into with pytype during a Python=
 upgrade.</div><div><br></div><div>&quot;&quot;&quot;in 3.7, if a function =
body is a plain docstring, the line number of the RETURN_VALUE opcode corre=
sponds to the docstring, whereas in 3.6 it corresponds to the function defi=
nition.&quot;&quot;&quot; (Thanks, Martin &amp; Rebecca!)</div><div><br></d=
iv><div>```python</div><div>def no_op():</div><div>=C2=A0 &quot;&quot;&quot=
;docstring instead of pass.&quot;&quot;&quot;</div><div>```</div><div><br><=
/div><div>so the location of what <i>was</i> originally an end of line `# p=
ytype: disable=3Dbad-return-type` comment (to work around an issue not rele=
vant here) turned awkward and version dependent.=C2=A0=C2=A0pytype is bytec=
ode based, thus that is where its line numbers=C2=A0come from.=C2=A0 metada=
ta comments in source can only be tied to bytecode via line numbers.=C2=A0 =
making end of line directives occasionally hard to match up.</div><div><br>=
</div><div>When there is no return statement, this opcode still exists.=C2=
=A0 what line number does it belong to?=C2=A0 3.6&#39;s answer made sense t=
o me.=C2=A0 3.7&#39;s seems wrong - a docstring isn&#39;t responsible for a=
 return opcode.=C2=A0 I didn&#39;t check what 3.8 and 3.9 do.=C2=A0 An alte=
rnate answer after this PEP is that it wouldn&#39;t have a line number when=
 there is no return statement (pedantically correct, I approve! #win).</div=
><div><br></div><div>-gps<br></div><div>=C2=A0</div><blockquote class=3D"gm=
ail_quote" style=3D"margin:0px 0px 0px 0.8ex;border-left-width:1px;border-l=
eft-style:solid;padding-left:1ex;border-left-color:rgb(204,204,204)">
<br>
Cheers,<br>
Mark.<br>
<br>
&gt; <br>
&gt; -gps<br>
</blockquote></div></div>
_______________________________________________<br>
Python-Dev mailing list -- <a href=3D"mailto:python-dev@python.org" target=
=3D"_blank">python-dev@python.org</a><br>
To unsubscribe send an email to <a href=3D"mailto:python-dev-leave@python.o=
rg" target=3D"_blank">python-dev-leave@python.org</a><br>
<a href=3D"https://mail.python.org/mailman3/lists/python-dev.python.org/" r=
el=3D"noreferrer" target=3D"_blank">https://mail.python.org/mailman3/lists/=
python-dev.python.org/</a><br>
Message archived at <a href=3D"https://mail.python.org/archives/list/python=
-dev@python.org/message/H3YBK275SUSCR5EHWHYBTJBF655UK7JG/" rel=3D"noreferre=
r" target=3D"_blank">https://mail.python.org/archives/list/python-dev@pytho=
n.org/message/H3YBK275SUSCR5EHWHYBTJBF655UK7JG/</a><br>
Code of Conduct: <a href=3D"http://python.org/psf/codeofconduct/" rel=3D"no=
referrer" target=3D"_blank">http://python.org/psf/codeofconduct/</a><br>
</blockquote></div></div>
</div>-- <br><div dir=3D"ltr" class=3D"gmail_signature" data-smartmail=3D"g=
mail_signature">--Guido (mobile)</div>

--00000000000075ecc005ab119986--

------------------------------

Date: Thu, 23 Jul 2020 13:37:31 +0900
From: Dong-hee Na <donghee.na92@gmail.com>
Subject: [Python-Dev] Add mimetypes.mimesniff
To: Python Dev <python-dev@python.org>
Message-ID:
	<CA+Z=B_m8wCYz1v=qeVgOe4m=+AMTtAnGChz2sn+-DsPMkxR24g@mail.gmail.com>
Content-Type: text/plain; charset="UTF-8"

Hi,

A few weeks ago, I suggested adding mimetypes.mimesniff on stdlib.
(https://bugs.python.org/issue40841,
https://github.com/python/cpython/pull/20720)

Detecting MIME types well is an important feature and we already have
mimetypes detection library but AFAIK it is not good enough.

Note that some of our stdlib module already use the sniffing algorithm
(e.g imghdr)

The question is how exactly the mime sniffing should be done in terms
of file formats and algorithm. Luckily, WHATWG published the standard
for mime sniffing, and I think we should follow it.
(https://mimesniff.spec.whatwg.org/)

So I created the issue on the bpo and implemented it.
I 'd like to listen to all your opinions :)

-- 
Software Development Engineer at Kakao corp.

Tel: +82 10-3353-9127
Email: donghee.na92@gmail.com | denny.i@kakaocorp.com
Linkedin: https://www.linkedin.com/in/dong-hee-na-2b713b49/

------------------------------

Date: Thu, 23 Jul 2020 11:57:33 +0200
From: Matthias Klose <doko@ubuntu.com>
Subject: [Python-Dev] Re: How to customize CPython to a minimal set
To: "Huang, Yang" <yang.huang@intel.com>,
	"python-dev@python.org" <python-dev@python.org>
Message-ID: <17099346-f1b4-5b85-8693-062b11d39ce2@ubuntu.com>
Content-Type: text/plain; charset=utf-8

On 7/20/20 10:30 AM, Huang, Yang wrote:
> 
> Hi, all
> 
> There is a request to run python in a Linux-based embedded resource constrained system with sqlite3 support.
> 
> So many features are not required, like posixmodule, signalmodule, hashtable ...
> But seems there are some dependencies among the Modules/Parser/Python/Objects/Programs...
> 
> Is there a way to tailor CPython 3 to a minimal set with sqlite3 (the less syscalls the better) ? 
> Is it possible to do that?

CPython comes with the promise of all batteries included, however sometimes it
feels like a complete power plant.  For packaging purposes, most Linux distros
make the decision to ship CPython as a set of runtime packages, and a set of
packages used for development (and building C extensions). Breaking that down
for the Debian/Ubuntu packages comes down to these sizes (unpacked,
uncompressed, on x86_64):

minimal:
5516K   debian/libpython3.9-minimal
5856K   debian/python3.9-minimal

stdlib:
8528K   debian/libpython3.9-stdlib
624K    debian/python3.9

development:
19468K  debian/libpython3.9-dev
25804K  debian/libpython3.9-testsuite
1232K   debian/python3-distutils
668K    debian/python3-lib2to3
548K    debian/python3.9-dev

extra modules:
1648K   debian/idle-python3.9
5208K   debian/python3.9-examples

132K    debian/python3-gdbm
844K    debian/python3-tk

What you don't see from the sizes, are the extra dependencies which add to the
size, e.g. the X stack for tk, or readline/curses/crypto for stdlib.

The "minimal" set may sound nice, however it's not used in practice, because the
set is not well defined, and it's difficult to keep the minimal set as a
self-contained set of stdlib modules, and the minimal usually keeps growing,
never shrinks.

It looks like this "minimal" set is even too much for your purposed, so you
would have to scale down this set even further.

Matthias

------------------------------

Subject: Digest Footer

_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/


------------------------------

End of Python-Dev Digest, Vol 204, Issue 125
********************************************

A  => textwrap/Makefile +6 -0
@@ 1,6 @@
clean:
	rm -rf textwrap

build:
	go build


A  => textwrap/main.go +81 -0
@@ 1,81 @@
package main

import (
	"fmt"
	"os"
	"strings"
	"bufio"
	"regexp"
	"flag"
)

const LENGTH = 40

func print_break(length int) {
	fmt.Printf("%s\n", strings.Repeat("-", length))
}

func print_wrapped(line string, length int, quote string) {
	len_quote := len(quote)
	buffer := quote
	for index, rune := range line[len_quote:] {
		buffer += string(rune)
		if (index + 1) % (length - len_quote) == 0 {
			fmt.Printf("%s\n", buffer)
			buffer = quote
		}
	}
	if buffer != "" {
		fmt.Printf("%s\n", buffer)
	}
}

func main() {
	// Open STDIN as scanner
	_, err := os.Stdin.Stat()
	if err != nil {
		fmt.Printf("%s\n", "cannot read input")
		os.Exit(1)
	}
	input := bufio.NewScanner(os.Stdin)

	// Look for arguments
	var width = flag.Int("width", 80, "target width for output")
	flag.Parse()

	// Compile regular expressions
	re_quote, err := regexp.Compile("^([> ]*)")
	if err != nil {
		fmt.Printf("internal error - %v\n", err)
		os.Exit(1)
	}
	re_break, err := regexp.Compile("^(?:-{5,}|={5,})$")
	if err != nil {
		fmt.Printf("internal error - %v\n", err)
		os.Exit(1)
	}

	// Scan line by line
	for input.Scan() {
		line := input.Text()
		line = strings.TrimSpace(line)

		if len(line) > *width {
			if re_break.MatchString(line) {
				print_break(*width)
			} else {
				quote := re_quote.FindString(line)
				print_wrapped(line, *width, quote)
			}
		} else {
			fmt.Printf("%s\n", line)
		}
	}

	// Check for scanner errors
	if err = input.Err(); err != nil {
		fmt.Printf("internal error - %v\n", err)
		os.Exit(1)
	}
}


A  => textwrap/textwrap +0 -0