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

Way to fall back on default parsing behavior for an overridden type spec #49

Open
Ricyteach opened this issue Jun 12, 2017 · 2 comments
Labels

Comments

@Ricyteach
Copy link
Contributor

Ricyteach commented Jun 12, 2017

Edit

After looking a bit closer at the Custom Type Conversions section of the pypi page, I think I can probably get things working the way I need using these. The page contains this statement:

Your custom type conversions may override the builtin types if you supply one with the same identifier.

Is there an exception I can raise, or a method I can call, in the custom type conversion so as to make it fall back on the default behavior for the type conversion (either the one supplied, or a modified version of the one that was supplied)?

Original Question

Is there a way to force the API to fall back on to the default behavior for formatting types when they have been overridden by extra_types? Below is an example of what I mean.

Desired float parsing behavior:

>>> parse('{: >f}{: >f}', '   1.025      1.033')
<Result (1.025, 1.033) {}> # expected result

Actual behavior (when overridden):

>>> parse('{: >f}{: >f}', '   1.025      1.033', extra_types=dict(f=float))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\ricky\Anaconda3\lib\site-packages\parse.py", line 1117, in parse
    return Parser(format, extra_types=extra_types).parse(string, evaluate_result=evaluate_result)
  File "C:\Users\ricky\Anaconda3\lib\site-packages\parse.py", line 699, in parse
    return self.evaluate_result(m)
  File "C:\Users\ricky\Anaconda3\lib\site-packages\parse.py", line 766, in evaluate_result
    fixed_fields[n] = self._type_conversions[n](fixed_fields[n], m)
  File "C:\Users\ricky\Anaconda3\lib\site-packages\parse.py", line 882, in f
    return type_converter(string)
ValueError: could not convert string to float: '.025      1.033'

Use Case

I have a need for a couple of custom numerical types that can handle empty strings and spaces in a manner similar to zeroes. I've implemented them something like this:

class Blank():
    def __new__(cls, value):
        try:
            return super().__new__(cls, value)
        except ValueError:
            if value == '' or value == ' ':
                return super().__new__(cls, 0.0)
            else:
                raise
    def __str__(self):
        return '' if self==0 else super().__str__()
    def __format__(self, spec):
        if (spec.endswith('d') or spec.endswith('f') or spec.endswith('n')) and self==0:
            spec = spec[:-1]+'s'
            return format('',spec)
        else:
            return super().__format__(spec)

class BlankInt(Blank, int):
    '''An int that prints blank when zero.'''
    pass
        
class BlankFloat(Blank, float):
    '''A float that prints blank when zero.'''
    pass

This seems to partially work the way I had in mind:

>>> from parse import parse 
>>> parse('{: >5f}',  '     ', extra_types=dict(f=BlankFloat))
<Result (0.0,) {}>
>>> parse('{: >5f}'*5,  '     ', extra_types=dict(f=BlankFloat))
<Result (0.0, 0.0, 0.0, 0.0, 0.0) {}>

However, this doesn't work (since float doesn't work, either and BlankFloat is a subclass):

>>> parse('{: >f}{: >f}', '   1.025      1.033', extra_types=dict(f=BlankFloat))
ValueError: could not convert string to float: '.025      1.033'
@Ricyteach Ricyteach changed the title Way to fall back on default parsing behavior for custom types Way to fall back on default parsing behavior for an overridden type spec Jun 12, 2017
@r1chardj0n3s
Copy link
Owner

r1chardj0n3s commented May 27, 2018

I apologise for the delay in answering this issue.

I think you're missing a key aspect of the custom types: that you should provide a regular expression pattern to tell the parser what characters should be matched. Thus your example using float as a custom type should be something like (untested):

@with_pattern(r'\d+\.\d+')
def myfloat(s):
    return float(s)

parse('{: >f}{: >f}', '   1.025      1.033', extra_types=dict(f=myfloat))

Otherwise the extra type will use the regex r'.+?' which gobbles everything (non-greedily). Note the "non-greedily" means this also should work:

    parse('{: >f} {: >f}', '   1.025      1.033', extra_types=dict(f=float))

Note the space char between the two floats in the parse spec, forcing a non-matched space between the two r'.+?' matches.

Parsing optionally present elements, like you wish for with BlankFloat, I believe is the domain of a real regex. I'm not sure how to sensibly do that in parse()...

@Ricyteach
Copy link
Contributor Author

I really appreciate the response! I did end up doing exactly what you suggested and have used actual regexes for my project. It works well.

I really like the parse package and at the time I was hoping it would prevent me from "wasting time" learning the regex language (which was a dumb attitude I now realize, having now learned it!). However it turns out the regexes I needed were incredibly simple and found a way to build the compile strings in a structured way, using a yaml input file (that can be updated when the file format spec changes later, which is great).

Anyway thanks again for the thoughts- the usage of the with_pattern decorator you showed was definitely something I was missing (probably because I wasn't wanting to learn regex, haha).

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

No branches or pull requests

2 participants