Title: SetManager inherits an API surface it doesn't want; decide its real interface · Issue #243 · derek73/python-nameparser · GitHub
Open Graph Title: SetManager inherits an API surface it doesn't want; decide its real interface · Issue #243 · derek73/python-nameparser
X Title: SetManager inherits an API surface it doesn't want; decide its real interface · Issue #243 · derek73/python-nameparser
Description: Summary SetManager subclasses collections.abc.Set, and most of its sharp edges trace back to that choice. 1.3.0 made the inherited surface safe (#238, PR #240); 2.0 should decide whether it should exist. Status after the 1.3.0 release: t...
Open Graph Description: Summary SetManager subclasses collections.abc.Set, and most of its sharp edges trace back to that choice. 1.3.0 made the inherited surface safe (#238, PR #240); 2.0 should decide whether it should ...
X Description: Summary SetManager subclasses collections.abc.Set, and most of its sharp edges trace back to that choice. 1.3.0 made the inherited surface safe (#238, PR #240); 2.0 should decide whether it should ...
Opengraph URL: https://github.com/derek73/python-nameparser/issues/243
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"SetManager inherits an API surface it doesn't want; decide its real interface","articleBody":"## Summary\n\n`SetManager` subclasses `collections.abc.Set`, and most of its sharp edges trace back to that choice. 1.3.0 made the inherited surface *safe* (#238, PR #240); 2.0 should decide whether it should *exist*.\n\n**Status after the 1.3.0 release:** the parts common to both options already shipped as deprecations — `__call__` and missing-member `remove()` emit `DeprecationWarning`, and `discard()` exists as the ignore-missing replacement (#253). The remaining decision here is only the option-A-vs-B question (the operator surface and the `Set` ABC), plus executing the removals.\n\n## Symptoms, all one root cause\n\n1. **Wrong ABC.** `collections.abc.Set` is the *immutable* set interface, yet the class exposes `add()`/`remove()`/`clear()`. The honest ABC for the current behavior is `MutableSet` — but see below for whether we want the ABC at all.\n2. **`remove()` has `discard()` semantics.** It silently ignores missing members, the opposite of what `set.remove` promises. `CONSTANTS.titles.remove('typo-entry')` reports nothing. *(1.3.0: now emits `DeprecationWarning`; `discard()` added.)*\n3. **`__call__` is an escape hatch.** `CONSTANTS.titles()` returns the raw underlying `set`, allowing mutation that bypasses both normalization and the `_on_change` cache-invalidation hook that `Constants` relies on. *(1.3.0: now emits `DeprecationWarning`.)*\n4. **The operator surface exists only by inheritance.** The `Set` mixins inject `|`/`\u0026`/`-`/`^` operators that accept arbitrary iterables and construct results outside the class's normalization invariant. Making them safe took six guarded, operand-normalizing overrides with ~10 `type: ignore` comments (PR #240) — when you fight both typeshed and the runtime ABC to guard methods you never designed, the supertype is wrong.\n\nRelated: the `__contains__` normalization asymmetry is now tracked separately as #244 (1.3.0); whichever option lands here, membership survives unchanged.\n\n## Options\n\n**A. Stop subclassing `Set`; compose over a plain `set` (recommended).**\nExpose exactly what the config use case needs: `add`, `remove`, `discard`, `clear`, `in`, iteration, `len`. The one internal set-algebra use, the `suffixes_prefixes_titles` union, works on the underlying sets directly.\n\n*Pros:*\n- Smallest honest surface — every method exists on purpose, and all mutation is forced through `add()`/`remove()`, so normalization and the `_on_change` cache-invalidation hook can never be bypassed\n- Deletes the six operator overrides and their ~10 `type: ignore` comments outright; nothing inherited is left to audit when Python's ABC mixins evolve\n- No future symptom 5: the class can't grow new behavior without someone deliberately adding it\n\n*Cons:*\n- `isinstance(x, collections.abc.Set)` stops passing; any downstream duck-typed set algebra on config sets breaks\n- `|`/`\u0026`/`-`/`^` on config sets stop working (mitigation: the operators only became *safe* in 1.3.0, so correct usage in the wild is necessarily rare)\n- Loses the ABC's free set-comparison semantics (`==`, `\u003c=` between managers); a deliberate two-line `__eq__` can be added back if wanted\n\n**B. Commit to the surface properly: switch to `MutableSet`.**\nKeep the normalized operators from PR #240, give `remove()` real `KeyError` semantics (`discard()` already shipped in 1.3.0), drop `__call__` (already deprecated).\n\n*Pros:*\n- Familiar set idioms keep working, including `isinstance` checks\n- `MutableSet`'s in-place mixins are a real fit: `__ior__` routes each element through `add()`, so `titles |= [...]` would normalize and fire `_on_change` for free — the inherited behavior finally *aligns* with the class invariant\n- Least migration for anyone using operators today\n\n*Cons:*\n- Keeps the root cause: a large inherited surface where every current and future mixin must be audited against the normalization invariant — `MutableSet` adds more, not less (`pop`, `__iand__`, `__ixor__`, `__isub__`, each of which mutates and must be verified to fire `_on_change`)\n- The typeshed-vs-runtime annotation friction from PR #240 remains and grows\n- Same breaking changes to `remove()`/`__call__` anyway, so B pays most of A's migration cost while keeping the maintenance tax\n\nOption A is preferred: no parser functionality needs set algebra on config sets, and every symptom above came from surface nobody designed. B's strongest point — `__ior__` normalizing for free — fixes one operator by accident of mixin implementation, which is the same coupling that produced the bugs.\n\n## Breaking changes (hence 2.0)\n\n- `isinstance(x, collections.abc.Set)` checks stop passing (option A)\n- `|`/`\u0026`/`-`/`^` on config sets stop working (option A) or keep 1.3.0 behavior (option B)\n- `remove()` of a missing member raises (`discard()`, shipped in 1.3.0, is the ignore-missing spelling)\n- `CONSTANTS.titles()` call syntax removed — iterate or copy instead\n\n\n","author":{"url":"https://github.com/derek73","@type":"Person","name":"derek73"},"datePublished":"2026-07-06T01:43:24.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/243/python-nameparser/issues/243"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:e6b7bc41-29f4-1866-c5d9-24eb03c72768 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 86EE:33585E:43732C0:5F4407D:6A56C459 |
| html-safe-nonce | 41cf944e59e74a8d3b415a618ecf341fbe83d424d7c127ccf88ab999868ae400 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4NkVFOjMzNTg1RTo0MzczMkMwOjVGNDQwN0Q6NkE1NkM0NTkiLCJ2aXNpdG9yX2lkIjoiMzU3NTUyMDc1NzcwOTk4MDc2MSIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 2764032c9984ca64195e511350c9fabce3af1da8e127ff1764f6e3866de38bde |
| hovercard-subject-tag | issue:4815297496 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/derek73/python-nameparser/243/issue_layout |
| twitter:image | https://opengraph.githubassets.com/a3318b9ecba9c0086222c931b354230debe483036d5103077539c54b1dc57a55/derek73/python-nameparser/issues/243 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/a3318b9ecba9c0086222c931b354230debe483036d5103077539c54b1dc57a55/derek73/python-nameparser/issues/243 |
| og:image:alt | Summary SetManager subclasses collections.abc.Set, and most of its sharp edges trace back to that choice. 1.3.0 made the inherited surface safe (#238, PR #240); 2.0 should decide whether it should ... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | derek73 |
| hostname | github.com |
| expected-hostname | github.com |
| None | df99b7164e1d961b639cf5d3cbd6b5ba673edc67bd7e47cdc4746b96099e11ae |
| turbo-cache-control | no-preview |
| go-import | github.com/derek73/python-nameparser git https://github.com/derek73/python-nameparser.git |
| octolytics-dimension-user_id | 18306 |
| octolytics-dimension-user_login | derek73 |
| octolytics-dimension-repository_id | 18353524 |
| octolytics-dimension-repository_nwo | derek73/python-nameparser |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 18353524 |
| octolytics-dimension-repository_network_root_nwo | derek73/python-nameparser |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 3146ab55fc8763ab0e1d2a875e600b3a545bc751 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width