Skip to content

Commit f53b6c1

Browse files
authored
Merge pull request #41 from bcdev/forman-config_revision
Config revision
2 parents 4c3ed27 + 43c7b4a commit f53b6c1

25 files changed

+548
-424
lines changed

CHANGES.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# XRLint Change History
22

3+
## Version 0.5.0 (in development)
4+
5+
- Introduced type aliases `ConfigLike` and `ConfigObjectLike`.
6+
- Renamed multiple components for improved clarity and consistency:
7+
- Renamed `Config` into `ConfigObject`
8+
- Renamed `ConfigList.configs` into `config_objects`
9+
- Renamed `ConfigList` into `Config`
10+
- Renamed `ConfigList.compute_config()` into `compute_config_object()`
11+
- Renamed `Result.config` into `config_object`
12+
- Renamed `XRLint.load_config_list()` into `init_config()`
13+
- Renamed `XRLint.verify_datasets()` into `verify_files()`
14+
- Added class method `from_config()` to `ConfigList`.
15+
- Removed function `xrlint.config.merge_configs` as it was no longer used.
16+
317
## Version 0.4.1 (from 2025-01-31)
418

519
### Changes

docs/api.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ This chapter provides a plain reference for the XRLint Python API.
1616
plugin metadata represented by [PluginMeta][xrlint.plugin.PluginMeta].
1717
- The `config` module provides classes that represent
1818
configuration information and provide related functionality:
19-
[Config][xrlint.config.Config] and [ConfigList][xrlint.config.ConfigList].
19+
[Config][xrlint.config.Config] and [ConfigObject][xrlint.config.ConfigObject].
2020
- The `rule` module provides rule related classes and functions:
2121
[Rule][xrlint.rule.Rule] comprising rule metadata,
2222
[RuleMeta][xrlint.rule.RuleMeta], the rule validation operations in
@@ -60,7 +60,11 @@ Note:
6060

6161
::: xrlint.config.Config
6262

63-
::: xrlint.config.ConfigList
63+
::: xrlint.config.ConfigObject
64+
65+
::: xrlint.config.ConfigLike
66+
67+
::: xrlint.config.ConfigObjectLike
6468

6569
::: xrlint.rule.define_rule
6670

examples/check_s3_bucket.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import xrlint.all as xrl
2+
3+
URL = "s3://xcube-test/"
4+
5+
xrlint = xrl.XRLint(no_config_lookup=True)
6+
xrlint.init_config("recommended")
7+
results = xrlint.verify_files([URL])
8+
print(xrlint.format_results(results))

mkruleref.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def write_plugin_rules(stream, plugin: Plugin):
6262
stream.write("\n\n")
6363

6464

65-
def get_plugin_rule_configs(plugin):
65+
def get_plugin_rule_configs(plugin: Plugin) -> dict[str, dict[str, RuleConfig]]:
6666
configs = plugin.configs
6767
config_rules: dict[str, dict[str, RuleConfig]] = {}
6868
for config_name, config_list in configs.items():

tests/_linter/test_rulectx.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@
44

55
# noinspection PyProtectedMember
66
from xrlint._linter.rulectx import RuleContextImpl
7-
from xrlint.config import Config
7+
from xrlint.config import ConfigObject
88
from xrlint.constants import NODE_ROOT_NAME
99
from xrlint.result import Message, Suggestion
1010

1111

1212
class RuleContextImplTest(TestCase):
1313
def test_defaults(self):
14-
config = Config()
14+
config_obj = ConfigObject()
1515
dataset = xr.Dataset()
16-
context = RuleContextImpl(config, dataset, "./ds.zarr", None)
17-
self.assertIs(config, context.config)
16+
context = RuleContextImpl(config_obj, dataset, "./ds.zarr", None)
17+
self.assertIs(config_obj, context.config)
1818
self.assertIs(dataset, context.dataset)
1919
self.assertEqual({}, context.settings)
2020
self.assertEqual("./ds.zarr", context.file_path)
2121
self.assertEqual(None, context.file_index)
2222

