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.
- mgost-0.4/.flake8 +10 -0
- mgost-0.4/.github/workflows/coverage.yml +24 -0
- mgost-0.4/.github/workflows/flake8.yml +17 -0
- mgost-0.4/.github/workflows/tests.yml +23 -0
- mgost-0.4/.gitignore +4 -0
- mgost-0.4/.pre-commit-config.yaml +16 -0
- mgost-0.4/.python-version +1 -0
- mgost-0.4/.vscode/launch.json +16 -0
- mgost-0.4/.vscode/settings.json +11 -0
- mgost-0.4/LICENSE +21 -0
- mgost-0.4/PKG-INFO +33 -0
- mgost-0.4/README.md +2 -0
- mgost-0.4/pyproject.toml +61 -0
- mgost-0.4/src/mgost/__init__.py +1 -0
- mgost-0.4/src/mgost/__main__.py +4 -0
- mgost-0.4/src/mgost/api/__init__.py +2 -0
- mgost-0.4/src/mgost/api/actions.py +114 -0
- mgost-0.4/src/mgost/api/api.py +272 -0
- mgost-0.4/src/mgost/api/caller.py +162 -0
- mgost-0.4/src/mgost/api/exceptions.py +29 -0
- mgost-0.4/src/mgost/api/request.py +18 -0
- mgost-0.4/src/mgost/api/schemas/__init__.py +6 -0
- mgost-0.4/src/mgost/api/schemas/general.py +11 -0
- mgost-0.4/src/mgost/api/schemas/mgost.py +86 -0
- mgost-0.4/src/mgost/cli/__init__.py +4 -0
- mgost-0.4/src/mgost/cli/app.py +8 -0
- mgost-0.4/src/mgost/cli/async_commands.py +93 -0
- mgost-0.4/src/mgost/cli/callback.py +44 -0
- mgost-0.4/src/mgost/cli/commands.py +76 -0
- mgost-0.4/src/mgost/console.py +160 -0
- mgost-0.4/src/mgost/mgost/__init__.py +1 -0
- mgost-0.4/src/mgost/mgost/mgost.py +259 -0
- mgost-0.4/src/mgost/mgost/progress_utils.py +35 -0
- mgost-0.4/src/mgost/mgost/sync.py +241 -0
- mgost-0.4/src/mgost/mgost/utils.py +61 -0
- mgost-0.4/src/mgost/settings/__init__.py +1 -0
- mgost-0.4/src/mgost/settings/logger.py +34 -0
- mgost-0.4/src/mgost/settings/settings.py +194 -0
- mgost-0.4/temp/.gitignore +1 -0
- mgost-0.4/temp/.mgost/.env +1 -0
- mgost-0.4/temp/.mgost/settings.json +6 -0
- mgost-0.4/temp/Mun Ku.png +0 -0
- mgost-0.4/temp/logs/latest.log +0 -0
- mgost-0.4/temp/main.md +5 -0
- mgost-0.4/temp/output.docx +0 -0
- mgost-0.4/tests/__init__.py +0 -0
- mgost-0.4/tests/test_settings.py +6 -0
mgost-0.4/.flake8
ADDED
|
@@ -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,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
|
+
}
|
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
mgost-0.4/pyproject.toml
ADDED
|
@@ -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,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())
|