-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NULL fields and tag removal #919
Conversation
Awesome; I think this is a great compromise—it's possible to mark something as "deleted" for the purposes of tags without exposing One thing I'm slightly worried about is that this makes a store followed by a load "non-transparent". That is, previously, you could count on this:
always printing the same value twice. Now, if I know it seems hacky, but maybe it would be useful to have separate state for indicating whether a field has been deleted? For example, |
I agree that the attributes should be invariant under |
I know it's complicated, but I do think it's incredibly valuable for most fields (ReplayGain being an exception!) to avoid If anything, I think I'd advocate for removing the field entirely from the object when it's unset. For example, I'll continue to ponder also, but this is a surprisingly tricky issue! |
A tricky issue indeed, but I like the proposition in the second paragraph; it’s more pythonesque. Also, we already have a |
Indeed, it could be that the only time you need "guaranteed success" is when formatting to a string, in which case It's a pretty classic trade-off: we should endeavor to make the common case simple while keeping the uncommon case possible. If stuff like |
It think |
Yeah, behaving like a "normal" dict where values go away when they're deleted is a very strong argument—your translation of my example being a perfect instance where an explicit default is nicer. And it is indeed frustrating that flexible attributes are different from fixed attributes in this way; it really would be better if this could be considered an implementation detail. Want to give that refactoring a try? We'll need to look for all cases in the code that assume the existence of fixed attributes and make them explicitly handle absence. |
Sure. What do you think of the following API Model APIActually two APIs: One for programmer input and one for user input.
Types API
Not sure if we need the last two. Print API
Invariantsmodel[key] = value
assert model[key] == value
model.store()
model.load()
assert model[key] == value
assert t.from_sql(t.to_sql(x)) == x
assert t.to_sql(t.from_sql(x)) == x I don’t know if the second line is stricly necessary, or if you we will loose too much flexibility. |
Yes, awesome! I like this direction. Here are a few comments:
I'm excited! This seems like it could be a really clarifying simplification. |
I was planning on just replacing missing values with a single value from
Its not really an optimization since we iterate and parse the modifications. With the new API it becomes for key, val in mods.items():
item.set(key, val) so it’s actually shorter.
In this case I'd lean towards assigning either only
This seems to be the best argument for including the |
Normalization already does handle None (that's most of its purpose). You're right that This conversion/coercion stuff seems pretty orthogonal to the issue at hand, which is deletion—perhaps we should leave it in place for now to make this refactoring step more focused?
We're currently parsing the modifications once and then applying them N times to all the modified objects, without re-parsing. Your example would require re-parsing the modifications for every updated item (i.e., |
You’re right, having an API for assigning default values makes sense. This has lead me to another issue. If don’t think that it makes sense for fixed fields to be missing. Otherwise something like this wouldn’t work as expected item1.update(dict(item2)) The same issue arises with mediafiles which delete
I think they are pretty closely connected: We have to ask ourselves how deleted values are represented internally in the object and in the database—and this is handled by conversion and coercion. I think we should hijack this issue and do right.
It was me. I totally missed the outer loop over objects. We could provide a All this has lead me to the following conclusions.
|
A basic implementation would look like this |
2ded210
to
4b11eed
Compare
fd3f7ee
to
4038b75
Compare
This is now a working implementation. It is a bit hacky in parts but all tests pass. What do you think @sampsyo? |
Hey @geigerzaehler; sorry for being so slow with this discussion! I've been a little busy with real-life stuff lately and therefore managing only to tread water with beets. I am working on formulating a better opinion here; I hope I'll have something smarter to say tomorrow. |
Take your time—it’s quite a handful and I’m still not confident I didn’t break anything. I suggest you ignore the |
Removing a fixed field sets it to NULL in the database. If the model is loaded, all NULL values are passed to `Type.normalize()`, so attributes do not return `None`. Fixes beetbox#886.
We can now set model fields to `None`. `MediaFile` removes the tags corresponding to `None` fields. Hopefully fixes beetbox#157.
All field operations of a model use the methods of a `Type` instance to provide custom handling. Field operations include getting, setting, deleting and conversion to and from SQL. * There is a one-to-one mapping between SQL values and the values stored in the model. Conversion is done with `Type.from_sql()` and `Type.to_sql()`. `NULL` values in SQL are directly mapped to `None` values in the model * `Model[field]` retrieves the internal value stored in the model. For fixed field this either gives a value or `None`. For flex fields this may raise a `KeyError`. * `Model.get(field, default)` uses `Type.default` to replace missing and `None` values * `Model[field] = value` uses `Type.normalize()` to make sure internal values are consisten. Normalization is skipped for `None`. * `Model.set(field, value)` excpects a string value and uses `Type.parse()` to convert and normalize it to an internal value.
This abstraction allows us to generalise a lot of code. It also makes the the `normalize` method, and by extension assignment of values in models, more restrictive. I therefore had to fix a few test cases. For a description of the `model_type` property, see its documentation.
326f6e7
to
2e412be
Compare
Thanks for your patience, and all the awesome work on this refactoring effort! I think I have to advocate again for raising an exception for missing fields instead of returning None. I remain too concerned about the effect of exposing nulls silently throughout beets. In my experience, this kind of silent propagation of bad values can be subtle and pernicious. I just debugged a similar (unrelated) issue today. It's a troublesome anti-pattern for buggy code to not result in an error and instead just do the wrong thing. There's also the other aforementioned issue of symmetry between fixed and flexible fields. It would be extremely valuable to make the two kinds of attributes as similar in behavior as possible—it should be possible for client code to be unaware of the distinction. The delete-means-None semantics breaks this. Even so, there are other good pieces in here that we should add individually— What do you think? I know there's the not-insignificant problem of making MediaFile work with missing fields. Perhaps we can make this a longer-term thing—we can take it one step at a time, maybe starting with MediaFile. |
This is a mere proposal, so I’m perfectly fine if we just pick out the good parts. To be honest I think it has become a little to big to be merged (one sometimes gets carried away 😉) and I would like to split the discussion. There are three parts to this.
The first step should be 2. I will open another PR with some parts from this one and elaborate why I think it’s a good idea. Then, I think, we should continue the discussion about semantics here. Maybe we could start be recording the current state. This involves the the interface for |
Fantastic—I look forward to seeing the ideas for refactoring; we could certainly use some more transparency in the dbcore stuff. Describing the semantics is also critical. I'll start compiling some notes too. If we end up with something useful, we should consider adding it to the Sphinx docs too. |
Is this still relevant? |
It's a super complicated issue and supporting tag deletion would still be great to do someday, but yes, I do think we can close this effort now. |
NULL
in the database