2323
def test_report(self):
24-
context = RuleContextImpl(Config(), xr.Dataset(), "./ds.zarr", None)
24+
context = RuleContextImpl(ConfigObject(), xr.Dataset(), "./ds.zarr", None)
2525
with context.use_state(rule_id="no-xxx"):
2626
context.report(
2727
"What the heck do you mean?",

tests/cli/test_config.py

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
import pytest
77

8-
from xrlint.cli.config import ConfigError, read_config_list
9-
from xrlint.config import Config, ConfigList
8+
from xrlint.cli.config import ConfigError, read_config
9+
from xrlint.config import Config, ConfigObject
1010
from xrlint.rule import RuleConfig
1111

1212
from .helpers import text_file
@@ -58,24 +58,24 @@ def new_config_py(self):
5858

5959
def test_read_config_yaml(self):
6060
with text_file("config.yaml", yaml_text) as config_path:
61-
config = read_config_list(config_path)
61+
config = read_config(config_path)
6262
self.assert_config_ok(config, "yaml-test")
6363

6464
def test_read_config_json(self):
6565
with text_file("config.json", json_text) as config_path:
66-
config = read_config_list(config_path)
66+
config = read_config(config_path)
6767
self.assert_config_ok(config, "json-test")
6868

6969
def test_read_config_py(self):
7070
with text_file(self.new_config_py(), py_text) as config_path:
71-
config = read_config_list(config_path)
71+
config = read_config(config_path)
7272
self.assert_config_ok(config, "py-test")
7373

7474
def assert_config_ok(self, config: Any, name: str):
7575
self.assertEqual(
76-
ConfigList(
76+
Config(
7777
[
78-
Config(
78+
ConfigObject(
7979
name=name,
8080
rules={
8181
"rule-1": RuleConfig(2),
@@ -94,7 +94,7 @@ def test_read_config_invalid_arg(self):
9494
match="configuration file must be of type str|Path|PathLike, but got None",
9595
):
9696
# noinspection PyTypeChecker
97-
read_config_list(None)
97+
read_config(None)
9898

9999
def test_read_config_json_with_format_error(self):
100100
with text_file("config.json", "{") as config_path:
@@ -106,34 +106,34 @@ def test_read_config_json_with_format_error(self):
106106
" line 1 column 2 \\(char 1\\)"
107107
),
108108
):
109-
read_config_list(config_path)
109+
read_config(config_path)
110110

111111
def test_read_config_yaml_with_format_error(self):
112112
with text_file("config.yaml", "}") as config_path:
113113
with pytest.raises(
114114
ConfigError,
115115
match="config.yaml: while parsing a block node",
116116
):
117-
read_config_list(config_path)
117+
read_config(config_path)
118118

119119
def test_read_config_yaml_with_type_error(self):
120120
with text_file("config.yaml", "97") as config_path:
121121
with pytest.raises(
122122
ConfigError,
123123
match=(
124-
r"config\.yaml\: config_list must be of"
125-
r" type ConfigList \| list\[Config \| dict \| str\],"
124+
r"config\.yaml\: config must be of type"
125+
r" Config \| ConfigObjectLike \| str \| Sequence\[ConfigObjectLike \| str\],"
126126
r" but got int"
127127
),
128128
):
129-
read_config_list(config_path)
129+
read_config(config_path)
130130

131131
def test_read_config_with_unknown_format(self):
132132
with pytest.raises(
133133
ConfigError,
134134
match="config.toml: unsupported configuration file format",
135135
):
136-
read_config_list("config.toml")
136+
read_config("config.toml")
137137

138138
def test_read_config_py_no_export(self):
139139
py_code = "x = 42\n"
@@ -145,7 +145,7 @@ def test_read_config_py_no_export(self):
145145
" not found in module 'config_1002'"
146146
),
147147
):
148-
read_config_list(config_path)
148+
read_config(config_path)
149149

150150
def test_read_config_py_with_value_error(self):
151151
py_code = "def export_config():\n raise ValueError('value is useless!')\n"
@@ -154,7 +154,7 @@ def test_read_config_py_with_value_error(self):
154154
ValueError,
155155
match="value is useless!",
156156
):
157-
read_config_list(config_path)
157+
read_config(config_path)
158158

159159
def test_read_config_py_with_os_error(self):
160160
py_code = "def export_config():\n raise OSError('where is my hat?')\n"
@@ -163,47 +163,47 @@ def test_read_config_py_with_os_error(self):
163163
ConfigError,
164164
match="where is my hat?",
165165
):
166-
read_config_list(config_path)
166+
read_config(config_path)
167167

168168
def test_read_config_py_with_invalid_config_list(self):
169169
py_code = "def export_config():\n return 42\n"
170170
with text_file(self.new_config_py(), py_code) as config_path:
171171
with pytest.raises(
172172
ConfigError,
173173
match=(
174-
r"\.py: return value of export_config\(\):"
175-
r" config_list must be of type"
176-
r" ConfigList \| list\[Config\ | dict \| str\],"
174+
r"\.py: failed converting value of 'config_1003:export_config':"
175+
r" config must be of type"
176+
r" Config \| ConfigObjectLike \| str \| Sequence\[ConfigObjectLike \| str\],"
177177
r" but got int"
178178
),
179179
):
180-
read_config_list(config_path)
180+
read_config(config_path)
181181

182182

183183
class CliConfigResolveTest(unittest.TestCase):
184184
def test_read_config_py(self):
185185
self.assert_ok(
186-
read_config_list(Path(__file__).parent / "configs" / "recommended.py")
186+
read_config(Path(__file__).parent / "configs" / "recommended.py")
187187
)
188188

189189
def test_read_config_json(self):
190190
self.assert_ok(
191-
read_config_list(Path(__file__).parent / "configs" / "recommended.json")
191+
read_config(Path(__file__).parent / "configs" / "recommended.json")
192192
)
193193

194194
def test_read_config_yaml(self):
195195
self.assert_ok(
196-
read_config_list(Path(__file__).parent / "configs" / "recommended.yaml")
196+
read_config(Path(__file__).parent / "configs" / "recommended.yaml")
197197
)
198198

199-
def assert_ok(self, config_list: ConfigList):
200-
self.assertIsInstance(config_list, ConfigList)
201-
self.assertEqual(7, len(config_list.configs))
202-
config = config_list.compute_config("test.zarr")
199+
def assert_ok(self, config: Config):
203200
self.assertIsInstance(config, Config)
204-
self.assertEqual(None, config.name)
205-
self.assertIsInstance(config.plugins, dict)
206-
self.assertEqual({"xcube"}, set(config.plugins.keys()))
207-
self.assertIsInstance(config.rules, dict)
208-
self.assertIn("coords-for-dims", config.rules)
209-
self.assertIn("xcube/cube-dims-order", config.rules)
201+
self.assertEqual(7, len(config.objects))
202+
config_obj = config.compute_config_object("test.zarr")
203+
self.assertIsInstance(config_obj, ConfigObject)
204+
self.assertEqual(None, config_obj.name)
205+
self.assertIsInstance(config_obj.plugins, dict)
206+
self.assertEqual({"xcube"}, set(config_obj.plugins.keys()))
207+
self.assertIsInstance(config_obj.rules, dict)
208+
self.assertIn("coords-for-dims", config_obj.rules)
209+
self.assertIn("xcube/cube-dims-order", config_obj.rules)

tests/cli/test_main.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,24 @@ def test_files_no_config(self):
7878
self.assertIn("Warning: no configuration file found.\n", result.output)
7979
self.assertEqual(1, result.exit_code)
8080

81+
def test_files_no_config_lookup(self):
82+
with text_file(DEFAULT_CONFIG_FILE_YAML, self.ok_config_yaml):
83+
result = self.xrlint("--no-config-lookup", "--no-color", *self.files)
84+
self.assertEqual(
85+
"\n"
86+
"dataset1.zarr:\n"
87+
"dataset error No rules configured or applicable.\n\n"
88+
"dataset1.nc:\n"
89+
"dataset error No rules configured or applicable.\n\n"
90+
"dataset2.zarr:\n"
91+
"dataset error No rules configured or applicable.\n\n"
92+
"dataset2.nc:\n"
93+
"dataset error No rules configured or applicable.\n\n"
94+
"4 errors\n\n",
95+
result.output,
96+
)
97+
self.assertEqual(1, result.exit_code)
98+
8199
def test_files_one_rule(self):
82100
with text_file(DEFAULT_CONFIG_FILE_YAML, self.ok_config_yaml):
83101
result = self.xrlint("--no-color", *self.files)

tests/formatters/helpers.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from xrlint.config import Config
1+
from xrlint.config import ConfigObject
22
from xrlint.formatter import FormatterContext
33
from xrlint.plugin import new_plugin
44
from xrlint.result import Message, Result, ResultStats
@@ -38,11 +38,11 @@ class Rule1(RuleOp):
3838
class Rule2(RuleOp):
3939
pass
4040

41-
config = Config(plugins={"test": plugin})
41+
config_obj = ConfigObject(plugins={"test": plugin})
4242

4343
return [
4444
Result.new(
45-
config,
45+
config_object=config_obj,
4646
file_path="test.nc",
4747
messages=[
4848
Message(message="message-1", rule_id="test/rule-1", severity=2),
@@ -51,7 +51,7 @@ class Rule2(RuleOp):
5151
],
5252
),
5353
Result.new(
54-
config,
54+
config_object=config_obj,
5555
file_path="test-2.nc",
5656
messages=[
5757
Message(message="message-1", rule_id="test/rule-1", severity=1),

tests/formatters/test_simple.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
from unittest import TestCase
22

33
from tests.formatters.helpers import get_context
4-
from xrlint.config import Config
4+
from xrlint.config import ConfigObject
55
from xrlint.formatters.simple import Simple
66
from xrlint.result import Message, Result
77

88

99
class SimpleTest(TestCase):
1010
errors_and_warnings = [
1111
Result.new(
12-
Config(),
12+
config_object=ConfigObject(),
1313
file_path="test1.nc",
1414
messages=[
1515
Message(message="what", rule_id="rule-1", severity=2),
@@ -21,7 +21,7 @@ class SimpleTest(TestCase):
2121

2222
warnings_only = [
2323
Result.new(
24-
Config(),
24+
ConfigObject(),
2525
file_path="test2.nc",
2626
messages=[
2727
Message(message="what", rule_id="rule-1", severity=1),

0 commit comments

Comments
 (0)