Skip to content

Conversation

@DPeterK
Copy link
Member

@DPeterK DPeterK commented Mar 29, 2017

Adds the options module to master (cf. #2462). In particular, this PR adds an Option to control certain aspects of NetCDF file handling, specifically what to do with a custom conventions attribute on saving a cube to a NetCDF file.

In this sense this PR is proposing a very similar change to the excellent suggestion made by @morwenna-01 in #2457, but does so in the manner suggested there by @bjlittle.

Copy link
Member

@bjlittle bjlittle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkillick This is great, but I'm keen to understand how this might scale as more options are added.

That is, we know that's going to happen pretty swiftly on adoption, so there will be separate option name-spaces for netcdf and parallel (or whatever for dask) ... how are these extra options added. Can you preempt that by having a common behaviour and state that can be inherited? Or is your strategy for us to refactor/rationalise at the point when a new additional option is added?

It would also be handy to be able to do

  >>> iris.options

or

>>> print(iris.options)

and see what options are available. Or is your expectation for the user to do help(iris.options) ?

I've not specifically gone through your tests, in case there is fall out from my current comments.

"""
self.__dict__['conventions_override'] = conventions_override

def __str__(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use __repr__ instead, otherwise you get this behaviour ...

>>> iris.options.netcdf
<iris.options.NetCDF object at 0x7fdc0b9f60d0>
>>> print iris.options.netcdf
NetCDF options: conventions_override=False.
>>> 

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't have much success with __repr__ either 😒


def __str__(self):
msg = 'NetCDF options: conventions_override={}.'
return msg.format(self.conventions_override)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there's potential for more option attributes to come, this pattern is handy ...

msg = 'NetCDF options: conventions_override={self.conventions_override!r}.'
return msg.format(self)

import warnings


class NetCDF(object):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkillick Is this thread safe? We'll want that for dask usage ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we though? It is interesting that dask's set_options is not thread-safe. I left this like this for precisely this reason -- so that we'd get some commenting on it.

In fact I'd go so far as to argue that we don't want options thread-safe (!) -- my reason being that if I set an option I'd like it to be available in all threads, not just the calling thread. Of course, this may indicate some mixed-up thinking about how threads work, so I'm happy to stand corrected about this one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we'll be driven by the behaviour. Let's see how it plays!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say if you provide it as a context manager, then it's not a one-off switch to set-and-forget.
In that case, thread-safe is definitely preferable IMHO.


def __setattr__(self, name, value):
if name not in self.__dict__:
msg = "'Future' object has no attribute {!r}".format(name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cut'n paste carnage ... Future?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops! Good spot 😉


@property
def _defaults_dict(self):
return {'conventions_override': False}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the intent, but there's a slight disconnect here, in that there is a default value in the formal argument list of the __init__.

These could contradict each other ... it would be nice to specify this in one place somehow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True! I'll poke it and see what I can do.

if name not in self.__dict__:
msg = "'Future' object has no attribute {!r}".format(name)
raise AttributeError(msg)
if value not in [True, False]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we restricting all option attributes to be only bool?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, in a "maximise work not done" sense 😉 -- in this case I only need to care about bools, so I have. I do have a plan for making this more broad, but that will appear in #2462, where I need it.

@bjlittle bjlittle self-assigned this Mar 30, 2017
@pp-mo
Copy link
Member

pp-mo commented Mar 30, 2017

I fell this doesn't follow the spirit of what you mentioned

the manner suggested there by @bjlittle.

I think I'd find it much tidier to have 'iris.netcdf.options' instead of 'iris.options.netcdf', because it scales nicely : In particular, the definition and usage of the options settings relating to a given area (e.g. netcdf) are kept together and contained in the relevant place, i.e. the sub-module.
However, I think it also makes very good sense to write the class code once and have that in 'iris.options'. It should be similar to the iris.Future usage, though I know we have outstanding concerns about the thread-safe usage.

I suspect we may want a top-level 'iris.options', mostly just so we can print a summary.
In that case, you could make it a dictionary which the other modules populate on install.
E.G. netcdf.py could have something like ...

from iris.options import Options, options as iris_options

options = Options(conventions_override=False, other_things=2)
iris_options['netcdf'] = options

Then you can ...

from iris.fileformats.netcdf import options as nc_options
. . .
    with nc_options.context(conventions_override=True):
       save(...)

Also print iris.options.options and/or print iris.fileformats.netcdf.options

@DPeterK
Copy link
Member Author

DPeterK commented Mar 30, 2017

@pp-mo I think it is tidier to have all of the options in one place (there's another one for controlling parallel processing options), and I intend them to inherit from a superclass Option. All of this suggests to me that we lump all the Options together into one place...

@pp-mo
Copy link
Member

pp-mo commented Mar 30, 2017

it is tidier to have all of the options in one place

Not if they are going to divide into separate self-contained areas of interest.
Which it already sounds like they are doing.

This is very much like iris.Future...
That is getting a bit awkward as more things are added : they don't really interact or belong together.
The difference with iris.Future is that we expect all the components to be transient
-- when the implementation matures + deprecation cycle is complete, they get removed again.
But that wouldn't happen with iris.options -- the complexity will simply increase steadily over time.

@DPeterK
Copy link
Member Author

DPeterK commented Mar 31, 2017

Following from an offline conversation with @pp-mo I've moved the Options to config.py, which we think might be the best fit given the code that already exists within Iris. It is possible that doing this has blatted some of @bjlittle's comments on these changes without addressing them... sorry!

@marqh
Copy link
Member

marqh commented Apr 7, 2017

i think i am following the intent of this

the ability to control low level options in these ways seems useful

I am wondering how these are described for users. I think there could be some be some explanatory docs in the refdocs for config to better handle this.
Perhaps a user guide section, or references from the user guide in the appropriate sections, e.g. save

I also wonder whether the sample.site.cfg should include this, and perhaps remove some of the other deprecated options whilst we are there:
https://github.com/dkillick/iris/blob/8dace2350feac49fa92461665233548e33ff9c58/lib/iris/etc/site.cfg.template

this file is a useful reference, under source control

@DPeterK
Copy link
Member Author

DPeterK commented Apr 10, 2017

Rebased.

If `False` (the default), specifies that Iris should set the
CF Conventions version when saving cubes as NetCDF files.
If `True`, specifies that the cubes being saved to NetCDF should
set the cf conventions version for the saved NetCDF files.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkillick Apologies for being picky, but is this cf conventions or CF Conventions ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nono, good spot! My bad, I'll change (it should be the latter).

* Specify, for the lifetime of the session, that we want all cubes
written to NetCDF to define their own CF Conventions versions::
iris.options.netcdf.conventions_override = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkillick This has gone state ... should now be iris.config.netcdf.conventions_override

* Specify, with a context manager, that we want a cube written to
NetCDF to define its own CF Conventions version::
with iris.options.netcdf.context(conventions_override=True):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkillick Same reek of staleness here, thanks

def _defaults_dict(self):
return {'conventions_override': {'default': False,
'options': [True, False]},
}
Copy link
Member

@bjlittle bjlittle Apr 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkillick The _defaults_dict is private to the class, so is there a real need to make this a property? I can understand making this a read-only property if it was part of the public API, but it's not, so is there a need to be this restrictive or am I missing something? (most likely)

Why can't the self._defaults_dict be created and initialised just the once in the __init__ ?

def __setattr__(self, name, value):
if name not in self.__dict__:
# Can't add new names.
msg = "'Option' object has no attribute {!r}".format(name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkillick This isn't an Option instance ... or are you thinking in the generic sense? If so, would it be better to say something along the lines of "{!r} option has no attribute {!r}.".format(type(self).__name__, name) ...

# anything goes.
if value not in self._defaults_dict[name]['options']:
good_value = self._defaults_dict[name]['default']
wmsg = ('Attempting to set bad value {!r} for attribute {!r}. '
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor suggestion, bad -> invalid

Do we want to raise a warning here, which may be silently ignored and use the default, or raise a hard exception ...

@bjlittle
Copy link
Member

@dkillick You have quite a few test failures.

Without delving too deeply, I think that the failures might be related to the fact that the options are not thread safe? In that, a test is setting the conventions_override for the purpose of what it's trying to test, but this is a global setting that is interfering with other concurrent running tests ...

I'll leave you to dig and confirm whether this is the case or not ...

@bjlittle
Copy link
Member

bjlittle commented Apr 10, 2017

@dkillick We still need to consider how this approach is going to scale ... at the moment we will be adding all of the options into iris.config ... I think we should consider whether we can easily separate our concerns, or accept that iris.config may bloat quite quickly ...

Also, as a user of iris I want an easy way to check what possible options are available to me and what specific options are set for a group i.e. what NetCDF options are set?, what regrid options are set?, what are all the options groups? Do you have any thoughts on this ...


conventions = CF_CONVENTIONS_VERSION
if iris.config.netcdf.conventions_override:
conventions = cube.attributes['Conventions']
Copy link
Member

@bjlittle bjlittle Apr 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkillick The cube.attributes may not necessarily have any Conventions to use ...

I guess if that is the case, it seems reasonable to use the default CF_CONVENTIONS_VERSION instead rather than raise an exception ...

#
# You should have received a copy of the GNU Lesser General Public License
# along with Iris. If not, see <http://www.gnu.org/licenses/>.
"""Unit tests for the `iris.options.Paralle` class."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkillick cut'n paste ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And a typo!

@DPeterK
Copy link
Member Author

DPeterK commented Apr 11, 2017

We still need to consider how this approach is going to scale

At the moment iris.config is still small and, I think, the best place to have options like these. If it ends up getting big in the future then maybe we can think about shuffling things around then. It may also be clearer how/if to when we have a lot more examples of options like these.

@DPeterK
Copy link
Member Author

DPeterK commented Apr 11, 2017

@bjlittle we have a green light!! 🚦

@bjlittle
Copy link
Member

@dkillick Awesome, thanks! 👍

@bjlittle bjlittle merged commit 538eed4 into SciTools:master Apr 11, 2017
@DPeterK DPeterK deleted the options branch April 11, 2017 14:44
@QuLogic QuLogic added this to the v2.0 milestone Apr 11, 2017
@QuLogic QuLogic modified the milestones: v1.13.0, v2.0 May 17, 2017
@DPeterK DPeterK mentioned this pull request Jul 20, 2017
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

Successfully merging this pull request may close these issues.

5 participants