loxo-cli 0.2.2__tar.gz → 0.3.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.
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/CHANGELOG.md +24 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/PKG-INFO +4 -2
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/README.md +3 -1
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/pyproject.toml +1 -1
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/activities.py +6 -3
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/ref.py +48 -4
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_cmd_activities.py +25 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_cmd_ref.py +85 -3
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/uv.lock +1 -1
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/.github/workflows/ci.yml +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/.github/workflows/publish.yml +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/.gitignore +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/LICENSE +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/__init__.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/__main__.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/client.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/__init__.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/_app.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/_helpers.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/api.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/candidates.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/companies.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/configure.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/deals.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/jobs.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/people.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/commands/webhooks.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/config.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/errors.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/models/__init__.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/models/base.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/models/candidate.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/models/company.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/models/deal.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/models/job.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/models/person.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/models/reference.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/models/webhook.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/output.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/src/loxo_cli/pagination.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/conftest.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_app_state.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_client.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_cmd_api.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_cmd_candidates.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_cmd_companies.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_cmd_configure.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_cmd_deals.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_cmd_jobs.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_cmd_people.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_cmd_webhooks.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_config.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_error_exit_codes.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_errors.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_helpers.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_models.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_output.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_pagination.py +0 -0
- {loxo_cli-0.2.2 → loxo_cli-0.3.0}/tests/test_smoke.py +0 -0
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `loxo ref custom-fields` gains `--object/-o` and `--custom-only`. `--object`
|
|
8
|
+
filters to one object's fields by matching `item_type` case-insensitively
|
|
9
|
+
(e.g. `--object deal`); an unknown value errors with the available object
|
|
10
|
+
types. `--custom-only` hides built-in fields, leaving just the agency-defined
|
|
11
|
+
ones. The table now shows `key`, `name`, and `type` (plus `item_type` when
|
|
12
|
+
unfiltered) instead of `id`/`name`, so the opaque keys (`custom_text_3`) map to
|
|
13
|
+
their plain-language names at a glance. `--json` carries the full field record
|
|
14
|
+
plus a derived flat `type`.
|
|
15
|
+
|
|
16
|
+
## [0.2.3]
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- `loxo activities list` drops the `--job-id` filter and gains `--company-id`.
|
|
21
|
+
Loxo's `person_events` endpoint rejects `job_id` as a query parameter (HTTP
|
|
22
|
+
422 `Invalid parameters: [:job_id]`), so the flag never worked. `person_id`
|
|
23
|
+
and `company_id` are the only server-side filters the endpoint accepts.
|
|
24
|
+
(`activities add --job-id` is unaffected — there `job_id` is a request-body
|
|
25
|
+
field, not a query parameter.)
|
|
26
|
+
|
|
3
27
|
## [0.2.2]
|
|
4
28
|
|
|
5
29
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loxo-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Unofficial command-line interface for the Loxo recruiting ATS/CRM API
|
|
5
5
|
Project-URL: Homepage, https://github.com/alphaomegateam/loxo-cli
|
|
6
6
|
License: MIT
|
|
@@ -91,7 +91,9 @@ printed by `loxo configure list`, logged, or shown in `--verbose` output.
|
|
|
91
91
|
|
|
92
92
|
Custom (dynamic) fields are supported on writes via repeatable `--field key=value`
|
|
93
93
|
(use `key[]=value` to force a list, e.g. hierarchy fields). Discover valid keys with
|
|
94
|
-
`loxo ref custom-fields
|
|
94
|
+
`loxo ref custom-fields`, which maps each key (`custom_text_3`) to its plain-language
|
|
95
|
+
name and type. Filter to one object with `--object deal` (matches the field's
|
|
96
|
+
`item_type`, case-insensitive) and hide built-ins with `--custom-only`.
|
|
95
97
|
|
|
96
98
|
## Output
|
|
97
99
|
|
|
@@ -76,7 +76,9 @@ printed by `loxo configure list`, logged, or shown in `--verbose` output.
|
|
|
76
76
|
|
|
77
77
|
Custom (dynamic) fields are supported on writes via repeatable `--field key=value`
|
|
78
78
|
(use `key[]=value` to force a list, e.g. hierarchy fields). Discover valid keys with
|
|
79
|
-
`loxo ref custom-fields
|
|
79
|
+
`loxo ref custom-fields`, which maps each key (`custom_text_3`) to its plain-language
|
|
80
|
+
name and type. Filter to one object with `--object deal` (matches the field's
|
|
81
|
+
`item_type`, case-insensitive) and hide built-ins with `--custom-only`.
|
|
80
82
|
|
|
81
83
|
## Output
|
|
82
84
|
|
|
@@ -16,16 +16,19 @@ activities_app = typer.Typer(
|
|
|
16
16
|
def list_activities(
|
|
17
17
|
ctx: typer.Context,
|
|
18
18
|
person_id: Optional[int] = typer.Option(None, "--person-id"),
|
|
19
|
-
|
|
19
|
+
company_id: Optional[int] = typer.Option(None, "--company-id"),
|
|
20
20
|
all_pages: bool = typer.Option(False, "--all"),
|
|
21
21
|
per_page: int = typer.Option(50, "--per-page"),
|
|
22
22
|
) -> None:
|
|
23
|
+
# Loxo's person_events endpoint only accepts person_id / company_id as
|
|
24
|
+
# server-side filters. job_id is not a valid query param (returns 422), so
|
|
25
|
+
# it is intentionally not exposed here — filter by person or company instead.
|
|
23
26
|
state = ctx.obj
|
|
24
27
|
params: dict = {}
|
|
25
28
|
if person_id is not None:
|
|
26
29
|
params["person_id"] = person_id
|
|
27
|
-
if
|
|
28
|
-
params["
|
|
30
|
+
if company_id is not None:
|
|
31
|
+
params["company_id"] = company_id
|
|
29
32
|
client = state.client()
|
|
30
33
|
if all_pages:
|
|
31
34
|
rows = list(
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
3
5
|
import typer
|
|
4
6
|
|
|
5
7
|
from loxo_cli.pagination import extract_items, paginate
|
|
@@ -52,16 +54,58 @@ def lists(ctx: typer.Context) -> None:
|
|
|
52
54
|
_list_reference(ctx, "person_lists", paginated=False)
|
|
53
55
|
|
|
54
56
|
|
|
57
|
+
def _emit_custom_fields(
|
|
58
|
+
ctx: typer.Context, object_filter: Optional[str], custom_only: bool
|
|
59
|
+
) -> None:
|
|
60
|
+
state = ctx.obj
|
|
61
|
+
# dynamic_fields returns its full config list in one response and ignores
|
|
62
|
+
# after_id, so fetch once (driving the keyset paginator would loop -> 429).
|
|
63
|
+
rows: list[dict[str, Any]] = extract_items(state.client().get("dynamic_fields"), None)
|
|
64
|
+
if object_filter:
|
|
65
|
+
wanted = object_filter.strip().lower()
|
|
66
|
+
available = sorted({r["item_type"] for r in rows if r.get("item_type")})
|
|
67
|
+
if wanted not in {a.lower() for a in available}:
|
|
68
|
+
raise typer.BadParameter(
|
|
69
|
+
f"Unknown object {object_filter!r}. Available: {', '.join(available)}",
|
|
70
|
+
param_hint="--object",
|
|
71
|
+
)
|
|
72
|
+
rows = [r for r in rows if (r.get("item_type") or "").lower() == wanted]
|
|
73
|
+
if custom_only:
|
|
74
|
+
rows = [r for r in rows if not r.get("built_in")]
|
|
75
|
+
for r in rows:
|
|
76
|
+
r["type"] = (r.get("dynamic_field_type") or {}).get("name")
|
|
77
|
+
# item_type is constant once filtered, so drop that column.
|
|
78
|
+
cols = ["key", "name", "type"] if object_filter else ["key", "name", "type", "item_type"]
|
|
79
|
+
state.emit(rows, columns=cols)
|
|
80
|
+
|
|
81
|
+
|
|
55
82
|
@ref_app.command("custom-fields")
|
|
56
|
-
def custom_fields(
|
|
83
|
+
def custom_fields(
|
|
84
|
+
ctx: typer.Context,
|
|
85
|
+
object_filter: Optional[str] = typer.Option(
|
|
86
|
+
None,
|
|
87
|
+
"--object",
|
|
88
|
+
"-o",
|
|
89
|
+
help="Filter to one object's fields (matches item_type, case-insensitive).",
|
|
90
|
+
),
|
|
91
|
+
custom_only: bool = typer.Option(
|
|
92
|
+
False,
|
|
93
|
+
"--custom-only",
|
|
94
|
+
help="Show only agency-defined fields (exclude built-ins).",
|
|
95
|
+
),
|
|
96
|
+
) -> None:
|
|
57
97
|
"""List agency custom (dynamic) field definitions."""
|
|
58
|
-
|
|
98
|
+
_emit_custom_fields(ctx, object_filter, custom_only)
|
|
59
99
|
|
|
60
100
|
|
|
61
101
|
@ref_app.command("dynamic-fields", hidden=True)
|
|
62
|
-
def dynamic_fields_alias(
|
|
102
|
+
def dynamic_fields_alias(
|
|
103
|
+
ctx: typer.Context,
|
|
104
|
+
object_filter: Optional[str] = typer.Option(None, "--object", "-o"),
|
|
105
|
+
custom_only: bool = typer.Option(False, "--custom-only"),
|
|
106
|
+
) -> None:
|
|
63
107
|
"""Alias of custom-fields."""
|
|
64
|
-
|
|
108
|
+
_emit_custom_fields(ctx, object_filter, custom_only)
|
|
65
109
|
|
|
66
110
|
|
|
67
111
|
@ref_app.command("hierarchies")
|
|
@@ -21,6 +21,31 @@ def test_activities_list():
|
|
|
21
21
|
assert json.loads(result.stdout)[0]["notes"] == "called"
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
@respx.mock
|
|
25
|
+
def test_activities_list_filters_by_person_and_company():
|
|
26
|
+
route = respx.get("https://app.loxo.co/api/acme/person_events").mock(
|
|
27
|
+
return_value=httpx.Response(200, json={"scroll_id": None, "person_events": []})
|
|
28
|
+
)
|
|
29
|
+
result = runner.invoke(
|
|
30
|
+
app,
|
|
31
|
+
["--json", "activities", "list", "--person-id", "50", "--company-id", "7"],
|
|
32
|
+
env=ENV,
|
|
33
|
+
)
|
|
34
|
+
assert result.exit_code == 0
|
|
35
|
+
assert dict(route.calls.last.request.url.params) == {
|
|
36
|
+
"person_id": "50",
|
|
37
|
+
"company_id": "7",
|
|
38
|
+
"per_page": "50",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_activities_list_has_no_job_id_option():
|
|
43
|
+
# job_id is not a valid person_events query param (Loxo returns 422), so the
|
|
44
|
+
# list command must not advertise a --job-id filter.
|
|
45
|
+
result = runner.invoke(app, ["activities", "list", "--help"], env=ENV)
|
|
46
|
+
assert "--job-id" not in result.stdout
|
|
47
|
+
|
|
48
|
+
|
|
24
49
|
@respx.mock
|
|
25
50
|
def test_activities_add_wraps_person_event():
|
|
26
51
|
captured = {}
|
|
@@ -54,20 +54,102 @@ def test_ref_source_types_still_paginates():
|
|
|
54
54
|
assert route.call_count == 3
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
# A representative slice of GET /dynamic_fields: built-in and agency-defined
|
|
58
|
+
# fields spread across more than one object (item_type).
|
|
59
|
+
DYNAMIC_FIELDS = [
|
|
60
|
+
{
|
|
61
|
+
"id": 1,
|
|
62
|
+
"name": "Account manager",
|
|
63
|
+
"key": "owned_by_id",
|
|
64
|
+
"item_type": "Deal",
|
|
65
|
+
"dynamic_field_type": {"id": 3, "name": "Enumeration"},
|
|
66
|
+
"built_in": True,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": 2,
|
|
70
|
+
"name": "UTM Medium",
|
|
71
|
+
"key": "custom_text_3",
|
|
72
|
+
"item_type": "Deal",
|
|
73
|
+
"dynamic_field_type": {"id": 1, "name": "Text"},
|
|
74
|
+
"built_in": False,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": 3,
|
|
78
|
+
"name": "First Name",
|
|
79
|
+
"key": "first_name",
|
|
80
|
+
"item_type": "Person",
|
|
81
|
+
"dynamic_field_type": {"id": 1, "name": "Text"},
|
|
82
|
+
"built_in": True,
|
|
83
|
+
},
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
|
|
57
87
|
@respx.mock
|
|
58
88
|
def test_ref_custom_fields_single_fetch():
|
|
59
89
|
# dynamic_fields is a flat config list that ignores after_id, so the command
|
|
60
90
|
# fetches it exactly once rather than driving the keyset paginator (which the
|
|
61
91
|
# endpoint would loop forever, causing 429s). A single non-empty response is
|
|
62
|
-
# all the endpoint ever returns.
|
|
92
|
+
# all the endpoint ever returns. With no filter every field comes back, and
|
|
93
|
+
# each row now exposes its `key`.
|
|
63
94
|
route = respx.get("https://app.loxo.co/api/acme/dynamic_fields").mock(
|
|
64
|
-
return_value=httpx.Response(200, json=
|
|
95
|
+
return_value=httpx.Response(200, json=DYNAMIC_FIELDS)
|
|
65
96
|
)
|
|
66
97
|
result = runner.invoke(app, ["--json", "ref", "custom-fields"], env=ENV)
|
|
67
|
-
|
|
98
|
+
keys = [x["key"] for x in json.loads(result.stdout)]
|
|
99
|
+
assert keys == ["owned_by_id", "custom_text_3", "first_name"]
|
|
68
100
|
assert route.call_count == 1
|
|
69
101
|
|
|
70
102
|
|
|
103
|
+
@respx.mock
|
|
104
|
+
def test_ref_custom_fields_filter_by_object():
|
|
105
|
+
# --object matches item_type case-insensitively and returns only that
|
|
106
|
+
# object's fields.
|
|
107
|
+
respx.get("https://app.loxo.co/api/acme/dynamic_fields").mock(
|
|
108
|
+
return_value=httpx.Response(200, json=DYNAMIC_FIELDS)
|
|
109
|
+
)
|
|
110
|
+
result = runner.invoke(app, ["--json", "ref", "custom-fields", "--object", "deal"], env=ENV)
|
|
111
|
+
rows = json.loads(result.stdout)
|
|
112
|
+
assert {x["item_type"] for x in rows} == {"Deal"}
|
|
113
|
+
assert [x["key"] for x in rows] == ["owned_by_id", "custom_text_3"]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@respx.mock
|
|
117
|
+
def test_ref_custom_fields_unknown_object_errors():
|
|
118
|
+
# An item_type that isn't present is a usage error that names the available
|
|
119
|
+
# object types.
|
|
120
|
+
respx.get("https://app.loxo.co/api/acme/dynamic_fields").mock(
|
|
121
|
+
return_value=httpx.Response(200, json=DYNAMIC_FIELDS)
|
|
122
|
+
)
|
|
123
|
+
result = runner.invoke(app, ["ref", "custom-fields", "--object", "bogus"], env=ENV)
|
|
124
|
+
assert result.exit_code == 2
|
|
125
|
+
combined = result.output + (result.stderr if result.stderr_bytes else "")
|
|
126
|
+
assert "Deal" in combined and "Person" in combined
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@respx.mock
|
|
130
|
+
def test_ref_custom_fields_custom_only_drops_builtins():
|
|
131
|
+
# --custom-only keeps only agency-defined (built_in == False) fields.
|
|
132
|
+
respx.get("https://app.loxo.co/api/acme/dynamic_fields").mock(
|
|
133
|
+
return_value=httpx.Response(200, json=DYNAMIC_FIELDS)
|
|
134
|
+
)
|
|
135
|
+
result = runner.invoke(app, ["--json", "ref", "custom-fields", "--custom-only"], env=ENV)
|
|
136
|
+
rows = json.loads(result.stdout)
|
|
137
|
+
assert [x["key"] for x in rows] == ["custom_text_3"]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@respx.mock
|
|
141
|
+
def test_ref_custom_fields_json_carries_key_and_derived_type():
|
|
142
|
+
# JSON output preserves the full record and adds a flat `type` derived from
|
|
143
|
+
# dynamic_field_type.name.
|
|
144
|
+
respx.get("https://app.loxo.co/api/acme/dynamic_fields").mock(
|
|
145
|
+
return_value=httpx.Response(200, json=DYNAMIC_FIELDS)
|
|
146
|
+
)
|
|
147
|
+
result = runner.invoke(app, ["--json", "ref", "custom-fields", "--object", "deal"], env=ENV)
|
|
148
|
+
row = json.loads(result.stdout)[0]
|
|
149
|
+
assert row["key"] == "owned_by_id"
|
|
150
|
+
assert row["type"] == "Enumeration"
|
|
151
|
+
|
|
152
|
+
|
|
71
153
|
@respx.mock
|
|
72
154
|
def test_ref_lists_uses_person_lists_endpoint():
|
|
73
155
|
# `ref lists` maps to the person_lists endpoint (GET /lists 404s). It returns
|
|
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
|
|
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
|