lkr-dev-cli 0.0.39__tar.gz → 0.0.41__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.
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/PKG-INFO +1 -1
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/codemode.md +10 -1
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/codemode/main.py +44 -10
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/extended_sdk_methods/classes.py +12 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/extended_sdk_methods/main.py +35 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/pyproject.toml +1 -1
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/tests/test_codemode.py +58 -1
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/tests/test_extended_sdk_methods.py +55 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/.github/workflows/ci.yml +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/.github/workflows/release.yml +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/.gitignore +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/.python-version +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/.vscode/launch.json +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/.vscode/settings.json +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/Dockerfile +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/LICENSE +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/Makefile +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/README.md +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/cloudbuild.yaml +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/__init__.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/auth/__init__.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/auth/main.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/auth/oauth.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/auth_service.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/classes.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/codemode/LOCAL.md +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/codemode/__init__.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/codemode/constant.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/codemode/download_swagger.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/codemode/examples.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/codemode/help.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/codemode/readme.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/codemode/swagger.json +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/codemode/type.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/constants.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/custom_types.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/exceptions.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/extended_sdk_methods/__init__.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/logger.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/main.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/mcp/classes.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/mcp/main.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/mcp/utils.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/observability/classes.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/observability/embed_container.html +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/observability/main.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/observability/utils.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/tools/classes.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/tools/main.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr/tools/permission_deprecation.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/lkr.md +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/tests/TESTING.md +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/tests/test_dependency_resolution.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/tests/test_deps.sh +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/tests/test_oauth_account.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/tests/test_permission_deprecation.py +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/ty.toml +0 -0
- {lkr_dev_cli-0.0.39 → lkr_dev_cli-0.0.41}/uv.lock +0 -0
|
@@ -47,5 +47,14 @@ To check things out on a web panel:
|
|
|
47
47
|
npx @modelcontextprotocol/inspector uvx -q --from lkr-dev-cli[codemode] lkr code-mode run
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
## Architectural & Troubleshooting Notes
|
|
51
|
+
|
|
52
|
+
### The stdio Stream Corruption Trap
|
|
53
|
+
When running an MCP server over standard input/output (`stdio`) transport, JSON-RPC packets are transmitted directly through the process's `stdout`.
|
|
54
|
+
|
|
55
|
+
If Python code executed within the server (such as tool execution or third-party libraries) writes raw text directly to `sys.stdout` via standard `print()` calls, that raw text corrupts the JSON-RPC response stream. This causes fatal JSON parsing errors on the client (e.g., `invalid character 'C' looking for beginning of value`) and immediately closes the connection.
|
|
56
|
+
|
|
57
|
+
To safeguard against this:
|
|
58
|
+
1. `run_python_code` automatically wraps all Monty execution in a `redirect_stdout` block.
|
|
59
|
+
2. If any `print()` output is captured, it is safely packaged and returned as part of a valid JSON structure rather than polluting raw `stdout`.
|
|
51
60
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import inspect
|
|
2
|
-
import io
|
|
3
2
|
import json
|
|
4
|
-
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import tempfile
|
|
6
|
+
from contextlib import contextmanager
|
|
5
7
|
|
|
6
8
|
import typer
|
|
7
9
|
import pydantic_monty
|
|
@@ -21,8 +23,38 @@ __all__ = ["group"]
|
|
|
21
23
|
mcp = FastMCP("lkr:codemode")
|
|
22
24
|
group = typer.Typer()
|
|
23
25
|
ctx_lkr: LkrCtxObj | None = None
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
class OSCapture:
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.output = ""
|
|
29
|
+
|
|
30
|
+
@contextmanager
|
|
31
|
+
def capture_os_stdout():
|
|
32
|
+
cap = OSCapture()
|
|
33
|
+
fd, temp_path = tempfile.mkstemp()
|
|
34
|
+
|
|
35
|
+
if sys.stdout and hasattr(sys.stdout, "flush"):
|
|
36
|
+
sys.stdout.flush()
|
|
37
|
+
|
|
38
|
+
original_stdout = sys.stdout
|
|
39
|
+
original_stdout_fd = os.dup(1)
|
|
40
|
+
try:
|
|
41
|
+
os.dup2(fd, 1)
|
|
42
|
+
with os.fdopen(os.dup(1), "w") as f:
|
|
43
|
+
sys.stdout = f
|
|
44
|
+
yield cap
|
|
45
|
+
f.flush()
|
|
46
|
+
finally:
|
|
47
|
+
sys.stdout = original_stdout
|
|
48
|
+
os.dup2(original_stdout_fd, 1)
|
|
49
|
+
os.close(original_stdout_fd)
|
|
50
|
+
os.close(fd)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
with open(temp_path, "r") as f:
|
|
54
|
+
cap.output = f.read()
|
|
55
|
+
finally:
|
|
56
|
+
if os.path.exists(temp_path):
|
|
57
|
+
os.remove(temp_path)
|
|
26
58
|
|
|
27
59
|
def get_mcp_sdk(ctx: LkrCtxObj):
|
|
28
60
|
sdk = get_auth(ctx).get_current_sdk(prompt_refresh_invalid_token=True)
|
|
@@ -126,13 +158,12 @@ def run_python_code(code: str, dev_mode: bool = False) -> str:
|
|
|
126
158
|
|
|
127
159
|
m = pydantic_monty.Monty(code)
|
|
128
160
|
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
with redirect_stdout(f):
|
|
161
|
+
# Use low-level OS stdout capture to ensure any print() statements
|
|
162
|
+
# in Rust or sub-interpreters don't corrupt the JSON-RPC stream
|
|
163
|
+
with capture_os_stdout() as cap:
|
|
133
164
|
result = m.run(external_functions=external_funcs)
|
|
134
165
|
|
|
135
|
-
printed_output =
|
|
166
|
+
printed_output = cap.output
|
|
136
167
|
|
|
137
168
|
# m.run() returns the evaluated result of the last expression (which is already a primitive)
|
|
138
169
|
try:
|
|
@@ -148,7 +179,10 @@ def run_python_code(code: str, dev_mode: bool = False) -> str:
|
|
|
148
179
|
output = repr(result)
|
|
149
180
|
|
|
150
181
|
if printed_output:
|
|
151
|
-
return
|
|
182
|
+
return json.dumps({
|
|
183
|
+
"stdout": printed_output,
|
|
184
|
+
"result": result
|
|
185
|
+
}, indent=2, default=str)
|
|
152
186
|
return output
|
|
153
187
|
except Exception as e:
|
|
154
188
|
logger.error(f"Error executing Monty: {e}")
|
|
@@ -13,6 +13,7 @@ __all__ = [
|
|
|
13
13
|
"GenerateLookMLParameters",
|
|
14
14
|
"SelectedTable",
|
|
15
15
|
"GenerateLookMLWithNewFilesResponse",
|
|
16
|
+
"ProjectCommitRequest",
|
|
16
17
|
]
|
|
17
18
|
|
|
18
19
|
|
|
@@ -84,3 +85,14 @@ class GenerateLookMLWithNewFilesResponse(BaseModel):
|
|
|
84
85
|
|
|
85
86
|
generate_lookml: str = Field(..., description="The API response from generating LookML, typically an empty string on success.")
|
|
86
87
|
new_files: list[Any] = Field(..., description="List of newly created files.")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ProjectCommitRequest(BaseModel):
|
|
91
|
+
"""Request body for LookML project commit."""
|
|
92
|
+
|
|
93
|
+
files: Optional[list[str]] = Field(
|
|
94
|
+
None,
|
|
95
|
+
description="List of files to commit. If omitted or passed as None, Looker automatically stages and commits all modified, newly added, and deleted LookML files in the project's current development workspace.",
|
|
96
|
+
)
|
|
97
|
+
message: Optional[str] = Field(None, description="Commit message")
|
|
98
|
+
amend: Optional[bool] = Field(None, description="Amend the last commit")
|
|
@@ -13,6 +13,7 @@ from lkr.extended_sdk_methods.classes import (
|
|
|
13
13
|
ProjectGeneratorColumn,
|
|
14
14
|
ProjectGeneratorTable,
|
|
15
15
|
SelectedTable,
|
|
16
|
+
ProjectCommitRequest,
|
|
16
17
|
)
|
|
17
18
|
|
|
18
19
|
__all__ = [
|
|
@@ -26,6 +27,7 @@ __all__ = [
|
|
|
26
27
|
"GenerateLookMLParameters",
|
|
27
28
|
"SelectedTable",
|
|
28
29
|
"GenerateLookMLWithNewFilesResponse",
|
|
30
|
+
"ProjectCommitRequest",
|
|
29
31
|
]
|
|
30
32
|
|
|
31
33
|
|
|
@@ -310,3 +312,36 @@ class ExtendedLooker40SDK(Looker40SDK):
|
|
|
310
312
|
return GenerateLookMLWithNewFilesResponse(
|
|
311
313
|
generate_lookml=response, new_files=new_files
|
|
312
314
|
)
|
|
315
|
+
|
|
316
|
+
def commit(
|
|
317
|
+
self,
|
|
318
|
+
project_id: str,
|
|
319
|
+
body: Optional[Union[ProjectCommitRequest, dict, Any]] = None,
|
|
320
|
+
transport_options: Optional[transport.TransportOptions] = None,
|
|
321
|
+
) -> str:
|
|
322
|
+
"""Commit changes to the project's git repository.
|
|
323
|
+
|
|
324
|
+
If you do not specify an array of files in the body (or pass None),
|
|
325
|
+
Looker automatically stages and commits all modified, newly added,
|
|
326
|
+
and deleted LookML files in the project's current development workspace.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
project_id: Id of project.
|
|
330
|
+
body: Optional commit parameters.
|
|
331
|
+
transport_options: Optional transport options.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
str: API response.
|
|
335
|
+
"""
|
|
336
|
+
project_id = self.encode_path_param(project_id)
|
|
337
|
+
path = f"/projects/{project_id}/commit"
|
|
338
|
+
request_body = self._prepare_body(body)
|
|
339
|
+
return cast(
|
|
340
|
+
str,
|
|
341
|
+
self.post(
|
|
342
|
+
path=path,
|
|
343
|
+
structure=str,
|
|
344
|
+
body=request_body,
|
|
345
|
+
transport_options=transport_options,
|
|
346
|
+
),
|
|
347
|
+
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from typing import Any, cast
|
|
2
|
+
import json
|
|
2
3
|
import pytest
|
|
3
4
|
from unittest.mock import patch, MagicMock
|
|
4
5
|
from looker_sdk.rtl.auth_session import AuthSession
|
|
@@ -198,7 +199,7 @@ return "\\n".join(res)
|
|
|
198
199
|
|
|
199
200
|
def test_extended_sdk_methods_present():
|
|
200
201
|
code = """
|
|
201
|
-
methods = ['all_project_files', 'get_file_content', 'create_file', 'update_file', 'delete_file', 'create_project_directory', 'delete_project_directory', 'generate_lookml', 'generate_lookml_with_new_files']
|
|
202
|
+
methods = ['all_project_files', 'get_file_content', 'create_file', 'update_file', 'delete_file', 'create_project_directory', 'delete_project_directory', 'generate_lookml', 'generate_lookml_with_new_files', 'commit']
|
|
202
203
|
for m in methods:
|
|
203
204
|
if m not in dir():
|
|
204
205
|
return "Missing " + m
|
|
@@ -225,4 +226,60 @@ return lookup('ProjectGeneratorTable')
|
|
|
225
226
|
assert "primary_key: string" in result_lookup
|
|
226
227
|
|
|
227
228
|
|
|
229
|
+
def test_commit_help_and_lookup():
|
|
230
|
+
code_help = """
|
|
231
|
+
return help('commit')
|
|
232
|
+
"""
|
|
233
|
+
result = run_python_code(code_help)
|
|
234
|
+
assert "Function: commit" in result or "Type: ProjectCommitRequest" in result
|
|
235
|
+
|
|
236
|
+
code_lookup = """
|
|
237
|
+
return lookup('commit')
|
|
238
|
+
"""
|
|
239
|
+
result_lookup = run_python_code(code_lookup)
|
|
240
|
+
assert "Function: commit" in result_lookup
|
|
241
|
+
assert "Looker automatically stages and commits all modified" in result_lookup
|
|
242
|
+
|
|
243
|
+
code_lookup_model = """
|
|
244
|
+
return lookup('ProjectCommitRequest')
|
|
245
|
+
"""
|
|
246
|
+
result_model = run_python_code(code_lookup_model)
|
|
247
|
+
assert "Type: ProjectCommitRequest" in result_model
|
|
248
|
+
assert "Looker automatically stages and commits" in result_model
|
|
249
|
+
|
|
228
250
|
|
|
251
|
+
def test_captured_print_json():
|
|
252
|
+
code = """
|
|
253
|
+
print("Hello from stdout")
|
|
254
|
+
return {"status": "success"}
|
|
255
|
+
"""
|
|
256
|
+
result_raw = run_python_code(code)
|
|
257
|
+
data = json.loads(result_raw)
|
|
258
|
+
assert data["stdout"] == "Hello from stdout\n"
|
|
259
|
+
assert data["result"] == {"status": "success"}
|
|
260
|
+
|
|
261
|
+
def test_captured_print_json_many():
|
|
262
|
+
code = """
|
|
263
|
+
for i in range(10):
|
|
264
|
+
print(i)
|
|
265
|
+
return {"status": "success"}
|
|
266
|
+
"""
|
|
267
|
+
result_raw = run_python_code(code)
|
|
268
|
+
data = json.loads(result_raw)
|
|
269
|
+
assert data["stdout"] == "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n"
|
|
270
|
+
assert data["result"] == {"status": "success"}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_parent_runtime_not_polluted(capfd):
|
|
274
|
+
code = """
|
|
275
|
+
print("This should be trapped and not leak to parent stdout")
|
|
276
|
+
print("Also checking second print")
|
|
277
|
+
return {"status": "clean"}
|
|
278
|
+
"""
|
|
279
|
+
result_raw = run_python_code(code)
|
|
280
|
+
data = json.loads(result_raw)
|
|
281
|
+
assert "This should be trapped" in data["stdout"]
|
|
282
|
+
assert "Also checking" in data["stdout"]
|
|
283
|
+
|
|
284
|
+
out, err = capfd.readouterr()
|
|
285
|
+
assert out == ""
|
|
@@ -8,6 +8,7 @@ from lkr.extended_sdk_methods.main import (
|
|
|
8
8
|
ProjectGeneratorColumn,
|
|
9
9
|
ProjectGeneratorTable,
|
|
10
10
|
SelectedTable,
|
|
11
|
+
ProjectCommitRequest,
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
|
|
@@ -186,3 +187,57 @@ def test_generate_lookml_with_new_files():
|
|
|
186
187
|
assert isinstance(result, GenerateLookMLWithNewFilesResponse)
|
|
187
188
|
assert result.generate_lookml == "generated"
|
|
188
189
|
assert result.new_files == [FakeFile("c")]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_project_commit_request_model():
|
|
193
|
+
req = ProjectCommitRequest(files=["a.lkml"], message="fix bug", amend=False)
|
|
194
|
+
assert req.files == ["a.lkml"]
|
|
195
|
+
assert req.message == "fix bug"
|
|
196
|
+
assert req.amend is False
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_commit():
|
|
200
|
+
mock_auth = MagicMock()
|
|
201
|
+
mock_auth.settings.base_url = "https://example.looker.com"
|
|
202
|
+
sdk = ExtendedLooker40SDK(
|
|
203
|
+
auth=mock_auth,
|
|
204
|
+
deserialize=MagicMock(),
|
|
205
|
+
serialize=MagicMock(),
|
|
206
|
+
transport=MagicMock(),
|
|
207
|
+
api_version="4.0",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
with patch.object(sdk, "post") as mock_post:
|
|
211
|
+
mock_post.return_value = "commit hash or project"
|
|
212
|
+
|
|
213
|
+
req = ProjectCommitRequest(files=["model.lkml"], message="initial commit")
|
|
214
|
+
res = sdk.commit(project_id="test_proj", body=req)
|
|
215
|
+
|
|
216
|
+
assert res == "commit hash or project"
|
|
217
|
+
mock_post.assert_called_once()
|
|
218
|
+
_, kwargs = mock_post.call_args
|
|
219
|
+
assert kwargs["path"] == "/projects/test_proj/commit"
|
|
220
|
+
assert kwargs["body"] == req.model_dump()
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_commit_no_body():
|
|
224
|
+
mock_auth = MagicMock()
|
|
225
|
+
mock_auth.settings.base_url = "https://example.looker.com"
|
|
226
|
+
sdk = ExtendedLooker40SDK(
|
|
227
|
+
auth=mock_auth,
|
|
228
|
+
deserialize=MagicMock(),
|
|
229
|
+
serialize=MagicMock(),
|
|
230
|
+
transport=MagicMock(),
|
|
231
|
+
api_version="4.0",
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
with patch.object(sdk, "post") as mock_post:
|
|
235
|
+
mock_post.return_value = "commit success"
|
|
236
|
+
|
|
237
|
+
res = sdk.commit(project_id="test_proj")
|
|
238
|
+
|
|
239
|
+
assert res == "commit success"
|
|
240
|
+
mock_post.assert_called_once()
|
|
241
|
+
_, kwargs = mock_post.call_args
|
|
242
|
+
assert kwargs["path"] == "/projects/test_proj/commit"
|
|
243
|
+
assert kwargs["body"] is None
|
|
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
|