Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into consistent-ruff
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielYang59 committed Jan 4, 2025
2 parents 020943e + 69f0f9f commit 9a25bfd
Show file tree
Hide file tree
Showing 15 changed files with 133 additions and 172 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ jobs:
fail-fast: false
max-parallel: 20
matrix:
os: [ubuntu-latest, macos-14] #, windows-latest]
python-version: ["3.10", "3.12"]
os: [ubuntu-latest, macos-14, windows-latest]
python-version: ["3.10", "3.11", "3.12", "3.13"]

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
Expand Down
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change log

## 2025.1.3
- Drop deadline warning in dev.deprecated
- allow strict="skip" for jsanitize

## 2024.12.10
- zopen changes: forbid implicit binary/text mode, signature change, default UTF-8 encoding in text mode, drop .z
support after one-year. (@DanielYang59)
Expand Down
3 changes: 3 additions & 0 deletions docs/monty.functools.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,13 @@ becomes
The decorated main accepts two new arguments:

> prof_file: Name of the output file with profiling data
> ```none
> If not given, a temporary file is created.
> ```
> sortby: Profiling data are sorted according to this value.
> ```none
> default is “time”. See sort_stats.
> ```
Expand Down
1 change: 1 addition & 0 deletions docs/monty.os.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ performing some tasks, and returns to the original working directory
afterwards. E.g.,

> with cd(“/my/path/”):
> ```none
> do_something()
> ```
Expand Down
2 changes: 2 additions & 0 deletions docs/monty.re.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ A powerful regular expression version of grep.
* **Returns**

