Skip to content

Commit

Permalink
Merge pull request #159 from brian-rose/attrdict
Browse files Browse the repository at this point in the history
Python 3.10 compatibility
  • Loading branch information
brian-rose committed Feb 4, 2022
2 parents 95d6432 + 208c389 commit 0328fec
Show file tree
Hide file tree
Showing 21 changed files with 580 additions and 19 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Build and test climlab

on:
on:
push:
pull_request:
workflow_dispatch:
Expand All @@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [3.7, 3.8, 3.9]
python-version: ['3.7', '3.8', '3.9', '3.10']
os: [Ubuntu, macOS, Windows]
include:
- os: Ubuntu
Expand Down
11 changes: 9 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,9 @@ These are handled automatically if you install with conda_.

Required
~~~~~~~~~~~~
- Python (currently testing on versions 3.7, 3.8, 3.9)
- Python (currently testing on versions 3.7, 3.8, 3.9, 3.10)
- numpy
- scipy
- attrdict
- future
- pooch (for remote data access and caching)
- xarray (for data handling)
Expand Down Expand Up @@ -155,6 +154,12 @@ These are self-describing, and should run out-of-the-box once the package is ins
Release history
----------------------

Version 0.7.13 (released February 2022)
Maintenance release to support Python 3.10.

The `attrdict package`_ by `Brendan Curran-Johnson`_ has been removed from the dependencies since it is broken on Python 3.10 and no longer under development.
A modified version of the MIT-licensed attrdict source is now bundled internally with climlab. There are no changes to climlab's public API.

Version 0.7.12 (released May 2021)
New feature: spectral output from RRTMG (accompanied by a new tutorial)

Expand Down Expand Up @@ -302,6 +307,8 @@ The documentation_ was first created by Moritz Kreuzer
.. _`tutorials in the docs`: https://climlab.readthedocs.io/en/latest/tutorial.html
.. _here: http://climlab.readthedocs.io
.. _`The Climate Laboratory`: https://brian-rose.github.io/ClimateLaboratoryBook/
.. _`attrdict package`: https://github.com/bcj/AttrDict
.. _`Brendan Curran-Johnson`: https://github.com/bcj


Contact and Bug Reports
Expand Down
1 change: 0 additions & 1 deletion ci/requirements-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ dependencies:
- fortran-compiler
- toolchain3
- future
- attrdict
- pytest
- codecov
- pytest-cov
1 change: 0 additions & 1 deletion ci/requirements-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ dependencies:
- gfortran_osx-64
- libgfortran
- future
- attrdict
- pytest
- codecov
- pytest-cov
1 change: 0 additions & 1 deletion ci/requirements-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ dependencies:
- scipy
- flang
- future
- attrdict
2 changes: 1 addition & 1 deletion climlab/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
has been documented for a comprehensive understanding and traceability.
'''

__version__ = '0.7.12'
__version__ = '0.7.13dev0'

# this should ensure that we can still import constants.py as climlab.constants
from .utils import constants, thermo, legendre
Expand Down
2 changes: 1 addition & 1 deletion climlab/domain/initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numpy as np
from climlab.domain import domain
from climlab.domain.field import Field
from attrdict import AttrDict
from climlab.utils.attrdict import AttrDict
from climlab.utils import legendre


Expand Down
2 changes: 1 addition & 1 deletion climlab/process/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
from climlab.domain.field import Field
from climlab.domain.domain import _Domain, zonal_mean_surface
from climlab.utils import walk
from attrdict import AttrDict
from climlab.utils.attrdict import AttrDict
from climlab.domain.xarray import state_to_xarray


Expand Down
2 changes: 1 addition & 1 deletion climlab/process/time_dependent_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from climlab import constants as const
from .process import Process
from climlab.utils import walk
from attrdict import AttrDict
from climlab.utils.attrdict import AttrDict


def couple(proclist, name='Parent'):
Expand Down
10 changes: 10 additions & 0 deletions climlab/utils/attrdict/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
attrdict contains several mapping objects that allow access to their
keys as attributes.
"""
from .mapping import AttrMap
from .dictionary import AttrDict
from .default import AttrDefault


__all__ = ['AttrMap', 'AttrDict', 'AttrDefault']
130 changes: 130 additions & 0 deletions climlab/utils/attrdict/default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""
A subclass of MutableAttr that has defaultdict support.
"""
from collections.abc import Mapping

import six

from .mixins import MutableAttr


__all__ = ['AttrDefault']


class AttrDefault(MutableAttr):
"""
An implementation of MutableAttr with defaultdict support
"""
def __init__(self, default_factory=None, items=None, sequence_type=tuple,
pass_key=False):
if items is None:
items = {}
elif not isinstance(items, Mapping):
items = dict(items)

self._setattr('_default_factory', default_factory)
self._setattr('_mapping', items)
self._setattr('_sequence_type', sequence_type)
self._setattr('_pass_key', pass_key)
self._setattr('_allow_invalid_attributes', False)

def _configuration(self):
"""
The configuration for a AttrDefault instance
"""
return self._sequence_type, self._default_factory, self._pass_key

def __getitem__(self, key):
"""
Access a value associated with a key.
Note: values returned will not be wrapped, even if recursive
is True.
"""
if key in self._mapping:
return self._mapping[key]
elif self._default_factory is not None:
return self.__missing__(key)

raise KeyError(key)

def __setitem__(self, key, value):
"""
Add a key-value pair to the instance.
"""
self._mapping[key] = value

def __delitem__(self, key):
"""
Delete a key-value pair
"""
del self._mapping[key]

def __len__(self):
"""
Check the length of the mapping.
"""
return len(self._mapping)

def __iter__(self):
"""
Iterated through the keys.
"""
return iter(self._mapping)

def __missing__(self, key):
"""
Add a missing element.
"""
if self._pass_key:
self[key] = value = self._default_factory(key)
else:
self[key] = value = self._default_factory()

return value

def __repr__(self):
"""
Return a string representation of the object.
"""
return six.u(
"AttrDefault({default_factory}, {pass_key}, {mapping})"
).format(
default_factory=repr(self._default_factory),
pass_key=repr(self._pass_key),
mapping=repr(self._mapping),
)

def __getstate__(self):
"""
Serialize the object.
"""
return (
self._default_factory,
self._mapping,
self._sequence_type,
self._pass_key,
self._allow_invalid_attributes,
)

def __setstate__(self, state):
"""
Deserialize the object.
"""
(default_factory, mapping, sequence_type, pass_key,
allow_invalid_attributes) = state

self._setattr('_default_factory', default_factory)
self._setattr('_mapping', mapping)
self._setattr('_sequence_type', sequence_type)
self._setattr('_pass_key', pass_key)
self._setattr('_allow_invalid_attributes', allow_invalid_attributes)

@classmethod
def _constructor(cls, mapping, configuration):
"""
A standardized constructor.
"""
sequence_type, default_factory, pass_key = configuration
return cls(default_factory, mapping, sequence_type=sequence_type,
pass_key=pass_key)
60 changes: 60 additions & 0 deletions climlab/utils/attrdict/dictionary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
A dict that implements MutableAttr.
"""
from .mixins import MutableAttr

import six


__all__ = ['AttrDict']


class AttrDict(dict, MutableAttr):
"""
A dict that implements MutableAttr.
"""
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)

self._setattr('_sequence_type', tuple)
self._setattr('_allow_invalid_attributes', False)

def _configuration(self):
"""
The configuration for an attrmap instance.
"""
return self._sequence_type

def __getstate__(self):
"""
Serialize the object.
"""
return (
self.copy(),
self._sequence_type,
self._allow_invalid_attributes
)

def __setstate__(self, state):
"""
Deserialize the object.
"""
mapping, sequence_type, allow_invalid_attributes = state
self.update(mapping)
self._setattr('_sequence_type', sequence_type)
self._setattr('_allow_invalid_attributes', allow_invalid_attributes)

def __repr__(self):
return six.u('AttrDict({contents})').format(
contents=super(AttrDict, self).__repr__()
)

@classmethod
def _constructor(cls, mapping, configuration):
"""
A standardized constructor.
"""
attr = cls(mapping)
attr._setattr('_sequence_type', configuration)

return attr

0 comments on commit 0328fec

Please sign in to comment.