Skip to content

Commit

Permalink
Merge branch 'main' into numpy-2
Browse files Browse the repository at this point in the history
* main:
  [DOCS] Update docstring for date2num (module function) (SciTools#483)
  Modernise setup scripts (SciTools#484)
  Make antlr optional (SciTools#423)
  Ruff: smaller steps (SciTools#364)
  Updated docstring for num2date. (SciTools#467)
  updated conda lock files (SciTools#479)
  Revert to 00:03 Mondays for lockfile updates. (SciTools#480)
  New trigger time for GMT. (SciTools#478)
  Check lockfile updates @ 3pm daily (temporary for testing). (SciTools#477)
  Fixlocks (SciTools#470)

# Conflicts:
#	pyproject.toml
#	requirements/cf-units.yml
#	requirements/locks/py310-lock-linux-64.txt
#	requirements/locks/py310-lock-osx-64.txt
#	requirements/locks/py310-lock-win-64.txt
#	requirements/locks/py311-lock-linux-64.txt
#	requirements/locks/py311-lock-osx-64.txt
#	requirements/locks/py311-lock-win-64.txt
#	requirements/locks/py312-lock-linux-64.txt
#	requirements/locks/py312-lock-osx-64.txt
#	requirements/locks/py312-lock-win-64.txt
  • Loading branch information
stephenworsley committed Sep 27, 2024
2 parents 664dff3 + 6d58b65 commit 6b433ab
Show file tree
Hide file tree
Showing 24 changed files with 264 additions and 272 deletions.
9 changes: 4 additions & 5 deletions .github/workflows/ci-locks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
uses: actions/[email protected]
env:
# Increment the build number to force a cache refresh.
CACHE_BUILD: 2
CACHE_BUILD: 5
with:
path: ~/conda_pkgs_dir
key: ${{ runner.os }}-conda-pkgs-${{ env.ENV_NAME }}-p${{ env.CACHE_PERIOD }}-b${{ env.CACHE_BUILD }}
Expand All @@ -52,17 +52,16 @@ jobs:
uses: conda-incubator/setup-miniconda@v3
with:
miniforge-version: latest
channels: conda-forge,defaults
channels: conda-forge
activate-environment: ${{ env.ENV_NAME }}
auto-update-conda: true
use-only-tar-bz2: true

- name: "Conda environment cache"
id: conda-env-cache
uses: actions/[email protected]
env:
# Increment the build number to force a cache refresh.
CACHE_BUILD: 2
CACHE_BUILD: 5
with:
path: ${{ env.CONDA }}/envs/${{ env.ENV_NAME }}
key: ${{ runner.os }}-conda-env-${{ env.ENV_NAME }}-p${{ env.CACHE_PERIOD }}-b${{ env.CACHE_BUILD }}
Expand All @@ -81,7 +80,7 @@ jobs:
uses: actions/[email protected]
env:
# Increment the build number to forece a cache refresh.
CACHE_BUILD: 2
CACHE_BUILD: 5
TOX_INI: ${{ github.workspace }}/tox.ini
with:
path: ${{ github.workspace }}/.tox
Expand Down
37 changes: 16 additions & 21 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,6 @@ repos:
# Don't commit to main branch.
- id: no-commit-to-branch

- repo: https://github.com/psf/black
rev: 24.8.0
hooks:
- id: black
types: [file, python]
args: [--config=./pyproject.toml]

- repo: https://github.com/PyCQA/flake8
rev: 7.1.1
hooks:
- id: flake8
types: [file, python]
args: [--config=./setup.cfg]

- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
types: [file, python]
args: [--filter-files]

- repo: https://github.com/aio-libs/sort-all
rev: v1.2.0
hooks:
Expand All @@ -52,3 +31,19 @@ repos:
- id: sp-repo-review
additional_dependencies: ["repo-review[cli]"] # TODO: Only neededed if extra dependencies are required
#args: ["--show=errskip"] # show everything for the moment

- repo: https://github.com/adamchainz/blacken-docs
rev: 1.16.0
hooks:
- id: blacken-docs
types: [file, rst]

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.4.10"
hooks:
# Run the linter
- id: ruff
types: [file, python]
args: [--fix, --show-fixes]
# Run the formatter.
- id: ruff-format
99 changes: 57 additions & 42 deletions cf_units/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,10 @@ def suppress_errors():
try:
_ud_system = _ud.read_xml(config.get_xml_path())
except _ud.UdunitsError as e:
error_msg = ': "%s"' % e.error_msg() if e.errnum else ""
error_msg = f': "{e.error_msg():s}"' if e.errnum else ""
raise OSError(
"[%s] Failed to open UDUNITS-2 XML unit database%s"
% (e.status_msg(), error_msg)
f"[{e.status_msg()}] "
f"Failed to open UDUNITS-2 XML unit database{error_msg}"
)


Expand Down Expand Up @@ -336,6 +336,10 @@ def date2num(date, unit, calendar):
time-zone offset. If there is a time-zone offset in unit, it will be
applied to the returned numeric values.
Return type will be of type `integer` if (all) the times can be
encoded exactly as an integer with the specified units,
otherwise a float type will be returned.
Args:
* date (datetime):
Expand All @@ -350,7 +354,7 @@ def date2num(date, unit, calendar):
Name of the calendar, see cf_units.CALENDARS.
Returns:
float, or numpy.ndarray of float.
float/integer or numpy.ndarray of floats/integers
For example:
Expand All @@ -364,6 +368,12 @@ def date2num(date, unit, calendar):
>>> cf_units.date2num([dt1, dt2], 'hours since 1970-01-01 00:00:00',
... cf_units.CALENDAR_STANDARD)
array([6.5, 7.5])
>>> # Integer type preferentially returned if possible:
>>> dt1 = datetime.datetime(1970, 1, 1, 5, 0)
>>> dt2 = datetime.datetime(1970, 1, 1, 6, 0)
>>> cf_units.date2num([dt1, dt2], 'hours since 1970-01-01 00:00:00',
... cf_units.CALENDAR_STANDARD)
array([5, 6])
"""

Expand Down Expand Up @@ -498,7 +508,7 @@ def as_unit(unit):
result = unit
else:
result = None
use_cache = isinstance(unit, (str,)) or unit is None
use_cache = isinstance(unit, str) or unit is None
if use_cache:
result = _CACHE.get(unit)
if result is None:
Expand Down Expand Up @@ -566,11 +576,9 @@ def _ud_value_error(ud_err, message):

ud_msg = ud_err.error_msg()
if ud_msg:
message = "{}: {}".format(message, ud_msg)
message = f"{message}: {ud_msg}"

message = "[{status}] {message}".format(
status=ud_err.status_msg(), message=message
)
message = f"[{ud_err.status_msg()}] {message}"

return ValueError(message)

Expand All @@ -594,7 +602,7 @@ class Unit(_OrderedHashable):
def _init_from_tuple(self, values):
# Implements the required interface for an _OrderedHashable.
# This will also ensure a Unit._init(*Unit.names) method exists.
for name, value in zip(self._names, values):
for name, value in zip(self._names, values, strict=False):
object.__setattr__(self, name, value)

# Provide hash semantics
Expand All @@ -614,12 +622,12 @@ def __lt__(self, other):

def __setattr__(self, name, value):
raise AttributeError(
"Instances of %s are immutable" % type(self).__name__
f"Instances of {type(self).__name__:s} are immutable"
)

def __delattr__(self, name):
raise AttributeError(
"Instances of %s are immutable" % type(self).__name__
f"Instances of {type(self).__name__:s} are immutable"
)

# Declare the attribute names relevant to the ordered and hashable
Expand Down Expand Up @@ -727,13 +735,13 @@ def __init__(self, unit, calendar=None):
ut_unit = _ud.parse(_ud_system, unit.encode("utf8"), encoding)
except _ud.UdunitsError as exception:
value_error = _ud_value_error(
exception, 'Failed to parse unit "{}"'.format(str_unit)
exception, f'Failed to parse unit "{str_unit}"'
)
raise value_error from None
if _OP_SINCE in unit.lower():
if calendar is None:
calendar_ = CALENDAR_STANDARD
elif isinstance(calendar, (str,)):
elif isinstance(calendar, str):
calendar_ = calendar.lower()
if calendar_ in CALENDAR_ALIASES:
calendar_ = CALENDAR_ALIASES[calendar_]
Expand Down Expand Up @@ -932,7 +940,7 @@ def title(self, value):
dt = self.num2date(value)
result = dt.strftime("%Y-%m-%d %H:%M:%S")
else:
result = "%s %s" % (str(value), self)
result = f"{value} {self}"
return result

@property
Expand Down Expand Up @@ -1220,15 +1228,15 @@ def offset_by_time(self, origin):
"""

if not isinstance(origin, (float, (int,))):
if not isinstance(origin, float | int):
raise TypeError(
"a numeric type for the origin argument is" " required"
"a numeric type for the origin argument is required"
)
try:
ut_unit = _ud.offset_by_time(self.ut_unit, origin)
except _ud.UdunitsError as exception:
value_error = _ud_value_error(
exception, "Failed to offset {!r}".format(self)
exception, f"Failed to offset {self!r}"
)
raise value_error from None
calendar = None
Expand Down Expand Up @@ -1300,7 +1308,7 @@ def root(self, root):
except _ud.UdunitsError as exception:
value_error = _ud_value_error(
exception,
"Failed to take the root of {!r}".format(self),
f"Failed to take the root of {self!r}",
)
raise value_error from None
calendar = None
Expand Down Expand Up @@ -1338,13 +1346,12 @@ def log(self, base):
ut_unit = _ud.log(base, self.ut_unit)
except TypeError:
raise TypeError(
"A numeric type for the base argument is " " required"
"A numeric type for the base argument is required"
)
except _ud.UdunitsError as exception:
value_err = _ud_value_error(
exception,
"Failed to calculate logorithmic base "
"of {!r}".format(self),
f"Failed to calculate logorithmic base of {self!r}",
)
raise value_err from None
calendar = None
Expand Down Expand Up @@ -1386,10 +1393,11 @@ def __repr__(self):
"""
if self.calendar is None:
result = "{}('{}')".format(self.__class__.__name__, self)
result = f"{self.__class__.__name__}('{self}')"
else:
result = "{}('{}', calendar='{}')".format(
self.__class__.__name__, self, self.calendar
result = (
f"{self.__class__.__name__}"
f"('{self}', calendar='{self.calendar}')"
)
return result

Expand Down Expand Up @@ -1430,7 +1438,7 @@ def _op_common(self, other, op_func):
other = as_unit(other)

if self.is_no_unit() or other.is_no_unit():
raise ValueError("Cannot %s a 'no-unit'." % op_label)
raise ValueError(f"Cannot {op_label:s} a 'no-unit'.")

if self.is_unknown() or other.is_unknown():
result = Unit(_UNKNOWN_UNIT_STRING)
Expand All @@ -1440,7 +1448,7 @@ def _op_common(self, other, op_func):
except _ud.UdunitsError as exception:
value_err = _ud_value_error(
exception,
"Failed to {} {!r} by {!r}".format(op_label, self, other),
f"Failed to {op_label} {self!r} by {other!r}",
)
raise value_err from None
calendar = None
Expand Down Expand Up @@ -1583,10 +1591,10 @@ def __pow__(self, power):
root = int(round(1 / power))
result = self.root(root)
else:
# Failing that, check for powers which are (very nearly) simple
# integer values.
# Failing that, check for powers which are (very nearly)
# simple integer values.
if not math.isclose(power, round(power)):
msg = "Cannot raise a unit by a decimal (got %s)." % power
msg = f"Cannot raise a unit by a decimal (got {power:s})."
raise ValueError(msg)
power = int(round(power))

Expand All @@ -1595,7 +1603,7 @@ def __pow__(self, power):
except _ud.UdunitsError as exception:
value_err = _ud_value_error(
exception,
"Failed to raise the power of {!r}".format(self),
f"Failed to raise the power of {self!r}",
)
raise value_err from None
result = Unit._new_from_existing_ut(_CATEGORY_UDUNIT, ut_unit)
Expand Down Expand Up @@ -1662,10 +1670,10 @@ def __ne__(self, other):

def change_calendar(self, calendar):
"""
Returns a new unit with the requested calendar, modifying the reference
date if necessary. Only works with calendars that represent the real
world (standard, proleptic_gregorian, julian) and with short time
intervals (days or less).
Returns a new unit with the requested calendar, modifying the
reference date if necessary. Only works with calendars that
represent the real world (standard, proleptic_gregorian, julian)
and with short time intervals (days or less).
For example:
Expand All @@ -1674,7 +1682,7 @@ def change_calendar(self, calendar):
>>> u.change_calendar('standard')
Unit('days since 1499-12-23T00:00:00', calendar='standard')
"""
""" # NOQA E501
if not self.is_time_reference():
raise ValueError("unit is not a time reference")

Expand Down Expand Up @@ -1772,7 +1780,7 @@ def convert(self, value, other, ctype=FLOAT64, inplace=False):
except _ud.UdunitsError as exception:
value_err = _ud_value_error(
exception,
"Failed to convert {!r} to {!r}".format(self, other),
f"Failed to convert {self!r} to {other!r}",
)
raise value_err from None
if isinstance(result, np.ndarray):
Expand All @@ -1796,9 +1804,8 @@ def convert(self, value, other, ctype=FLOAT64, inplace=False):
# Strict type check of numpy array.
if result.dtype.type not in (np.float32, np.float64):
raise TypeError(
"Expect a numpy array of '%s' or '%s'"
% np.float32,
np.float64,
"Expect a numpy array of "
f"'{np.float32}' or '{np.float64}'"
)
ctype = result.dtype.type
# Utilise global convenience dictionary
Expand Down Expand Up @@ -1830,7 +1837,7 @@ def convert(self, value, other, ctype=FLOAT64, inplace=False):
return result
else:
raise ValueError(
"Unable to convert from '%r' to '%r'." % (self, other)
f"Unable to convert from '{self!r}' to '{other!r}'."
)

@property
Expand Down Expand Up @@ -1861,14 +1868,18 @@ def date2num(self, date):
Works for scalars, sequences and numpy arrays. Returns a scalar
if input is a scalar, else returns a numpy array.
Return type will be of type `integer` if (all) the times can be
encoded exactly as an integer with the specified units,
otherwise a float type will be returned.
Args:
* date (datetime):
A datetime object or a sequence of datetime objects.
The datetime objects should not include a time-zone offset.
Returns:
float or numpy.ndarray of float.
float/integer or numpy.ndarray of floats/integers
For example:
Expand All @@ -1881,6 +1892,10 @@ def date2num(self, date):
>>> u.date2num([datetime.datetime(1970, 1, 1, 5, 30),
... datetime.datetime(1970, 1, 1, 6, 30)])
array([5.5, 6.5])
>>> # Integer type preferentially returned if possible:
>>> u.date2num([datetime.datetime(1970, 1, 1, 5, 0),
... datetime.datetime(1970, 1, 1, 6, 0)])
array([5, 6])
"""
return cftime.date2num(date, self.cftime_unit, self.calendar)
Expand Down
Loading

0 comments on commit 6b433ab

Please sign in to comment.