Skip to content
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

Cannot enter a plain integer into an attribute specified as type=str #15

Open
keckler opened this issue Nov 22, 2021 · 6 comments
Open

Comments

@keckler
Copy link

keckler commented Nov 22, 2021

If one specifies an attribute to be of type str, they cannot then enter a valid string that looks like an integer to that attribute in their stream.

For example:

In [1]: from yamlize import Object, Attribute

In [2]: class Pet(Object):
    ...:     name = Attribute(type=str)
    ...:     age = Attribute(type=str)
    ...:

In [3]: lucy = Pet.load(u'''
    ...: name: Lucy
    ...: age: 8
    ...: ''')

YamlizingError: Coerced `<class 'int'>` to `<class 'str'>`, but the new value `8` is not equal to old `8`.
start:   in "<unicode string>", line 3, column 6:
    age: 8
         ^ (line: 3)
end:   in "<unicode string>", line 3, column 7:
    age: 8
          ^ (line: 3)

Instead, if the user wants to supply (what is ostensibly) an int to a string attribute, they have to use quotes around the value. But this is strange, because quotes are not needed in other cases. For instance, if a user supplies something that obviously looks like a string to a string attribute, quotes are not needed:

In [1]: from yamlize import Object, Attribute

In [2]: class Pet(Object):
    ...:     name = Attribute(type=str)
    ...:     age = Attribute(type=str)
    ...:

In [3]: lucy = Pet.load(u'''
    ...: name: Lucy is my name and this is obviously a string
    ...: age: "8"
    ...: ''')

In [4]: lucy.name
Out[4]: 'Lucy is my name and this is obviously a string'

It would be nice if Yamlize would fully recognize that the type=str parameter being passed to Attribute means that whatever the user provides, even if it looks like an int or anything else, it should be coerced to a str.

At the least, the printed error could be changed to be more meaningful. Right now it says:

but the new value `8` is not equal to old `8`

and this is confusing because the two printed values look exactly the same.

@SimplyKnownAsG
Copy link
Owner

Hm. This is an interesting edge case, though I believe it is consistent with the standard. YAML is typed. For example, null is not equal to "null". Also, if I remember correctly, yes for some crazy reason used to be a boolean.

The excerpt of the error message from your comment is missing the start.

Coerced `<class 'int'>` to `<class 'str'>`

Comments:

  1. Can you override the type to be something that considers a string and an integer to be equal?
  2. Can you propose a better error message? Is the issue with the term coerce? It is maybe a little esoteric.

@keckler
Copy link
Author

keckler commented Nov 24, 2021

I suppose my confusion lies at the fact that sometimes the data can be casted from one type to another using the type= parameter, while other times it cannot. For instance, in your README you give the example:

>>> from yamlize import Object, Attribute
>>>
>>> class StronglyTypedThing(Object):
...
...     my_int = Attribute(type=int)
...     my_float = Attribute(type=float)
...     my_str = Attribute(type=str)
...
>>>
>>> stt2 = StronglyTypedThing.load(u'''
... my_int: 81.0      # this will be cast to an integer
... my_float: 92.1
... my_str: another boring message
... ''')
>>> stt2.my_int
81

In that case, a float is cast into an integer. So the type=int is not used as a validator, it is used to cast.

But in the case that I provided in my initial comment, type=str is used as a validator, not to cast. I guess this behavior is a little confusing because it seems arbitrary. It is confusing because it conflicts with the casting behavior that I just showed. According to the logic of the error message that is printed in my original comment, I would expect the example that I show in this comment to print an error like:

YamlizingError: Coerced `<class 'float'>` to `<class 'int'>`, but the new value `81` is not equal to old `81.0`.

But instead, in my original example, the casting takes place and you get an error message that shows two values which look exactly the same. It does not make it clear that the error resulted because the casting cannot be performed in this particular case, and this is not obvious otherwise because sometimes the casting can be performed without error.

Does that make sense?

With my use case right now, I can get around this easily enough just by using quotes. I just wanted to report the confusing behavior.

@SimplyKnownAsG
Copy link
Owner

SimplyKnownAsG commented Dec 1, 2021

Yup.. that's a good point.

I think I lean more toward being strict than lenient. YAML is nice because it is mostly human maintainable. I believe I originally added the cast for int -> float conversion, because humans don't care about decimals when the float is a round number, and writing !!float 1 is not helpful.

So.... Now you've got me thinking. Is the right approach to create a yamlize.FloatLike object? I think we might be able to get rid of the cast validation which I believe would help reduce the inconsistency which you've pointed out. Are you working with ARMI friends? I'd have to bump the version for sure.

@keckler
Copy link
Author

keckler commented Dec 6, 2021

I think what you propose could be a good approach, but I'm not exactly clear on what you were thinking. My interpretation is that this would allow a user to do something like this without ambiguity:

>>> import yamlize
>>> from yamlize import Object, Attribute
>>>
>>> class Thing(Object):
...
...     my_float1 = Attribute(type=yamlize.FloatLike)
...     my_float2 = Attribute(type=yamlize.FloatLike)
...
>>>
>>> t = Thing.load(u'''
... my_float1: 81.0      # this is a float and will be interpreted as such
... my_float2: 92      # this is an int but will be interpreted as a float
... ''')
>>> t.my_float1
81.0
>>> t.my_float2
92.0

If that is indeed what you were thinking, perhaps also a yamlize.StrLike object which would enable the following to be done unambiguously:

>>> import yamlize
>>> from yamlize import Object, Attribute
>>>
>>> class Thing(Object):
...
...     my_str1 = Attribute(type=yamlize.StrLike)
...     my_str2 = Attribute(type=yamlize.StrLike)
...     my_str3 = Attribute(type=yamlize.StrLike)
...
>>>
>>> t = Thing.load(u'''
... my_str1: abcd      # this looks like a str and will be treated as such
... my_str2: 92.1       # this is a float but will be interpreted as a str
... my_str3: 666        # this is an int but will be interpreted as a str
... ''')
>>> t.my_str1
'abcd'
>>> t.my_str2
'92.1'
>>> t.my_str3
'666'

I might be misunderstanding what you are suggesting, though, so please let me know if my interpretation is off.

@keckler
Copy link
Author

keckler commented Dec 6, 2021

But now that I type that out... I doubt that is what you were thinking because it moves further away from the underlying assumptions of YAML (i.e. that 1 is an int, '1' is a str, and '1.0' is a float). So maybe you could explain in a little more detail what you had in mind? Are you suggesting to get rid of the type=... option in the Attribute class entirely?

@SimplyKnownAsG
Copy link
Owner

So, maybe you could explain a little more detail what you had in mind?

Um. I think I like the StrLike implementation you're describing. Off hand, I'm not sure how to do it. It would be pretty confusing for another YAML interpreter to read, but I guess that's not our problem.

I doubt that is what you were thinking because it moves further away from the underlying assumptions of YAML ...

The *Like classes have special behavior. The underlying int, float, str classes behave normally. That makes it explicit, and I like explicit. This follows the Open/Closed principle.

Are you suggesting to get rid of the type=... option in the Attribute class entirely?

No. Definitely want to keep that. It's been so long, but I'm pretty sure this is with regard to "I think we might be able to get rid of the cast validation which I believe would help reduce the inconsistency which you've pointed out." That statement is talking about the check that exists to ensure that after casting from the yaml interpreted type to the Attribute's specified type, the objects are equivalent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants