Skip to content

Plans for packaging.metadata #570

Open

Description

@brettcannon

Activity

  1. brettcannon commented on Jul 10, 2022

    @brettcannon
    MemberAuthor

    @abravalheri @pfmoore when you say you want more lenient handling, what does that mean to you? I think we are going to want to use exception groups so that all issues can be gathered and reported to the user instead of just the first error (@pradyunsg should we take a dependency on exceptiongroup or have our own ExceptionGroup shim class?). That leads to a possible solution where there's an option of returning the exception group so the caller can choose how to handle the problems (with all the relevant data saved to the exceptions). What we could then do is something like:

    @overload
    def from_email_headers(..., raise_exc=True) -> Self: ...
    
    @overload
    def from_email_headers(..., raise_exc=False) -> (Self, ExceptionGroup): ...

    In the end the only code necessary to accommodate this is some branching at the end of the function:

    if raise_exc:
        if exception_group:
            raise exception_group
        else:
            return metadata_
    else:
        return metadata_, exception_group

    I contemplated only having the lenient return type, but I realized that's not a great API if you forget to raise that exception group if that's what you truly want compared to getting a TypeError that there are not enough values to unpack for metadata_, exc_group = Metadata.from_email_headers(..., raise_exc=True).

    As for what exceptions are returned within the exception group, I was thinking of all of them being InvalidMetadata which stores the core metadata name, value, and the reason why it failed (either a string or setting __cause__ as appropriate, e.g. InvalidSpecifier).

    BTW I am side-stepping if there would be such a thing as an unrecoverable error no matter what, like leaving out the metadata version, project name, etc.

  2. pfmoore commented on Jul 10, 2022

    @pfmoore
    Member

    It means that I want to be able to parse metadata with (for example) legacy versions and not have an InvalidVersion error raised, but instead get the value as a string. Returning exceptions rather than raising them is of no value for my use cases.

    For me, there are two parts to parsing metadata (from email, for example):

    1. Parse the email format into name-value pairs for the various metadata fields. At this stage, the "value" is a simple string value (or list of such values).
    2. Validate the values which have a defined format, and convert them into structured types as appropriate.

    I consider "lenient" parsing to be just doing step 1, so that all possible values can be processed, regardless of validity. Strict parsing involves doing step 2 as well, and rejecting invalid values.

    I tend to think that "lenient" parsing is important, on the basis of the "be lenient in what you consume and strict in what you produce" principle. When reading a file, you want to do whatever you can to get from the raw data to at least a name-value structure, so you can do more detailed processing on a simple but structured representation. Maybe you'll want to reject invalid values, maybe you'll want to spot them and report them, maybe you'll want to analyze patterns in what sort of deviations from valid values are present. The point is, being able to get the raw data into an internal form is key here. Before you write data, though, you want it to be in a valid form (usually - there are use cases for simply reading and re-writing data and allowing invalid values to pass through unmodified, but they are specialised). So you store the data in a validating internal data structure, that catches any dodgy values as early as possible, and then write it out knowing you're only writing valid data to the external form.

    Being able to read data from an external form, and then convert it to the validating internal structure (with errors being flagged at conversion time) may well be the more common pattern when working with a single metadata file. But when working with metadata files in bulk, the pattern is (IMO) much more likely to be "read in bulk without validation, then validate in bulk".

    My pkg_metadata project is focused on only doing step 1, with the dictionary format being the "unvalidated key-value" data structure. I would still happily contribute it to packaging, with step 2 then being about converting the dictionary format to the Metadata class. And probably some convenience functions to do steps 1+2 together, for people who don't need the separation. But if you don't agree with my split into steps 1 and 2, I can keep pkg_metadata as a separate project for people who need that extra flexibility (I'll probably add something to do step 2, converting to the packaging Metadata class once it has stabilised, in that case).

  3. abravalheri commented on Jul 11, 2022

    @abravalheri
    Contributor

    @abravalheri @pfmoore when you say you want more lenient handling, what does that mean to you?

    I agree with Paul's definition of not raising an error. It would make sense to parse whichever fields are possible on a best effort basis and populate the parsed information into the structured object. A tuple with an exception group also make sense, so a build-backend for example could use it to produce its own warnings.

    In my mind, some examples of things that are not correct but can be parsed on "a best effort basis" are:

    1. "long text" fields (e.g. Description) when the indentation does not strictly follow the " " * 7 + "|" rule (e.g. when the indentation is " " * 8).
    2. "outdated" forms of a field's key or value, when the metadata version is inconsistent, e.g.:
      • PEP 314 => PEP 345 renaming, such as: Requires => Requires-dist
      • Requires-Dist using a parenthesised version without operator (e.g. dep (1.2))
    3. Multi-line Summary (we can simply join multiple lines when parsing the value).

    We can also ignore non-standard fields (e.g. License-Files).

    (I tried to capture some of this "permissiveness" on the implementation of #498. Probably not the best code in the universe, but please feel free to steal if you find something useful).

  4. merwok commented on Jul 11, 2022

    @merwok

    (Just to avoid confusion: Requires from PEP 314 is not just an old name for Requires-Dist, because it expected an importable name for value, not a project name. So these are really two separate fields, one obsolete (can be discarded) and one current.)

  5. pfmoore commented on Jul 11, 2022

    @pfmoore
    Member

    To the list of "things that are not correct but may not be a problem", can I add

    • Invalid values (e.g., incorrectly formatted specifiers) in any field, when you're not interested in that value. Very few applications in my experience actually care about the actual value of (for example) the "Author" and "Author-Email" metadata read from an existing distribution, but wanting to copy that data unaltered (or even just to display it unaltered!) is a fairly reasonable thing to hope for.
  6. brettcannon commented on Jul 12, 2022

    @brettcannon
    MemberAuthor

    Returning exceptions rather than raising them is of no value for my use cases.

    Fair enough, but the exceptions would still cause you to have access to the invalid data via their raw strings. So I think this is coming more down to you want strings all the time while I want to provide structured data where possible. I acknowledge both approaches have legitimate uses, and so I will stop bugging you about this. 🙂

    A tuple with an exception group also make sense, so a build-backend for example could use it to produce its own warnings.

    👍

    We can also ignore non-standard fields (e.g. License-Files).

    I honestly wasn't planning to strictly check non-standard field names anyway. The biggest chance of failure here is something packaging itself can't parse (e.g. the dep (1.2) example). But hopefully returning the incorrect strings for introspection for tools decide what extra effort they want to put in is flexible enough.

  7. pfmoore commented on Jul 12, 2022

    @pfmoore
    Member

    Fair enough, but the exceptions would still cause you to have access to the invalid data via their raw strings.

    Maybe. But my core use case looks something like this:

    structured_metadata = {filename: msg_to_metadata(get_metadata_bytes(filename) for filename in list_of_filenames}
    

    With a string-only ("phase 1 only") API, that "just works". Validation can happen in a later stage, when processing a particular file. How would you do that with an API that only does the full conversion to a validated structure, returning exceptions when needed, in such a way that I can later write the data out unmodified?

    The sort of use case I have in mind is processing a bunch of wheels off PyPI (using HTTP range requests to only pull the METADATA file out of the wheel and not download the whole lot, running parallel downloads for speed), processing the metadata to report "interesting statistics", and then saving the structured data to a database (where it'll all be serialised back to strings) for further future analysis. That structured_metadata dictionary is the in-memory data structure I'd use for that processing and then to serialise from.

    Anyway, like you say, both approaches have legitimate uses, so we can agree to differ. I'm mostly just a little sad because you will have to internally read the metadata into strings before converting to types like Version and Requirement, and yet you prefer not to expose that intermediate state meaning that people wanting string-only values have to reimplement that code. But I have that reimplemetation in pkg_metadata, so there's no more extra work now anyway.

  8. pradyunsg commented on Jul 13, 2022

    @pradyunsg
    Member

    We don't need to have a single object represent both "raw" and "validated + parsed" metadata, FWIW.

    • Provide a "low level" parse function, that just does the email.FeedParser stuff, from the raw underlying string/bytes and provides a value (either the Message as is or a different class, StringValuedMetadata or something, that's aware of keys and does a lookup on attribute access mirroring the Metadata class's attributes).
    • Metadata will perform the validation during initialisation. It would also grow a classmethod to allow initialising through that in addition to being able to be initialised from the raw string/bytes as well. We'll keep the exception groups as well.

    This would cover both use cases, at the cost of an additional layer of indirection which is necessary to deal with the two sets of user needs here.


    That's a design if we want to solve both use cases in a single module. I think it's worthwhile, but I won't make that call unilaterally of course. :)

    should we take a dependency on exceptiongroup

    Meh... It depends on flit_scm for its own build, which in turn uses setuptools_scm and flit_core. I'm ambivalent on this because it's gonna mean that we have redistributors feel more pain and, possibly, enough of it that they come complaining about it. :)

  9. pfmoore commented on Jul 13, 2022

    @pfmoore
    Member

    This would cover both use cases, at the cost of an additional layer of indirection which is necessary to deal with the two sets of user needs here.

    That was my suggestion, with the "json-compatible dictionary" format specified in the metadata PEP as the "raw" form.

  10. brettcannon commented on Jul 13, 2022

    @brettcannon
    MemberAuthor
    • that just does the email.FeedParser stuff

    So just somewhat of a one-off for email headers, or would we eventually want it for pyproject.toml as well? I guess the code can guide us as to whether it's easier to normalize to JSON for everything and have Metadata take that in to do the conversion to the appropriately types and do validation.

    • either the Message as is or a different class, StringValuedMetadata or something

    I would assume we would have a TypedDict defined for the JSON dict format.

    When we were originally discussing dict vs. data class, I was thinking "one or the other," not "both". That does simplify the story and structure of the overall approach by normalizing to JSON as the low-level input for Metadata, but at the cost of more code to manage the same data, just in different formats. But since we don't exactly change the core metadata spec that often the duplication shouldn't be burdensome.

    This would cover both use cases, at the cost of an additional layer of indirection which is necessary to deal with the two sets of user needs here.

    That was my suggestion, with the "json-compatible dictionary" format specified in the metadata PEP as the "raw" form.

    Does that work for you as well, @abravalheri ? I would assume we would ditch the overrides on Metadata.from_email_headers() in preference to this two-step process of getting from METADATA to Metadata.

    @pfmoore might actually get what he wants. 😉

  11. abravalheri commented on Jul 14, 2022

    @abravalheri
    Contributor

    Does that work for you as well, @abravalheri ? I would assume we would ditch the overrides on Metadata.from_email_headers() in preference to this two-step process of getting from METADATA to Metadata.

    It is hard to give a informed opinion before working with the API, so please take the following with a grain of salt:

    I don't mind having to do more steps to use the API, but I would say that for the use case I have in mind1, from_XXX(..., raise_exc=False) doing "best effort" parsing and returning a tuple with a (potentially incomplete) structured metadata object is what I am really looking for.

    I don't think a build backend fits in the category of consumers that want to copy the data unaltered (or even just to display it unaltered). We can consider that a backend want to parse the PKG-INFO into an structured object, use this object for the build and then use the API to create a valid METADATA file...

    So if the only way of not running into errors is to use the a raw TypedDict, the backend would have to re-implement the logic that converts the TypedDict into an structured metadata object...


    A minor comment about a previous topic of discussion:

    The biggest chance of failure here is something packaging itself can't parse (e.g. the dep (1.2) example). But hopefully returning the incorrect strings for introspection for tools decide what extra effort they want to put in is flexible enough.

    If there are limitations on what packaging can parse, that is fine. An backend using this API can decide to ignore all the fields that are not essential for a --no-binary installation (e.g. author, summary) and try to do something about the fields that are essential (e.g. requires-dist) based on the raw value in the ExceptionGroup. The important part is that a (potentially incomplete) structured data object is returned, so the backend does not have to re-implement the entire conversion logic.

    By the way, if packaging cannot parse Requires-dist: dep (1.2), this also means that this API will not be able to parse Metadata-Version <= 1.2, right?

    Footnotes

    1. a build backend parsing a PKG-INFO file from an old sdist to produce a wheel to be used by the installer. I am assuming this is necessary to support pip install --no-binary or when no wheel is available in the package index for an specific operating system/processor architecture. ↩

  12. brettcannon commented on Jul 14, 2022

    @brettcannon
    MemberAuthor

    I don't mind having to do more steps to use the API, but I would say that for the use case I have in mind1, from_XXX(..., raise_exc=False) doing "best effort" parsing and returning a tuple with a (potentially incomplete) structured metadata object is what I am really looking for.

    OK, so you would appreciate the exception group with all the failed metadata still being returned.

    So given email headers as the input, we have:

    1. Get me the data out of email headers, but don't process anything (Paul)
    2. Do your best to process the data into some structure, but give back the left-overs (Anderson)
    3. Be strict about the data (... no one?)

    Maybe I have been thinking about this the wrong way by trying to generalize everything such that every metadata format should be treated the same instead of just treating every format as unique (and to hell with the complexity of it all)? Thinking about the formats and reading/writing them, you have:

    1. pyproject.toml (strict read, no write)
    2. PKG-INFO (loose read, strict write)
    3. METADATA (loose read, strict write)
    4. JSON/metadata.json (??? read, strict write)
    5. Direct object creation (strict read in the sense of typing will make sure you don't mess it up)

    So maybe from_email_headers() should only return (Self, dict[str, str]) with as much processed data as possible in Self and a dictionary with everything that wasn't used/usable (with the keys all normalized to lower-case)? I don't know what we would want to do with data-related errors like something in Dynamic that actually shouldn't be there in the PKG-INFO case (let it through, return it in the dict?). But since the tools reading PKG-INFO/METADATA seem to want leniency, it might be best to just embrace it and let tools decide if they care whether the left-overs dict wasn't empty?

    By the way, if packaging cannot parse Requires-dist: dep (1.2), this also means that this API will not be able to parse Metadata-Version <= 1.2, right?

    It can parse dep (>=1.2) just fine; the lack of version specification is the issue.

    >>> from packaging import requirements
    >>> requirements.Requirement("dep (>=1.2)")
    <Requirement('dep>=1.2')>
    >>> requirements.Requirement("dep (1.2)")
    Traceback (most recent call last):
      File "/tmp/temp-venv/lib/python3.9/site-packages/packaging/requirements.py", line 102, in __init__
        req = REQUIREMENT.parseString(requirement_string)
      File "/tmp/temp-venv/lib/python3.9/site-packages/pyparsing/core.py", line 1141, in parse_string
        raise exc.with_traceback(None)
    pyparsing.exceptions.ParseException: Expected string_end, found '('  (at char 4), (line:1, col:5)
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/tmp/temp-venv/lib/python3.9/site-packages/packaging/requirements.py", line 104, in __init__
        raise InvalidRequirement(
    packaging.requirements.InvalidRequirement: Parse error at "'(1.2)'": Expected string_end
    >>> requirements.Requirement("dep (==1.2)")
    <Requirement('dep==1.2')>
  13. dstufft commented on Jul 15, 2022

    @dstufft
    Member

    PyPI would want strict validation FWIW


    The way I'd implement this is that you have a variety of functions that read a file and turn it into a "standard" shaped dictionary with primitive keys / values. No validation would be done at this point so it's completely YOLO, the only different between this and a hypothetical json.loads() is that any differences in serialization are papered over so that all of the different metadata methods return a "standard" dictionary. Call these the parse_FORMAT function, so like parse_json, parse_email, parse_toml, etc.

    So, they'd have a function signature that looked something like:

    from typing import TypedDict, Optional
    
    class ParsedMetadata(TypedDict):
        name: Optional[str]
        # Everything is optional, everything is primitive types.
    
    
    def parse_FORMAT(content: str) -> ParsedMetadata:
        pass

    There are a few subtle things here that I would suggest:

    • If a field is missing, or otherwise unable to be read from the raw format (see next point) they are just omitted from the returned dictionary rather than generating some kind of error.
    • Part of papering over different serializations will be parsing types, for instance if Project-URL is chosen to be a dictionary, then pase_email will need to convert ["Key, URL"] into {"Key": "URL"} 1.
    • Extra fields that we don't know about are omitted from the returned value 2.

    Then a classmethod can be added to the existing Metadata class, like:

    class Metadata:
        # ...
    
        @classmethod
        def from_parsed(cls, parsed: ParsedMetadata) -> Metadata:
            pass

    This method consumes the parsed value, validates all of the metadata, then returns the validated Metadata object.

    I wouldn't actually have the raises keyword, I would just have this method always raise, but attach the incomplete result to the raised exception. That satisfies the same use cases but avoids a boolean parameter and the need to deal with overloading return types.

    I think this has a lot of nice benefits:

    • People who want something that will do as little parsing as possible, but still provide some semblance of a unified type, if you don't care about a unified type, then you're best just reading the files directly 3.
    • We cleanly separate concerns from parsing the file contents and massaging them into a unified shape from validating the primitive types and turning them into more complex, specific objects.
      • This has the added bonus that new formats can be implemented/tested outside of packaging or even custom formats.
    • We handle all 3 "levels" of validation that people have expressed an interest in.

    For someone that wants the strict validation, they would ultimately call this like:

    meta = Metadata.from_parsed(parse_email(content))

    We could offer some shortcut methods, that do:

    class Metadata:
    
        @classmethod
        def from_email(cls, content: str) -> Metadata:
            return cls.from_parsed(parse_email(content))

    Which then gives people the option for the best of both worlds.

    Footnotes

    1. This means that technically some malformed metadata fields will be unable to be read using these methods since they'll fail to parse and be omitted. We should strive to make our parsing as lenient as we reasonably can (for instance, the Project-URL example, make sure we work in cases where the URL or key is missing, the comma is missing, etc to produce something). If these lenient methods aren't lenient enough, then you'll have to implement it yourself (but the layering here means if your implementation emits a ParsedMetadata it can be used in the other methods). ↩

    2. We could allow this; my biggest concern would be that we can't enforce any kind of typing in it which may be a footgun for consumers of this API. Maybe an option would be a _extra key that is just dict[str, Any] or have the parse_FORMAT return two values, ParsedMetadata and the left over items from the dict. ↩

    3. This is because there is no such thing without massaging the data to have a return type that returns the same data for every serialization format. Formats like email are going to treat everything as strings, or list of strings, but a format like json or toml will support other primitive types, so they can't return str unless we cast them to string, which would be silly. ↩

  14. 75 remaining items

  15. nilskattenbeck-bosch commented on Jun 20, 2023

    @nilskattenbeck-bosch

    May I also suggest adding a small information box to the documentation as to why the function is called parse_email. By now I read through the corresponding PEPs and specifications to understand that the metadata is serialized just like email headers and parsed using that module though at first it was really unnatural and felt like I was using the wrong function and that it would leak abstraction detail. If other parse_FORMAT will be introduced then this naming makes sense though having an assurance that this is the correct method and why it is named that way would be assuring.

  16. brettcannon commented on Jun 20, 2023

    @brettcannon
    MemberAuthor

    May I also suggest adding a small information box to the documentation as to why the function is called parse_email.

    I would go even farther and say you can propose a PR to add such a note. 😁

  17. brettcannon commented on Aug 18, 2023

    @brettcannon
    MemberAuthor

    With my work for reading metadata now complete, I'm personally considering my work on this issue done. Hopefully someone else will feel motivated to do the "writing" bit, but as someone who has only written tools to consume metadata I don't think I'm in a good position to drive the writing part of this.

  18. abravalheri commented on Aug 23, 2023

    @abravalheri
    Contributor

    With my work for reading metadata now complete, I'm personally considering my work on this issue done.

    Thank you very much @brettcannon for working on this.

    Hopefully someone else will feel motivated to do the "writing" bit, but as someone who has only written tools to consume metadata I don't think I'm in a good position to drive the writing part of this.

    There was at least one previous PR (#498, probably more) that tried to address the "writing" capabilities for metadata, however these were closed because probably they don't fit into the long term vision that the maintainers would like for the API/implementation (which is a very good thing to have in a project).

    Would it be possible (for the sake of anyone that intends to contribute with the "writing" part) to have a clear guideline on how we should go about it? (This question is targeted at all packaging maintainers, not only Brett 😝).

    I am just concerned that throwing PRs at packaging without a clear design goal/acceptance criteria will just result on PRs getting closed and work hours being lost.

  19. brettcannon commented on Aug 23, 2023

    @brettcannon
    MemberAuthor

    Would it be possible (for the sake of anyone that intends to contribute with the "writing" part) to have a clear guideline on how we should go about it?

    I think the first question is what are the requirements for the feature to write metadata? Do you need to minimize diffs for METADATA output that you originally read from? Or should the output be consistent across tools (i.e., field order is hard-coded)?

    The other question is whether tools will build up the data in a TypedDict via RawMetadata and then use Metadata to do the writing, or do you make it so you build up the metadata in Metadata itself? And if you do the latter, do you do validation as you go, or as a last step before creating the bytes? I will admit I totally punted on this one based on how I coded Metadata and it currently lends itself to building up via RawMetadata as the descriptor I used is a non-data descriptor and thus the caching does not lend itself to attribute assignment.

    There's also how much you want to worry about older metadata versions? Do you let the user specify the metadata version you're going to write out and thus need to do fancier checks for what's allowed?

    For me, I think consistency in output is more important than keeping pre-existing METADATA files from having a smaller diff (since long-term that will happen naturally). As for the API, I think that's up to people like you, @abravalheri , who author build back-ends since you will be the folks using any construction API. But what I will say is if you build up a RawMetadata dict then an API to generate the bytes is surprisingly straightforward to code up (define the descriptors in the order you want them written out, iterate over Metadata for all instances of _Validator, get the values, and then introspect on the results to know how to write out the format appropriately for strings vs lists vs dicts).

  20. dstufft commented on Sep 29, 2023

    @dstufft
    Member

    I'd also mention that everything I've done with METADATA has primarily been consuming them not emitting them, but my intuition is that it should have these properties:

    • Don't worry about diffs produced in round tripping, I suspect that use case is going to be extremely niche (but I could be wrong) and I wouldn't spend a lot of time worrying about it.
      • I think at the extreme edges of this, you basically need a special parser that preserves the original formatting, which the email parser doesn't have nor does any of the stdlib parsers, making it a much bigger deal to do.
    • I would have the write APIs mirror the same pattern as the read APIs, in that:
      • You can work with a Metadata object, and that object maintains the requirement that you can only get or set valid metadata to it. This Metadata object has a Metadata().to_raw() function that returns a RawMetadata.
      • You can work with a RawMetadata object, which is a dict at runtime or a TypedDict at type check time, and there is minimal validation here (basically just that primitive types match).
      • We have a write_email, function that takes a RawMetadata and returns the serialized bytes, probably also any leftover fields that it didn't know how to serialize.
      • We have Metadata().to_email() functions that act as a wrapper around Metadata().to_raw() and write_email.
    • I would let the user set a metadata version, but if they don't set one then use a default value.
      • Of course validating that the fields used exist in the given metadata version.
      • A good question is whether the default should be the latest version or if we should attempt to do something like the minimal version that contains the fields that are in use.
        • Personally I think we should do the latest version, but with the caveat that maybe whenever a brand new version is released, we should hold off on setting the default to that for some period of time to give tools time to catch up? But maybe we would have to implement version detection then incase someone uses a new version, so it might be simpler/better to just always emit the latest by default.
  21. dstufft commented on Sep 29, 2023

    @dstufft
    Member

    Oh ugh, emitting probably also has to answer the newlines in fields question, and possibly the other issues I raised with the spec earlier up thread.

  22. dstufft commented on Oct 9, 2023

    @dstufft
    Member

    Just a note, I have a PR up now to Warehouse (pypi/warehouse#14718) that switches our metadata validation on upload from a custom validation routine implemented using wtforms to packaging.metadata.

    The split between Metadata and RawMetadata was super useful, since we (currently) have the metadata handed to Warehouse as a multipart form data on the request, I was able to make a custom shim to generate a RawMetadata from that form data, and then just pass that into the Metadata.from_raw().

    I'm still giving it a through set of manual testing, but so far it looks like other than a few bugs/issues that fell out (#733, #735) integrating it was relatively painless. Of course the real test will be when it goes live and that wide array of nonsense that people upload starts flowing through it.

    We're not yet using the actual parsing of METADATA files yet (though that is planned), so this is strictly just the validation aspect of the Metadata class that we're currently using.

  23. pradyunsg commented on Oct 9, 2023

    @pradyunsg
    Member

    Do you think it might make sense to keep both the old code and the new code paths running for a bit on warehouses' end, with the results of the old code path being returned?

    i.e.

    def upload(...):
        try:
            new_result = new_upload(...)
        except Exception:
            new_result = ...
        old_result = old_upload_handler(...)
        if new_result != old_result:
           log.somehow(old_result, new_result)
        return old_result
  24. cosmicexplorer commented on Dec 21, 2025

    @cosmicexplorer
  25. brettcannon commented on Jan 6, 2026

    @brettcannon
    MemberAuthor

    @cosmicexplorer I've hid your comment due to its accusatory tone. If you want to rephrase it then I will happily unhide it.

    The most important point about this PEP is that it contains a baldfaced actual falsehood and commits it to the historical record.

    I'm sorry you feel that way, but none of the pip maintainers ever suggested what I said in the PEP was wrong. So if you felt pip install --report constituted a lock file format, that view was either not shared with or communicated to anyone else who participated over the multiple years that the lock file spec was worked on publicly.

    Is the packaging library @brettcannon's personal ideological project?

    No. I am only one of the maintainers (and not even the most active).

  26. PhilienTaylor commented on Jan 10, 2026

    @PhilienTaylor
  27. notatallshaw commented on Jan 10, 2026

    @notatallshaw
    Member

    Hi @PhilienTaylor, I'm putting my moderator hat on here, please don't use accusatory language like this:

    If you are a maintainer, can you act like one

    Everyone here has agreed to follow a code of conduct: https://policies.python.org/python.org/code-of-conduct/ by participating here: https://github.com/pypa/packaging?tab=coc-ov-file

    We all agree to this CoC so we don't get into long, heated discussions, misassigning intentions, building resentment, and other dysfunctional communication. Such communication breaks down constructive discussion and the sense of community that motivates people to volunteer on these sorts of projects.

    I am going to mark your comment off topic, you are welcome to continue contributing in a constructive way, but if such accusatory language continues I will lock this conversation to packaging maintainers only.

    I'm shocked and appalled, so I would expect people to act this way if I was told my package management exposed me to code injection by uncleaned email addresses or headers, for example. This has nothing to do with other vulnerabilities they mentioned, too

    This may well be somewhere in @cosmicexplorer's post, but I estimate there are over two dozen distinct points in the post, and I did not and do not have time to read it all. "Tone" aside, such a lengthy post covering so many topics is not helpful or constructive to a discussion and leads to fragmentation and confusion. I often appreciate his insights, but such communication on a forum like this is not constructive.

    Most of the points I read seemed to be centered on the accuracy of PEP text and this is the wrong forum for that discussion. If anyone wishes to update the PEP text, or packaging guide, they should raise a PR on one of https://github.com/python/peps or https://github.com/pypa/packaging.python.org and if it's non-trivial start a discussion on https://discuss.python.org/c/packaging/14 to achieve a consensus on the change in text.

    If there is some vulnerability you are particularly concerned about, please post the details describing it in a dedicated issue, or if there is already an issue that people have not posted an update in a long time, a single post to bring attention back to it may be warranted. Of course you are welcome to submit PRs to packaging and I will do my best to engage with and review them.

  28. FFY00 commented on Jan 10, 2026

    @FFY00
    Member

    I could be given a package injection via a crafted METADATA file, customized for me as a wheel-api user or provided to me in a mistaken package I adopt. Did I understand that right? @cosmicexplorer?

    I don't understand how "tricking" a package manager to inject a dependency is a real world vulnerability, unless you are auditing sources, but not the output of the resolver.
    I am not saying that it isn't an issue we should fix, it totally is, but I believe it has been largely misrepresented. If it was indeed a real world vulnerability, disclosing it like this would be incredibly irresponsible, not to mention hands down unprofessional to do so in such a fashion.

    I am no stranger to trying to make changes to the Python packaging ecosystem as an outsider, so that experience of Danny's does ressonante with me, and I am truly sorry he experienced it. However, how he chose to communicate his frustations, resorting to personal attacks, is not an acceptable way to communicate with fellow community members.

    With no prejudice, I hope Danny re-evaluates his stance, and we can have an actually productive conversation. Though, as has been pointed out already, https://discuss.python.org would be the correct place to discuss most of the raised concerns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions