checkup 0.6.0__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {checkup-0.6.0 → checkup-0.7.0}/PKG-INFO +6 -1
- {checkup-0.6.0 → checkup-0.7.0}/README.md +5 -0
- {checkup-0.6.0 → checkup-0.7.0}/pyproject.toml +28 -32
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/commands/run.py +17 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/executor.py +25 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/utils.py +2 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/configuration/io.py +2 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/configuration/models.py +2 -6
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/configuration/schema.py +8 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/metric.py +2 -1
- checkup-0.7.0/src/checkup/selection.py +103 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/__init__.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/__init__.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/commands/__init__.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/commands/config.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/commands/init.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/commands/plugins.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/commands/schema.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/config_wizard/__init__.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/config_wizard/_common.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/config_wizard/create.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/cli/config_wizard/edit.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/config.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/configuration/__init__.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/configuration/env.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/errors.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/executor/__init__.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/executor/batch_executors.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/executor/metric_calculator.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/executor/provider_executor.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/executor/state.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/graph.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/hub.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/materializers/__init__.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/materializers/base.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/materializers/console.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/materializers/csv_file.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/materializers/database.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/materializers/html_report.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/materializers/markdown.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/measurement.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/provider.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/providers/__init__.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/providers/tags.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/registry/__init__.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/registry/discovery.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/templates/metrics_report.html +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/types.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/utils.py +0 -0
- {checkup-0.6.0 → checkup-0.7.0}/src/checkup/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: checkup
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Computational governance framework for measuring data product health
|
|
5
5
|
Author: Jan Vanbuel
|
|
6
6
|
Author-email: Jan Vanbuel <jan.vanbuel@ond.vlaanderen.be>
|
|
@@ -32,6 +32,11 @@ Description-Content-Type: text/markdown
|
|
|
32
32
|
<a href="https://pypi.org/project/checkup/"><img src="https://img.shields.io/pypi/pyversions/checkup" alt="Python versions" /></a>
|
|
33
33
|
</p>
|
|
34
34
|
|
|
35
|
+
<p align="center">
|
|
36
|
+
<!-- <img src="https://raw.githubusercontent.com/datamindedbe/checkup/main/images/demo.gif" alt="CheckUp demo" width="100%" /> -->
|
|
37
|
+
<img src="./images/demo.gif" alt="CheckUp demo" width="100%" />
|
|
38
|
+
</p>
|
|
39
|
+
|
|
35
40
|
## Plugins
|
|
36
41
|
|
|
37
42
|
Checkup uses plugins to provide metrics for different tools and platforms.
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
<a href="https://pypi.org/project/checkup/"><img src="https://img.shields.io/pypi/pyversions/checkup" alt="Python versions" /></a>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
|
+
<p align="center">
|
|
14
|
+
<!-- <img src="https://raw.githubusercontent.com/datamindedbe/checkup/main/images/demo.gif" alt="CheckUp demo" width="100%" /> -->
|
|
15
|
+
<img src="./images/demo.gif" alt="CheckUp demo" width="100%" />
|
|
16
|
+
</p>
|
|
17
|
+
|
|
13
18
|
## Plugins
|
|
14
19
|
|
|
15
20
|
Checkup uses plugins to provide metrics for different tools and platforms.
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "checkup"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
description = "Computational governance framework for measuring data product health"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Jan Vanbuel", email = "jan.vanbuel@ond.vlaanderen.be" }]
|
|
7
7
|
requires-python = ">=3.12"
|
|
8
8
|
classifiers = [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
"Programming Language :: Python :: 3",
|
|
10
|
+
"Programming Language :: Python :: 3.12",
|
|
11
|
+
"Programming Language :: Python :: 3.13",
|
|
12
12
|
]
|
|
13
13
|
dependencies = [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
"jinja2>=3.1.6",
|
|
15
|
+
"pydantic>=2.11.7",
|
|
16
|
+
"pyyaml>=6.0",
|
|
17
|
+
"questionary>=2.0",
|
|
18
|
+
"rich>=13.0",
|
|
19
|
+
"sqlalchemy>=2.0",
|
|
20
|
+
"typer>=0.24",
|
|
21
21
|
]
|
|
22
22
|
|
|
23
23
|
[project.urls]
|
|
@@ -41,23 +41,19 @@ build-backend = "uv_build"
|
|
|
41
41
|
|
|
42
42
|
[tool.uv.workspace]
|
|
43
43
|
members = [
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
"plugins/checkup-git",
|
|
45
|
+
"plugins/checkup-dbt",
|
|
46
|
+
"plugins/checkup-python",
|
|
47
|
+
"plugins/checkup-conveyor",
|
|
48
|
+
"plugins/checkup-gitlab",
|
|
49
|
+
"plugins/checkup-bitbucket",
|
|
50
|
+
"plugins/checkup-github",
|
|
51
|
+
"plugins/checkup-airflow",
|
|
52
52
|
]
|
|
53
53
|
|
|
54
54
|
[dependency-groups]
|
|
55
55
|
dev = ["pytest>=9.0.2", "pytest-cov>=7.0.0", "ruff>=0.8.6", "prek>=0.3.0"]
|
|
56
|
-
docs = [
|
|
57
|
-
"mkdocs>=1.6.0",
|
|
58
|
-
"mkdocs-material>=9.5.0",
|
|
59
|
-
"pymdown-extensions>=10.0",
|
|
60
|
-
]
|
|
56
|
+
docs = ["mkdocs>=1.6.0", "mkdocs-material>=9.5.0", "pymdown-extensions>=10.0"]
|
|
61
57
|
|
|
62
58
|
[tool.pytest.ini_options]
|
|
63
59
|
pythonpath = [".", "tests"]
|
|
@@ -69,16 +65,16 @@ line-length = 88
|
|
|
69
65
|
|
|
70
66
|
[tool.ruff.lint]
|
|
71
67
|
select = [
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
"E", # pycodestyle errors
|
|
69
|
+
"W", # pycodestyle warnings
|
|
70
|
+
"F", # Pyflakes
|
|
71
|
+
"I", # isort
|
|
72
|
+
"B", # flake8-bugbear
|
|
73
|
+
"C4", # flake8-comprehensions
|
|
74
|
+
"UP", # pyupgrade
|
|
79
75
|
]
|
|
80
76
|
ignore = [
|
|
81
|
-
|
|
77
|
+
"E501", # line too long (handled by formatter)
|
|
82
78
|
]
|
|
83
79
|
|
|
84
80
|
[tool.ruff.format]
|
|
@@ -36,6 +36,21 @@ def run(
|
|
|
36
36
|
"--materializer", help="Set materializer (type or type:key=value)"
|
|
37
37
|
),
|
|
38
38
|
] = None,
|
|
39
|
+
select: Annotated[
|
|
40
|
+
str | None,
|
|
41
|
+
typer.Option(
|
|
42
|
+
"--select",
|
|
43
|
+
"-s",
|
|
44
|
+
help="Only materialize matching metrics ('method:pattern'). Methods: name (default), tag, type.",
|
|
45
|
+
),
|
|
46
|
+
] = None,
|
|
47
|
+
exclude: Annotated[
|
|
48
|
+
str | None,
|
|
49
|
+
typer.Option(
|
|
50
|
+
"--exclude",
|
|
51
|
+
help="Exclude matching metrics from materialization. Same format as --select.",
|
|
52
|
+
),
|
|
53
|
+
] = None,
|
|
39
54
|
dry_run: Annotated[
|
|
40
55
|
bool,
|
|
41
56
|
typer.Option("--dry-run", help="Don't materialize, just print"),
|
|
@@ -71,4 +86,6 @@ def run(
|
|
|
71
86
|
materializer="console" if dry_run else materializer,
|
|
72
87
|
multiprocessing=multiprocessing,
|
|
73
88
|
quiet=quiet,
|
|
89
|
+
select=select,
|
|
90
|
+
exclude=exclude,
|
|
74
91
|
)
|
|
@@ -12,6 +12,7 @@ from checkup.hub import CheckHub
|
|
|
12
12
|
from checkup.materializers import ConsoleMaterializer
|
|
13
13
|
from checkup.providers.tags import TagProvider
|
|
14
14
|
from checkup.registry import get_registry
|
|
15
|
+
from checkup.selection import select_metrics
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
from checkup.materializers import Materializer
|
|
@@ -28,6 +29,8 @@ def execute_checkup(
|
|
|
28
29
|
materializer: str | None = None,
|
|
29
30
|
multiprocessing: bool = True,
|
|
30
31
|
quiet: bool = False,
|
|
32
|
+
select: str | None = None,
|
|
33
|
+
exclude: str | None = None,
|
|
31
34
|
) -> None:
|
|
32
35
|
"""
|
|
33
36
|
Execute checkup with the given configuration.
|
|
@@ -37,6 +40,8 @@ def execute_checkup(
|
|
|
37
40
|
materializer: Override materializer type (e.g., "console")
|
|
38
41
|
multiprocessing: If False, run sequentially without subprocesses
|
|
39
42
|
quiet: If True, send status/errors to stderr so stdout holds only the materializer output.
|
|
43
|
+
select: Selector for which metrics to materialize.
|
|
44
|
+
exclude: Selector for metrics to exclude from materialization.
|
|
40
45
|
"""
|
|
41
46
|
|
|
42
47
|
out = Console(stderr=True) if quiet else console
|
|
@@ -55,6 +60,10 @@ def execute_checkup(
|
|
|
55
60
|
|
|
56
61
|
materializer = _resolve_materializer(config, registry, materializer, out)
|
|
57
62
|
|
|
63
|
+
select = select if select is not None else config.select
|
|
64
|
+
exclude = exclude if exclude is not None else config.exclude
|
|
65
|
+
type_by_name = {mc.instance_name: mc.type for mc in config.metrics}
|
|
66
|
+
|
|
58
67
|
out.print(f"[blue]Running {len(metrics)} metrics...[/blue]")
|
|
59
68
|
|
|
60
69
|
result = (
|
|
@@ -68,6 +77,22 @@ def execute_checkup(
|
|
|
68
77
|
for _, error in result.errors:
|
|
69
78
|
out.print(f"[red]Error: {error}[/red]")
|
|
70
79
|
|
|
80
|
+
if select or exclude:
|
|
81
|
+
selected = select_metrics(
|
|
82
|
+
metrics,
|
|
83
|
+
select=select,
|
|
84
|
+
exclude=exclude,
|
|
85
|
+
type_resolver=lambda metric: type_by_name.get(metric.name),
|
|
86
|
+
)
|
|
87
|
+
result.measurements = [
|
|
88
|
+
m for m in result.measurements if m.metric.name in selected
|
|
89
|
+
]
|
|
90
|
+
result.direct_metric_names = result.direct_metric_names & selected
|
|
91
|
+
|
|
92
|
+
console.print(
|
|
93
|
+
f"[blue]Materializing {len(selected)} selected of {len(metrics)} total metrics[/blue]"
|
|
94
|
+
)
|
|
95
|
+
|
|
71
96
|
result.materialize(materializer)
|
|
72
97
|
|
|
73
98
|
|
|
@@ -8,8 +8,6 @@ from pydantic import BaseModel, Field
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class ProviderConfig(BaseModel):
|
|
11
|
-
"""Configuration for a single provider."""
|
|
12
|
-
|
|
13
11
|
name: str
|
|
14
12
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
15
13
|
|
|
@@ -25,19 +23,17 @@ class MetricConfig(BaseModel):
|
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
class MaterializerConfig(BaseModel):
|
|
28
|
-
"""Configuration for the materializer."""
|
|
29
|
-
|
|
30
26
|
type: str
|
|
31
27
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
32
28
|
|
|
33
29
|
|
|
34
30
|
class CheckupConfig(BaseModel):
|
|
35
|
-
"""Complete checkup configuration."""
|
|
36
|
-
|
|
37
31
|
tags: dict[str, Any] = Field(default_factory=dict)
|
|
38
32
|
providers: list[ProviderConfig] = Field(default_factory=list)
|
|
39
33
|
metrics: list[MetricConfig] = Field(default_factory=list)
|
|
40
34
|
materializer: MaterializerConfig | None = None
|
|
35
|
+
select: str | None = None
|
|
36
|
+
exclude: str | None = None
|
|
41
37
|
|
|
42
38
|
@classmethod
|
|
43
39
|
def empty(cls) -> "CheckupConfig":
|
|
@@ -182,6 +182,14 @@ def generate_schema() -> dict:
|
|
|
182
182
|
"description": "Tags to identify the data product (e.g., product, team)",
|
|
183
183
|
"additionalProperties": {"type": "string"},
|
|
184
184
|
},
|
|
185
|
+
"select": {
|
|
186
|
+
"type": "string",
|
|
187
|
+
"description": "Default selector for materialized output.",
|
|
188
|
+
},
|
|
189
|
+
"exclude": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"description": "Default selector for materialization to exclude.",
|
|
192
|
+
},
|
|
185
193
|
"providers": {
|
|
186
194
|
"type": "array",
|
|
187
195
|
"description": "Data providers for context enrichment",
|
|
@@ -6,7 +6,7 @@ from abc import ABC, abstractmethod
|
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
|
-
from pydantic import BaseModel
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
10
|
|
|
11
11
|
from checkup.types import Context
|
|
12
12
|
|
|
@@ -44,6 +44,7 @@ class Metric(ABC, BaseModel):
|
|
|
44
44
|
name: str
|
|
45
45
|
description: str = ""
|
|
46
46
|
unit: str = ""
|
|
47
|
+
tags: list[str] = Field(default_factory=list)
|
|
47
48
|
|
|
48
49
|
model_config = {"frozen": True}
|
|
49
50
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Metric selection for filtering materialized output.
|
|
3
|
+
|
|
4
|
+
Selection filters which measurements get materialized, it never changes what
|
|
5
|
+
is calculated, dependencies are always computed.
|
|
6
|
+
|
|
7
|
+
A selector is a set of whitespace-separated atoms (a union).
|
|
8
|
+
Each atom is ``[method:]pattern`` where:
|
|
9
|
+
|
|
10
|
+
- ``method`` is ``name`` (the default), ``tag`` or ``type``
|
|
11
|
+
- ``pattern`` supports ``*`` wildcards
|
|
12
|
+
|
|
13
|
+
Examples::
|
|
14
|
+
|
|
15
|
+
healthcheck_*
|
|
16
|
+
name:healthcheck_*
|
|
17
|
+
tag:healthcheck
|
|
18
|
+
type:git_tracked_file_count tag:healthcheck
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import fnmatch
|
|
22
|
+
from collections.abc import Callable, Iterable
|
|
23
|
+
from enum import StrEnum
|
|
24
|
+
from typing import assert_never
|
|
25
|
+
|
|
26
|
+
from checkup.metric import Metric
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SelectionMethod(StrEnum):
|
|
30
|
+
NAME = "name"
|
|
31
|
+
TAG = "tag"
|
|
32
|
+
TYPE = "type"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Resolves a metric instance to its registered type name.
|
|
36
|
+
# Names are needed by the filter, with a resolver being offered as argument
|
|
37
|
+
# because the name-type mapping is defined by the config.
|
|
38
|
+
TypeResolver = Callable[[Metric], str | None]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def select_metrics(
|
|
42
|
+
metrics: Iterable[Metric],
|
|
43
|
+
select: str | None = None,
|
|
44
|
+
exclude: str | None = None,
|
|
45
|
+
type_resolver: TypeResolver | None = None,
|
|
46
|
+
) -> set[str]:
|
|
47
|
+
"""
|
|
48
|
+
Return the names of the metrics whose measurements should be materialized.
|
|
49
|
+
|
|
50
|
+
With no ``select`` every metric is included;
|
|
51
|
+
``exclude`` (if given) is then removed from the set.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
metrics = list(metrics)
|
|
55
|
+
type_resolver = type_resolver or (lambda _name: None)
|
|
56
|
+
|
|
57
|
+
if select:
|
|
58
|
+
selected = _match_selector(select, metrics, type_resolver)
|
|
59
|
+
else:
|
|
60
|
+
selected = {metric.name for metric in metrics}
|
|
61
|
+
|
|
62
|
+
if exclude:
|
|
63
|
+
selected -= _match_selector(exclude, metrics, type_resolver)
|
|
64
|
+
|
|
65
|
+
return selected
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _match_selector(
|
|
69
|
+
selector: str,
|
|
70
|
+
metrics: list[Metric],
|
|
71
|
+
type_resolver: TypeResolver,
|
|
72
|
+
) -> set[str]:
|
|
73
|
+
atoms = selector.split()
|
|
74
|
+
return {
|
|
75
|
+
metric.name
|
|
76
|
+
for metric in metrics
|
|
77
|
+
if any(_match_atom(atom, metric, type_resolver) for atom in atoms)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _match_atom(
|
|
82
|
+
atom: str,
|
|
83
|
+
metric: Metric,
|
|
84
|
+
type_resolver: TypeResolver,
|
|
85
|
+
*,
|
|
86
|
+
default_method: SelectionMethod = SelectionMethod.NAME,
|
|
87
|
+
) -> bool:
|
|
88
|
+
method_name, separator, pattern = atom.partition(":")
|
|
89
|
+
if not separator:
|
|
90
|
+
method, pattern = default_method, atom
|
|
91
|
+
else:
|
|
92
|
+
method = SelectionMethod(method_name)
|
|
93
|
+
|
|
94
|
+
match method:
|
|
95
|
+
case SelectionMethod.NAME:
|
|
96
|
+
return fnmatch.fnmatch(metric.name, pattern)
|
|
97
|
+
case SelectionMethod.TAG:
|
|
98
|
+
return any(fnmatch.fnmatch(tag, pattern) for tag in metric.tags)
|
|
99
|
+
case SelectionMethod.TYPE:
|
|
100
|
+
metric_type = type_resolver(metric)
|
|
101
|
+
return metric_type is not None and fnmatch.fnmatch(metric_type, pattern)
|
|
102
|
+
case _:
|
|
103
|
+
assert_never(method)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|