MGost 0.4__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.
Files changed (47) hide show
  1. mgost-0.4/.flake8 +10 -0
  2. mgost-0.4/.github/workflows/coverage.yml +24 -0
  3. mgost-0.4/.github/workflows/flake8.yml +17 -0
  4. mgost-0.4/.github/workflows/tests.yml +23 -0
  5. mgost-0.4/.gitignore +4 -0
  6. mgost-0.4/.pre-commit-config.yaml +16 -0
  7. mgost-0.4/.python-version +1 -0
  8. mgost-0.4/.vscode/launch.json +16 -0
  9. mgost-0.4/.vscode/settings.json +11 -0
  10. mgost-0.4/LICENSE +21 -0
  11. mgost-0.4/PKG-INFO +33 -0
  12. mgost-0.4/README.md +2 -0
  13. mgost-0.4/pyproject.toml +61 -0
  14. mgost-0.4/src/mgost/__init__.py +1 -0
  15. mgost-0.4/src/mgost/__main__.py +4 -0
  16. mgost-0.4/src/mgost/api/__init__.py +2 -0
  17. mgost-0.4/src/mgost/api/actions.py +114 -0
  18. mgost-0.4/src/mgost/api/api.py +272 -0
  19. mgost-0.4/src/mgost/api/caller.py +162 -0
  20. mgost-0.4/src/mgost/api/exceptions.py +29 -0
  21. mgost-0.4/src/mgost/api/request.py +18 -0
  22. mgost-0.4/src/mgost/api/schemas/__init__.py +6 -0
  23. mgost-0.4/src/mgost/api/schemas/general.py +11 -0
  24. mgost-0.4/src/mgost/api/schemas/mgost.py +86 -0
  25. mgost-0.4/src/mgost/cli/__init__.py +4 -0
  26. mgost-0.4/src/mgost/cli/app.py +8 -0
  27. mgost-0.4/src/mgost/cli/async_commands.py +93 -0
  28. mgost-0.4/src/mgost/cli/callback.py +44 -0
  29. mgost-0.4/src/mgost/cli/commands.py +76 -0
  30. mgost-0.4/src/mgost/console.py +160 -0
  31. mgost-0.4/src/mgost/mgost/__init__.py +1 -0
  32. mgost-0.4/src/mgost/mgost/mgost.py +259 -0
  33. mgost-0.4/src/mgost/mgost/progress_utils.py +35 -0
  34. mgost-0.4/src/mgost/mgost/sync.py +241 -0
  35. mgost-0.4/src/mgost/mgost/utils.py +61 -0
  36. mgost-0.4/src/mgost/settings/__init__.py +1 -0
  37. mgost-0.4/src/mgost/settings/logger.py +34 -0
  38. mgost-0.4/src/mgost/settings/settings.py +194 -0
  39. mgost-0.4/temp/.gitignore +1 -0
  40. mgost-0.4/temp/.mgost/.env +1 -0
  41. mgost-0.4/temp/.mgost/settings.json +6 -0
  42. mgost-0.4/temp/Mun Ku.png +0 -0
  43. mgost-0.4/temp/logs/latest.log +0 -0
  44. mgost-0.4/temp/main.md +5 -0
  45. mgost-0.4/temp/output.docx +0 -0
  46. mgost-0.4/tests/__init__.py +0 -0
  47. mgost-0.4/tests/test_settings.py +6 -0
