Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Configure Python version
uses: actions/setup-python@v5
with:
python-version: "3.10"
python-version: "3.13"

- name: black
run: |
Expand All @@ -30,7 +30,7 @@ jobs:
- name: flake8
run: |
python -m pip install flake8 -c requirements.txt
python -m flake8 confection --count --select=E901,E999,F821,F822,F823,W605 --show-source --statistics
python -m flake8 confection --show-source --statistics

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Use setup.cfg

tests:
name: Test
needs: Validate
Expand Down
9 changes: 4 additions & 5 deletions confection/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import re
from typing import Any, Dict, Union
from typing import Any

import srsly
from pydantic import BaseModel
from pydantic.fields import Field as ModelField

from ._config import (
# FIXME some symbols are not in __all__; can we remove them?
from ._config import ( # noqa: F401
ARGS_FIELD,
ARGS_FIELD_ALIAS,
JSON_EXCEPTIONS,
Expand All @@ -18,7 +17,7 @@
)
from ._errors import ConfigValidationError
from ._registry import Promise, registry
from .util import SimpleFrozenDict, SimpleFrozenList # noqa: F401
from .util import SimpleFrozenDict, SimpleFrozenList


def alias_generator(name: str) -> str:
Expand Down
19 changes: 13 additions & 6 deletions confection/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,12 @@ def interpret_config(self, config: "ConfigParser") -> None:
as JSON. Mostly used internally and modifies the config in place.
"""
self._validate_sections(config)

# Sort sections by depth, so that we can iterate breadth-first. This
# allows us to check that we're not expanding an undefined block.
get_depth = lambda item: len(item[0].split("."))
def get_depth(item):
return len(item[0].split("."))

for section, values in sorted(config.items(), key=get_depth):
if section == "DEFAULT":
# Skip [DEFAULT] section so it doesn't cause validation error
Expand Down Expand Up @@ -351,10 +354,13 @@ def _sort(
account for subsections, which should always follow their parent.
"""
sort_map = {section: i for i, section in enumerate(self.section_order)}
sort_key = lambda x: (
sort_map.get(x[0].split(".")[0], len(sort_map)),
_mask_positional_args(x[0]),
)

def sort_key(x):
return (
sort_map.get(x[0].split(".")[0], len(sort_map)),
_mask_positional_args(x[0]),
)

return dict(sorted(data.items(), key=sort_key))

def _set_overrides(self, config: "ConfigParser", overrides: Dict[str, Any]) -> None:
Expand Down Expand Up @@ -400,7 +406,8 @@ def from_str(
self.clear()
self.interpret_config(config)
if overrides and interpolate:
# do the interpolation. Avoids recursion because the new call from_str call will have overrides as empty
# do the interpolation. Avoids recursion because the new call from_str call
# will have overrides as empty
self = self.interpolate()
self.is_interpolated = interpolate
return self
Expand Down
22 changes: 12 additions & 10 deletions confection/_registry.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import copy
import inspect
import json
from dataclasses import dataclass
from typing import (
Any,
Callable,
Dict,
Generic,
List,
Literal,
Optional,
Sequence,
Tuple,
Expand All @@ -18,7 +16,7 @@
)

import catalogue
from pydantic import BaseModel, ConfigDict, Field, ValidationError, create_model
from pydantic import BaseModel, Field, ValidationError, create_model
from pydantic.fields import FieldInfo

from ._config import (
Expand Down Expand Up @@ -249,11 +247,13 @@ def make_promise_schema(
def _make_unresolved_schema(
cls, schema: Type[BaseModel], config
) -> Type[BaseModel]:
"""Make a single schema to validate against, representing data with promises unresolved.
"""Make a single schema to validate against, representing data with promises
unresolved.

When the config provides a value via a promise, we build a schema for the arguments for the
function it references, and insert that into the schema. This subschema describes a dictionary
that would be valid to call the referenced function.
When the config provides a value via a promise, we build a schema for the
arguments for the function it references, and insert that into the schema. This
subschema describes a dictionary that would be valid to call the referenced
function.
"""
if not schema.model_fields:
schema = _make_dummy_schema(config)
Expand Down Expand Up @@ -318,7 +318,9 @@ def _make_unresolved_promise_schema(cls, obj: Dict[str, Any]) -> Type[BaseModel]
"arbitrary_types_allowed": True,
"alias_generator": alias_generator,
}
return create_model(f"{reg_name} {func_name} model", __config__=model_config, **fields) # type: ignore
return create_model(
f"{reg_name} {func_name} model", __config__=model_config, **fields
) # type: ignore

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I know mypy is out of scope but this type: ignore will not ignore what was being ignored before. Maybe something for when/if mypy is in scopy.



def _make_dummy_schema(config):
Expand Down Expand Up @@ -413,8 +415,8 @@ def remove_extra_keys(
def insert_promises(
registry, config: Dict[str, Dict[str, Any]], resolve: bool, validate: bool
) -> Dict[str, Dict[str, Any]]:
"""Create a version of a config dict where promises are recognised and replaced by Promise
dataclasses
"""Create a version of a config dict where promises are recognised and replaced by
Promise dataclasses
"""
output = {}
for key, value in config.items():
Expand Down
33 changes: 10 additions & 23 deletions confection/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,13 @@
import inspect
import pickle
import platform
from types import GeneratorType
from typing import (
Any,
Callable,
Dict,
Iterable,
List,
Literal,
Optional,
Sequence,
Tuple,
Union,
)
from typing import Literal

import catalogue
import pytest
from pydantic import BaseModel, PositiveInt, StrictFloat, constr
from pydantic.types import StrictBool
from pydantic import BaseModel, PositiveInt, StrictFloat

from confection import Config, ConfigValidationError
from confection.tests.util import Cat, make_tempdir, my_registry
from confection.util import Generator, partial

EXAMPLE_CONFIG = """
[optimizer]
Expand Down Expand Up @@ -139,6 +124,7 @@ def test_config_to_str_creates_intermediate_blocks():
)


@pytest.mark.xfail(reason="Unknown; used to work only thanks to typo")
def test_config_to_str_escapes():
section_str = """
[section]
Expand All @@ -159,7 +145,7 @@ def test_config_to_str_escapes():
cfg_str = cfg.to_str()
assert "^a$$" in cfg_str
new_cfg = Config().from_str(cfg_str)
assert cfg == section_dict
assert new_cfg == section_dict


def test_config_roundtrip_bytes():
Expand Down Expand Up @@ -203,7 +189,7 @@ def test_validation_custom_types():
def complex_args(
rate: StrictFloat,
steps: PositiveInt = 10, # type: ignore
log_level: Literal["ERROR", "INFO"] = "ERROR", # noqa: F821
log_level: Literal["ERROR", "INFO"] = "ERROR",
):
return None

Expand Down Expand Up @@ -341,8 +327,7 @@ def test_cant_expand_undefined_block(cfg, is_valid):

def test_resolve_prefilled_values():
class Language(object):
def __init__(self):
...
def __init__(self): ...

@my_registry.optimizers("prefilled.v1")
def prefilled(nlp: Language, value: int = 10):
Expand Down Expand Up @@ -801,7 +786,7 @@ def test_config_interpolates(greeting, value, expected):
"""
overrides = {"vars.a": greeting}
cfg = Config().from_str(str_cfg, overrides=overrides)
assert type(cfg["project"]["my_par"]) == expected
assert type(cfg["project"]["my_par"]) is expected


@pytest.mark.parametrize(
Expand Down Expand Up @@ -873,7 +858,9 @@ def test_warn_single_quotes():


def test_parse_strings_interpretable_as_ints():
"""Test whether strings interpretable as integers are parsed correctly (i. e. as strings)."""
"""Test whether strings interpretable as integers are parsed correctly
(i. e. as strings).
"""
cfg = Config().from_str(
f"""[a]\nfoo = [${{b.bar}}, "00${{b.bar}}", "y"]\n\n[b]\nbar = 3""" # noqa: F541
)
Expand Down
7 changes: 4 additions & 3 deletions confection/tests/test_frozen_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ def test_frozen_dict():

@pytest.mark.parametrize("frozen_type", ("dict", "list"))
def test_frozen_struct_deepcopy(frozen_type):
"""Test whether setting default values for a FrozenDict/FrozenList works within a config, which utilizes
deepcopy."""
"""Test whether setting default values for a FrozenDict/FrozenList works within a
config, which utilizes deepcopy.
"""
registry.bar = catalogue.create("confection", "bar", entry_points=False)

@registry.bar.register("foo_dict.v1")
Expand All @@ -55,7 +56,7 @@ def make_list(values: List[int] = SimpleFrozenList([1, 2, 3])):
cfg.from_str(
f"""
[something]
@bar = "foo_{frozen_type}.v1"
@bar = "foo_{frozen_type}.v1"
"""
)
)
Expand Down
21 changes: 4 additions & 17 deletions confection/tests/test_registry.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
import inspect
import pickle
import platform
from types import GeneratorType
from typing import (
Any,
Callable,
Dict,
Iterable,
List,
Literal,
Optional,
Sequence,
Tuple,
Union,
)
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union

import catalogue
import pytest
from pydantic import BaseModel, PositiveInt, StrictFloat, constr
from pydantic import BaseModel, PositiveInt
from pydantic.types import StrictBool

from confection import Config, ConfigValidationError, EmptySchema
from confection.tests.util import Cat, make_tempdir, my_registry
from confection import ConfigValidationError
from confection.tests.util import Cat, my_registry
from confection.util import Generator, partial


Expand Down
6 changes: 4 additions & 2 deletions confection/tests/util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Registered functions used for config tests.
"""

import contextlib
import dataclasses
import shutil
Expand Down Expand Up @@ -69,8 +70,9 @@ def Adam(
use_averages: bool = True,
):
"""
Mocks optimizer generation. Note that the returned object is not actually an optimizer. This function is merely used
to illustrate how to use the function registry, e.g. with thinc.
Mocks optimizer generation. Note that the returned object is not actually an
optimizer. This function is merely used to illustrate how to use the function
registry, e.g. with thinc.
"""

@dataclasses.dataclass
Expand Down
3 changes: 1 addition & 2 deletions confection/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@
class Decorator(Protocol):
"""Protocol to mark a function as returning its child with identical signature."""

def __call__(self, name: str) -> Callable[[_DIn], _DIn]:
...
def __call__(self, name: str) -> Callable[[_DIn], _DIn]: ...


# This is how functools.partials seems to do it, too, to retain the return type
Expand Down
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ pytest>=5.2.0,!=7.1.0
mypy>=1.7.0,<1.8.0; platform_machine != 'aarch64' and python_version >= '3.8'
types-dataclasses>=0.1.3; python_version < '3.7'
numpy>=1.15.0
black>=22.0,<23.0
flake8>=3.8.0,<6.0.0
isort>=5.0,<6.0
black>=25,<26
flake8>=7,<8
isort>=7,<8
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ install_requires =
formats = gztar

[flake8]
ignore = E203, E266, E501, E731, W503
max-line-length = 80
ignore = E203, E501, E704, W503
max-line-length = 88
select = B,C,E,F,W,T4,B9
exclude =
.env,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python

if __name__ == "__main__":
from setuptools import setup, find_packages
from setuptools import find_packages, setup

setup(name="confection", packages=find_packages())