Title: agent: mark derived properties @final to prevent multiple sources of truth · Issue #1868 · usethis-python/usethis-python · GitHub
Open Graph Title: agent: mark derived properties @final to prevent multiple sources of truth · Issue #1868 · usethis-python/usethis-python
X Title: agent: mark derived properties @final to prevent multiple sources of truth · Issue #1868 · usethis-python/usethis-python
Description: What happened A managed_files property in ToolSpec was overridden in a subclass (PytestToolSpec) to add a dynamic value, rather than populating the upstream source-of-truth attribute (meta.managed_files) directly. This created two indepe...
Open Graph Description: What happened A managed_files property in ToolSpec was overridden in a subclass (PytestToolSpec) to add a dynamic value, rather than populating the upstream source-of-truth attribute (meta.managed_...
X Description: What happened A managed_files property in ToolSpec was overridden in a subclass (PytestToolSpec) to add a dynamic value, rather than populating the upstream source-of-truth attribute (meta.managed_...
Opengraph URL: https://github.com/usethis-python/usethis-python/issues/1868
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"agent: mark derived properties @final to prevent multiple sources of truth","articleBody":"## What happened\n\nA `managed_files` property in `ToolSpec` was overridden in a subclass (`PytestToolSpec`) to add a dynamic value, rather than populating the upstream source-of-truth attribute (`meta.managed_files`) directly. This created two independent sources of truth: the `meta` dataclass and the overriding property.\n\n## Root cause\n\nThe `managed_files` property was not marked `@final`, so there was no static signal preventing subclasses from overriding it. When dynamic behavior was needed, the path of least resistance was to override the derived property rather than making the source attribute dynamic.\n\n## Generalised principle\n\nWhen a property derives entirely from a single source of truth (e.g. `return self.meta.managed_files`), mark it `@final` to prevent overrides that fragment the design. Dynamic needs should be addressed by making the source attribute dynamic (e.g. computing it in `meta`), not by bypassing the derived property.\n\n## Resolution\n\n- `ToolSpec.managed_files` was marked `@final` in `src/usethis/_tool/spec.py`.\n- `PytestToolSpec.meta` was updated to dynamically include the test directory's `conftest.py` path, removing the now-invalid `managed_files` override.\n\n## Follow-up\n\nReview other derived properties in `ToolSpec` (e.g. `name`, `rule_config`) to determine if they should also be marked `@final` for consistency.","author":{"url":"https://github.com/nathanjmcdougall","@type":"Person","name":"nathanjmcdougall"},"datePublished":"2026-04-07T18:43:36.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":1},"url":"https://github.com/1868/usethis-python/issues/1868"}
| 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:5378dd31-d3d7-d2a3-fadd-04e0d456d5d3 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | CD5C:3D3A2:1068FDB:160A357:6A4D8912 |
| html-safe-nonce | 1b2abced3cb71a5a66213e42373b650ca750fe8a44e45b7cd5ee9c6b905b0a58 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDRDVDOjNEM0EyOjEwNjhGREI6MTYwQTM1Nzo2QTREODkxMiIsInZpc2l0b3JfaWQiOiIxODY0NDgzMjc2NzgyOTMwMTk0IiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | b42061b39d05dfe4278dec9017cba0791c1d9e7ac05d6400d241dda19c92b741 |
| hovercard-subject-tag | issue:4219863564 |
| 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/usethis-python/usethis-python/1868/issue_layout |
| twitter:image | https://opengraph.githubassets.com/15343c0fe335b86573e6e591ae04acd8a9ae723a95d5193ece40554271d1efb0/usethis-python/usethis-python/issues/1868 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/15343c0fe335b86573e6e591ae04acd8a9ae723a95d5193ece40554271d1efb0/usethis-python/usethis-python/issues/1868 |
| og:image:alt | What happened A managed_files property in ToolSpec was overridden in a subclass (PytestToolSpec) to add a dynamic value, rather than populating the upstream source-of-truth attribute (meta.managed_... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | nathanjmcdougall |
| hostname | github.com |
| expected-hostname | github.com |
| None | 9f8758a3953dfe943439713a6fa4f90d542a3431f10e861ca03dd7f39009f320 |
| turbo-cache-control | no-preview |
| go-import | github.com/usethis-python/usethis-python git https://github.com/usethis-python/usethis-python.git |
| octolytics-dimension-user_id | 216362695 |
| octolytics-dimension-user_login | usethis-python |
| octolytics-dimension-repository_id | 842189705 |
| octolytics-dimension-repository_nwo | usethis-python/usethis-python |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 842189705 |
| octolytics-dimension-repository_network_root_nwo | usethis-python/usethis-python |
| 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 | bffd5484f01713a661b03469b77678f72b6574ed |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width