mgost-0.4/.flake8 ADDED
@@ -0,0 +1,10 @@
1
+ [flake8]
2
+ ignore =
3
+ B008
4
+ exclude =
5
+ .venv
6
+ per-file-ignores =
7
+ # F401: imported but unused
8
+ # Sometimes imports in __init__
9
+ # done to make shortcuts
10
+ __init__.py: F401, F403
@@ -0,0 +1,24 @@
1
+ name: Coverage
2
+
3
+ on: [push]
4
+
5
+
6
+ jobs:
7
+ build:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v3
11
+ - name: Install uv
12
+ uses: astral-sh/setup-uv@v5
13
+ with:
14
+ enable-cache: true
15
+ - name: Installing project with coverage
16
+ run: uv sync --group tests
17
+ - name: Testing code with coverage
18
+ run: uv run -m coverage run -m unittest
19
+ - name: Collecting coverage
20
+ run: uv run -m coverage json
21
+ - uses: actions/upload-artifact@v4
22
+ with:
23
+ name: coverage
24
+ path: coverage.json
@@ -0,0 +1,17 @@
1
+ name: Flake8
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v3
10
+ - name: Install uv
11
+ uses: astral-sh/setup-uv@v5
12
+ with:
13
+ enable-cache: true
14
+ - name: Sync project
15
+ run: uv sync --group tests
16
+ - name: Testing code flake8
17
+ run: uv run -m flake8
@@ -0,0 +1,23 @@
1
+ name: Tests
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+ strategy:
8
+ matrix:
9
+ python-version: ["3.12", "3.13"]
10
+ os: [ubuntu-latest]
11
+ fail-fast: false
12
+ runs-on: ${{ matrix.os }}
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ enable-cache: true
20
+ - name: Sync project
21
+ run: uv sync --group tests
22
+ - name: Testing code with django test framework
23
+ run: uv run -m unittest
mgost-0.4/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ uv.lock
2
+ **/__pycache__
3
+ dist
4
+ .coverage
@@ -0,0 +1,16 @@
1
+ ci:
2
+ autoupdate_schedule: monthly
3
+ repos:
4
+ - repo: https://github.com/PyCQA/flake8
5
+ rev: 7.0.0
6
+ hooks:
7
+ - id: flake8
8
+ additional_dependencies:
9
+ - flake8-bugbear
10
+ types: [python]
11
+
12
+ - repo: https://github.com/pycqa/isort
13
+ rev: 5.13.2
14
+ hooks:
15
+ - id: isort
16
+ types: [python]
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,16 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Python Debugger: Module",
9
+ "type": "debugpy",
10
+ "request": "launch",
11
+ "module": "mgost",
12
+ "args": ["sync"],
13
+ "cwd": "${workspaceFolder}/test"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "files.exclude": {
3
+ "**/__pycache__": true,
4
+ ".venv": true,
5
+ "uv.lock": true,
6
+ },
7
+ "cSpell.words": [
8
+ "ARTICHAAPI",
9
+ "рендера"
10
+ ]
11
+ }
mgost-0.4/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ArtichaSite
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
mgost-0.4/PKG-INFO ADDED
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: MGost
3
+ Version: 0.4
4
+ Summary: MGost converter console app based on ArtichaAPI
5
+ Project-URL: homepage, https://articha.ru/mgost/
6
+ Project-URL: repository, https://github.com/ArtichaTM/MGost
7
+ Author-email: Геворкян Артём <tima-1324@mail.ru>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ArtichaSite,api
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Intended Audience :: Information Technology
15
+ Classifier: Natural Language :: English
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Requires-Python: >=3.13
23
+ Requires-Dist: aiofiles>=25.1.0
24
+ Requires-Dist: aiopath>=0.7.7
25
+ Requires-Dist: dotenv>=0.9.9
26
+ Requires-Dist: httpx>=0.28.1
27
+ Requires-Dist: pre-commit>=4.2.0
28
+ Requires-Dist: pydantic>=2.11.7
29
+ Requires-Dist: typer>=0.16.1
30
+ Description-Content-Type: text/markdown
31
+
32
+ # MGost
33
+ Библиотека позволяет пользоваться преимуществами MGost конвертера локально
mgost-0.4/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # MGost
2
+ Библиотека позволяет пользоваться преимуществами MGost конвертера локально
@@ -0,0 +1,61 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [tool.hatch.build.targets.wheel]
6
+ packages = [
7
+ "src/mgost"
8
+ ]
9
+
10
+ [project]
11
+ name = "MGost"
12
+ version = "0.4"
13
+ authors = [
14
+ {name="Геворкян Артём", email="tima-1324@mail.ru"}
15
+ ]
16
+ description = "MGost converter console app based on ArtichaAPI"
17
+ readme = "README.md"
18
+ license = "MIT"
19
+ license-files = ["LICENSE"]
20
+ keywords = ["ArtichaSite", "api"]
21
+ requires-python = ">=3.13"
22
+ dependencies = [
23
+ "aiofiles>=25.1.0",
24
+ "aiopath>=0.7.7",
25
+ "dotenv>=0.9.9",
26
+ "httpx>=0.28.1",
27
+ "pre-commit>=4.2.0",
28
+ "pydantic>=2.11.7",
29
+ "typer>=0.16.1",
30
+ ]
31
+
32
+
33
+ classifiers = [
34
+ "Development Status :: 4 - Beta"
35
+ , "Operating System :: POSIX :: Linux"
36
+ , "Operating System :: Microsoft :: Windows"
37
+ , "Environment :: Console"
38
+ , "Programming Language :: Python"
39
+ , "Programming Language :: Python :: 3"
40
+ , "Programming Language :: Python :: 3 :: Only"
41
+ , "Programming Language :: Python :: 3.13"
42
+ , "Natural Language :: English"
43
+ , "Intended Audience :: Education"
44
+ , "Intended Audience :: Information Technology"
45
+ ]
46
+
47
+ [project.urls]
48
+ homepage = "https://articha.ru/mgost/"
49
+ repository = "https://github.com/ArtichaTM/MGost"
50
+
51
+ [project.scripts]
52
+ mgost = "mgost:main"
53
+
54
+ [dependency-groups]
55
+ tests = [
56
+ "coverage>=7.11.0",
57
+ "flake8>=7.3.0",
58
+ ]
59
+
60
+ [tool.isort]
61
+ multi_line_output = 5
@@ -0,0 +1 @@
1
+ from .cli import app as main
@@ -0,0 +1,4 @@
1
+ from .cli import app as main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,2 @@
1
+ from .api import ArtichaAPI
2
+ from .exceptions import APIRequestError
@@ -0,0 +1,114 @@
1
+ from abc import ABC, abstractmethod
2
+ from dataclasses import dataclass
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING
5
+
6
+ from rich.progress import Progress
7
+
8
+ if TYPE_CHECKING:
9
+ from mgost.mgost import MGost
10
+
11
+ from .api import ArtichaAPI
12
+
13
+
14
+ @dataclass(frozen=True, slots=True)
15
+ class Action(ABC):
16
+ pass
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class MGostCompletableAction(Action, ABC):
21
+ @abstractmethod
22
+ async def complete_mgost(
23
+ self, mgost: 'MGost',
24
+ progress: Progress | None = None
25
+ ) -> Action | None:
26
+ raise NotImplementedError()
27
+
28
+
29
+ @dataclass(frozen=True, slots=True)
30
+ class APICompletableAction(MGostCompletableAction, ABC):
31
+ async def complete_mgost(
32
+ self, mgost, progress=None
33
+ ) -> Action | None:
34
+ return await self.complete_api(mgost.api, progress)
35
+
36
+ @abstractmethod
37
+ async def complete_api(
38
+ self,
39
+ api: 'ArtichaAPI',
40
+ progress: Progress | None = None
41
+ ) -> Action | None:
42
+ raise NotImplementedError()
43
+
44
+
45
+ @dataclass(frozen=True, slots=True)
46
+ class PathAction(Action, ABC):
47
+ project_id: int
48
+ path: Path
49
+
50
+
51
+ @dataclass(frozen=True, slots=True)
52
+ class MoveAction(PathAction, ABC):
53
+ new_path: Path
54
+
55
+
56
+ @dataclass(frozen=True, slots=True)
57
+ class DoNothing(APICompletableAction):
58
+ async def complete_api(self, api, progress=None):
59
+ from asyncio import sleep
60
+ from random import random
61
+ await sleep(random())
62
+
63
+
64
+ @dataclass(frozen=True, slots=True)
65
+ class UploadFileAction(APICompletableAction, PathAction):
66
+ overwrite: bool
67
+
68
+ async def complete_api(self, api, progress=None):
69
+ await api.upload(
70
+ project_id=self.project_id,
71
+ path=self.path,
72
+ overwrite=self.overwrite,
73
+ progress=progress
74
+ )
75
+
76
+
77
+ @dataclass(frozen=True, slots=True)
78
+ class DownloadFileAction(APICompletableAction, PathAction):
79
+ overwrite_ok: bool
80
+
81
+ async def complete_api(self, api, progress=None):
82
+ await api.download(
83
+ project_id=self.project_id,
84
+ path=self.path,
85
+ overwrite_ok=self.overwrite_ok,
86
+ progress=progress
87
+ )
88
+
89
+
90
+ @dataclass(frozen=True, slots=True)
91
+ class FileMovedLocally(APICompletableAction, MoveAction):
92
+ async def complete_api(self, api, progress=None):
93
+ project_files = await api.project_files(
94
+ self.project_id
95
+ )
96
+ if self.path in project_files:
97
+ await api.move_on_cloud(
98
+ project_id=self.project_id,
99
+ old_path=self.path,
100
+ new_path=self.new_path
101
+ )
102
+ else:
103
+ await api.upload(
104
+ self.project_id,
105
+ path=self.new_path,
106
+ overwrite=False,
107
+ progress=progress
108
+ )
109
+
110
+
111
+ @dataclass(frozen=True, slots=True)
112
+ class FileSync(MGostCompletableAction, PathAction):
113
+ async def complete_mgost(self, mgost, progress=None) -> Action:
114
+ return await mgost.sync_file(self.project_id, self.path)
@@ -0,0 +1,272 @@
1
+ from datetime import datetime
2
+ from os import utime
3
+ from pathlib import Path
4
+ from typing import Awaitable, Literal
5
+
6
+ from aiopath import AsyncPath
7
+ from httpx import (
8
+ AsyncClient, ConnectError, HTTPStatusError, QueryParams, Response
9
+ )
10
+ from httpx._types import RequestFiles
11
+ from rich.progress import Progress
12
+
13
+ from . import schemas
14
+ from .caller import api_request
15
+ from .exceptions import ClientClosed
16
+ from .request import APIRequestInfo
17
+
18
+ CURRENT_TIMEZONE = datetime.now().astimezone().tzinfo
19
+
20
+
21
+ class ArtichaAPI:
22
+ __slots__ = (
23
+ '_token',
24
+ '_client',
25
+ '_cache',
26
+ '_base_url',
27
+ )
28
+ _host: str = 'https://articha.ru/api'
29
+ _base_url: str
30
+ _token: str
31
+ _client: AsyncClient | None
32
+ _cache: dict[tuple[
33
+ str,
34
+ str,
35
+ QueryParams | dict | None,
36
+ RequestFiles | dict | None
37
+ ], Response]
38
+
39
+ def __init__(
40
+ self,
41
+ api_token: str,
42
+ /,
43
+ base_url: str | None = None
44
+ ) -> None:
45
+ assert base_url is None or isinstance(base_url, str)
46
+ assert isinstance(api_token, str)
47
+ if base_url is None:
48
+ base_url = self._host
49
+ assert base_url is not None
50
+ self._base_url = base_url
51
+ self._token = api_token
52
+ self._cache = dict()
53
+
54
+ async def __aenter__[T: ArtichaAPI](self: T) -> T:
55
+ assert self._base_url is not None
56
+ self._client = AsyncClient(
57
+ headers={
58
+ 'X-API-Key': self._token
59
+ },
60
+ base_url=self._base_url
61
+ )
62
+ await self._client.__aenter__()
63
+ return self
64
+
65
+ async def __aexit__(self, *args) -> None:
66
+ assert isinstance(self._client, AsyncClient)
67
+ if self._client is None:
68
+ raise ClientClosed(f"{self.__qualname__} is closed")
69
+ await self._client.__aexit__()
70
+ self._client = None
71
+
72
+ def method(
73
+ self, request: APIRequestInfo
74
+ ) -> Awaitable[Response]:
75
+ assert self._client is not None
76
+ return api_request(
77
+ client=self._client,
78
+ cache=self._cache,
79
+ request=request
80
+ )
81
+
82
+ def _invalidate_cache(self) -> None:
83
+ self._cache.clear()
84
+
85
+ async def validate_token(self) -> str | schemas.TokenInfo:
86
+ try:
87
+ resp = await self.method(APIRequestInfo(
88
+ 'GET', '/me'
89
+ ))
90
+ return schemas.TokenInfo(**resp.json())
91
+ except HTTPStatusError as e:
92
+ resp = e.response
93
+ info = resp.json()
94
+ assert 'detail' in info
95
+ return info['detail']
96
+ except ConnectError:
97
+ return "Ошибка подключения: сайт недоступен"
98
+
99
+ async def me(self) -> schemas.TokenInfo:
100
+ return schemas.TokenInfo(**(await self.method(APIRequestInfo(
101
+ 'GET', '/me'
102
+ ))).json())
103
+
104
+ async def trust(self) -> int:
105
+ return (await self.method(APIRequestInfo(
106
+ 'GET', '/trust'
107
+ ))).json()['trust']
108
+
109
+ async def trust_factors(self) -> dict[str, int]:
110
+ return (await self.method(APIRequestInfo(
111
+ 'GET', '/trust/factors'
112
+ ))).json()
113
+
114
+ async def download_example(
115
+ self,
116
+ name: str = 'init',
117
+ type: Literal['md', 'docx'] = 'md'
118
+ ) -> bytes:
119
+ resp = await self.method(APIRequestInfo(
120
+ 'GET', '/mgost/examples',
121
+ {
122
+ 'name': name,
123
+ 'type': type
124
+ }
125
+ ))
126
+ return resp.read()
127
+
128
+ async def is_project_available(self, project_id: int) -> bool:
129
+ assert isinstance(project_id, int)
130
+ try:
131
+ response = await self.method(APIRequestInfo(
132
+ 'GET', f'/mgost/project/{project_id}'
133
+ ))
134
+ return response.status_code == 200
135
+ except HTTPStatusError:
136
+ return False
137
+
138
+ async def projects(self) -> list[schemas.Project]:
139
+ resp = await self.method(APIRequestInfo(
140
+ 'GET', '/mgost/project'
141
+ ))
142
+ return [
143
+ schemas.Project(**i) for i in resp.json()
144
+ ]
145
+
146
+ async def project(self, project_id: int) -> schemas.ProjectExtended:
147
+ assert isinstance(project_id, int)
148
+ assert await self.is_project_available(project_id)
149
+ resp = await self.method(APIRequestInfo(
150
+ 'GET', f'/mgost/project/{project_id}'
151
+ ))
152
+ return schemas.ProjectExtended(
153
+ **resp.json(),
154
+ )
155
+
156
+ async def project_requirements(
157
+ self, project_id
158
+ ) -> dict[str, schemas.FileRequirement]:
159
+ resp = await self.method(APIRequestInfo(
160
+ 'GET', f'/mgost/project/{project_id}/requirements'
161
+ ))
162
+ return {
163
+ k: schemas.FileRequirement(
164
+ **v
165
+ ) for k, v in resp.json().items()
166
+ }
167
+
168
+ async def project_files(
169
+ self, project_id: int
170
+ ) -> dict[Path, schemas.ProjectFile]:
171
+ assert isinstance(project_id, int)
172
+ resp = await self.method(APIRequestInfo(
173
+ 'GET', f'/mgost/project/{project_id}/files'
174
+ ))
175
+ return {
176
+ Path(i['path']): schemas.ProjectFile(**i) for i in resp.json()
177
+ }
178
+
179
+ async def create_project(self, name: str) -> int:
180
+ assert isinstance(name, str)
181
+ resp = await self.method(APIRequestInfo(
182
+ 'GET', '/mgost/project',
183
+ {'project_name': name}
184
+ ))
185
+ self._invalidate_cache()
186
+ return resp.json()['id']
187
+
188
+ async def upload(
189
+ self,
190
+ project_id: int,
191
+ path: Path,
192
+ overwrite: bool,
193
+ progress: Progress | None = None
194
+ ) -> None:
195
+ assert isinstance(project_id, int)
196
+ assert isinstance(path, Path)
197
+ assert isinstance(overwrite, bool)
198
+ if not (path.exists() and path.is_file()):
199
+ raise FileNotFoundError
200
+ params: dict = {
201
+ 'project_id': project_id,
202
+ 'modify_time': datetime.fromtimestamp(
203
+ path.lstat().st_mtime, CURRENT_TIMEZONE
204
+ )
205
+ }
206
+ if overwrite:
207
+ await self.method(APIRequestInfo(
208
+ 'POST',
209
+ f'/mgost/project/{project_id}/files/{path}',
210
+ params=params,
211
+ request_file_path=AsyncPath(path),
212
+ progress=progress
213
+ ))
214
+ else:
215
+ params['path'] = path
216
+ await self.method(APIRequestInfo(
217
+ 'PUT',
218
+ f'/mgost/project/{project_id}/files/{path}',
219
+ params=params,
220
+ request_file_path=AsyncPath(path),
221
+ progress=progress
222
+ ))
223
+ self._invalidate_cache()
224
+
225
+ async def download(
226
+ self,
227
+ project_id: int,
228
+ path: Path,
229
+ overwrite_ok: bool = True,
230
+ progress: Progress | None = None
231
+ ) -> None:
232
+ assert isinstance(project_id, int)
233
+ assert isinstance(path, Path)
234
+ assert isinstance(overwrite_ok, bool)
235
+ resp = await self.method(APIRequestInfo(
236
+ 'GET', f'/mgost/project/{project_id}/files/{path}',
237
+ response_file_path=AsyncPath(path),
238
+ progress=progress
239
+ ))
240
+ resp.raise_for_status()
241
+ access_time = path.lstat().st_atime
242
+ project_files = await self.project_files(project_id)
243
+ project_file = project_files[path]
244
+ utime(path, (access_time, project_file.modified.timestamp()))
245
+
246
+ async def move_on_cloud(
247
+ self,
248
+ project_id: int,
249
+ old_path: Path,
250
+ new_path: Path
251
+ ) -> bool:
252
+ resp = await self.method(APIRequestInfo(
253
+ 'PATCH',
254
+ f'/mgost/project/{project_id}/files/{old_path}',
255
+ {'target': new_path}
256
+ ))
257
+ self._invalidate_cache()
258
+ return schemas.Message(**resp.json()).is_ok()
259
+
260
+ async def render(
261
+ self,
262
+ project_id: int
263
+ ) -> schemas.mgost.BuildResult:
264
+ """Requests api to render project
265
+ :raises HTTPStatusError: Raised when got non-success code from the api
266
+ """
267
+ resp = await self.method(APIRequestInfo(
268
+ 'GET', f'/mgost/project/{project_id}/render'
269
+ ))
270
+ resp.raise_for_status()
271
+ self._invalidate_cache()
272
+ return schemas.mgost.BuildResult(**resp.json())