> {key1: [[[matches…], lineno], [[matches…], lineno],
> ```none
> [[matches…], lineno], …],
> ```
> key2: …}
For reverse reads, the lineno is given as a -ve number. Please note
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ maintainers = [
]
description = "Monty is the missing complement to Python."
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.10,<3.14"
classifiers = [
"Programming Language :: Python :: 3",
"Development Status :: 4 - Beta",
Expand All @@ -22,7 +22,7 @@ dependencies = [
"ruamel.yaml",
"numpy",
]
version = "2024.12.10"
version = "2025.1.3"

[project.optional-dependencies]
ci = [
Expand Down
6 changes: 4 additions & 2 deletions src/monty/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ def __setitem__(self, key, value) -> None:

def update(self, *args, **kwargs) -> None:
"""Forbid adding or updating keys based on _allow_add and _allow_update."""
for key in dict(*args, **kwargs):

updates = dict(*args, **kwargs)
for key in updates:
if key not in self.data and not self._allow_add:
raise TypeError(
f"Cannot add new key {key!r} using update, because add is disabled."
Expand All @@ -99,7 +101,7 @@ def update(self, *args, **kwargs) -> None:
f"Cannot update key {key!r} using update, because update is disabled."
)

super().update(*args, **kwargs)
super().update(updates)

def setdefault(self, key, default=None) -> Any:
"""Forbid adding or updating keys based on _allow_add and _allow_update.
Expand Down
56 changes: 4 additions & 52 deletions src/monty/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@

import functools
import inspect
import logging
import os
import subprocess
import sys
import warnings
from dataclasses import is_dataclass
Expand All @@ -19,8 +16,6 @@
if TYPE_CHECKING:
from typing import Callable, Optional, Type

logger = logging.getLogger(__name__)


def deprecated(
replacement: Optional[Callable | str] = None,
Expand All @@ -35,8 +30,7 @@ def deprecated(
replacement (Callable | str): A replacement class or function.
message (str): A warning message to be displayed.
deadline (Optional[tuple[int, int, int]]): Optional deadline for removal
of the old function/class, in format (yyyy, MM, dd). A CI warning would
be raised after this date if is running in code owner' repo.
of the old function/class, in format (yyyy, MM, dd).
category (Warning): Choose the category of the warning to issue. Defaults
to FutureWarning. Another choice can be DeprecationWarning. Note that
FutureWarning is meant for end users and is always shown unless silenced.
Expand All @@ -45,48 +39,9 @@ def deprecated(
the choice accordingly.
Returns:
Original function, but with a warning to use the updated function.
Original function/class, but with a warning to use the replacement.
"""

def raise_deadline_warning() -> None:
"""Raise CI warning after removal deadline in code owner's repo."""

def _is_in_owner_repo() -> bool:
"""Check if is running in code owner's repo.
Only generate reliable check when `git` is installed and remote name
is "origin".
"""

try:
# Get current running repo
result = subprocess.run(
["git", "config", "--get", "remote.origin.url"],
stdout=subprocess.PIPE,
)
owner_repo = (
result.stdout.decode("utf-8")
.strip()
.lstrip("https://github.com/") # HTTPS clone
.lstrip("[email protected]:") # SSH clone
.rstrip(".git") # SSH clone
)

return owner_repo == os.getenv("GITHUB_REPOSITORY")

except (subprocess.CalledProcessError, FileNotFoundError):
return False

# Only raise warning in code owner's repo CI
if (
_deadline is not None
and os.getenv("CI") is not None
and datetime.now() > _deadline
and _is_in_owner_repo()
):
raise DeprecationWarning(
f"This function should have been removed on {_deadline:%Y-%m-%d}."
)

def craft_message(
old: Callable,
replacement: Callable | str,
Expand Down Expand Up @@ -150,11 +105,8 @@ def new_init(self, *args, **kwargs):

return cls

# Convert deadline to datetime type
_deadline = datetime(*deadline) if deadline is not None else None

# Raise CI warning after removal deadline
raise_deadline_warning()
# Convert deadline to `datetime` type
_deadline: datetime | None = datetime(*deadline) if deadline is not None else None

def decorator(target: Callable) -> Callable:
if inspect.isfunction(target):
Expand Down
2 changes: 1 addition & 1 deletion src/monty/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def zopen(
# TODO: remove default value of `mode` to force user to give one after deadline
if mode is None:
warnings.warn(
"We strongly discourage using a default `mode`, it would be"
"We strongly discourage using a default `mode`, it would be "
f"set to `r` now but would not be allowed after {_deadline}",
FutureWarning,
stacklevel=2,
Expand Down
24 changes: 15 additions & 9 deletions src/monty/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,8 @@ def jsanitize(
jsanitize will try to get the as_dict() attribute of the object. If
no such attribute is found, an attribute error will be thrown. If
strict is False, jsanitize will simply call str(object) to convert
the object to a string representation.
the object to a string representation. If "skip" is provided,
jsanitize will skip and return the original object without modification.
allow_bson (bool): This parameter sets the behavior when jsanitize
encounters a bson supported type such as objectid and datetime. If
True, such bson types will be ignored, allowing for proper
Expand Down Expand Up @@ -1009,7 +1010,7 @@ def jsanitize(
except AttributeError:
pass

if not strict:
if strict is False:
return str(obj)

if isinstance(obj, str):
Expand All @@ -1024,13 +1025,18 @@ def jsanitize(
recursive_msonable=recursive_msonable,
)

return jsanitize(
obj.as_dict(),
strict=strict,
allow_bson=allow_bson,
enum_values=enum_values,
recursive_msonable=recursive_msonable,
)
try:
return jsanitize(
obj.as_dict(),
strict=strict,
allow_bson=allow_bson,
enum_values=enum_values,
recursive_msonable=recursive_msonable,
)
except Exception as exc_:
if strict == "skip":
return obj
raise exc_


def _serialize_callable(o):
Expand Down
2 changes: 0 additions & 2 deletions src/monty/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
if TYPE_CHECKING:
from typing import Callable

logger = logging.getLogger(__name__)


def logged(level: int = logging.DEBUG) -> Callable:
"""
Expand Down
15 changes: 14 additions & 1 deletion tests/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ def test_update_allowed(self):
dct.update({"a": 3})
assert dct["a"] == 3

# Test Iterator handling
dct.update(zip(["c", "d"], [11, 12]))
assert dct["c"] == 11

dct.setdefault("a", 4) # existing key
assert dct["a"] == 3

Expand Down Expand Up @@ -122,6 +126,11 @@ def test_frozen_like(self):
assert not dct._allow_add
assert not dct._allow_update

def test_iterator_handling(self):
"""Make sure iterators are handling correctly."""
c_dict = ControlledDict(zip(["c", "d"], [11, 12]))
assert c_dict["c"] == 11


def test_frozendict():
dct = frozendict({"hello": "world"})
Expand Down Expand Up @@ -157,7 +166,11 @@ def test_namespace_dict():
dct["hello"] = "world"
assert dct["key"] == "val"

# Test update (not allowed)
# Test use `update` to add new values
dct.update({"new_key": "new_value"})
assert dct["new_key"] == "new_value"

# Test add (not allowed)
with pytest.raises(TypeError, match="update is disabled"):
dct["key"] = "val"

Expand Down
Loading

0 comments on commit 9a25bfd

Please sign in to comment.