skypro-cli-tool 0.2.0__tar.gz → 0.2.1__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.
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/PKG-INFO +14 -5
- skypro_cli_tool-0.2.1/README.md +30 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/pyproject.toml +11 -2
- skypro_cli_tool-0.2.1/src/skypro/cli/app.py +55 -0
- skypro_cli_tool-0.2.1/src/skypro/cli/commands/__init__.py +3 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/cli/commands/summary.py +6 -14
- skypro_cli_tool-0.2.1/src/skypro/config/__init__.py +18 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/config/logging.py +5 -7
- skypro_cli_tool-0.2.1/src/skypro/config/service.py +44 -0
- skypro_cli_tool-0.2.1/src/skypro/config/settings.py +55 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/domain/services.py +2 -3
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/api/skypro/client.py +3 -3
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/http/client.py +2 -2
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/repositories/summary.py +5 -2
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/repositories/work.py +1 -1
- skypro_cli_tool-0.2.1/src/skypro/logs/skypro.log +83 -0
- skypro_cli_tool-0.2.1/src/skypro/logs/skypro.log.1 +101 -0
- skypro_cli_tool-0.2.0/src/skypro/logs/skypro.log → skypro_cli_tool-0.2.1/src/skypro/logs/skypro.log.2 +2 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/main.py +1 -1
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/use_cases/get_summary_info.py +6 -3
- skypro_cli_tool-0.2.0/README.md +0 -21
- skypro_cli_tool-0.2.0/src/skypro/cli/app.py +0 -33
- skypro_cli_tool-0.2.0/src/skypro/cli/commands/__init__.py +0 -4
- skypro_cli_tool-0.2.0/src/skypro/cli/commands/initialize.py +0 -36
- skypro_cli_tool-0.2.0/src/skypro/config/settings.py +0 -127
- skypro_cli_tool-0.2.0/src/skypro/infra/http/__init__.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/__init__.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/cli/__init__.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/cli/renders/__init__.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/cli/renders/summary_table.py +0 -0
- {skypro_cli_tool-0.2.0/src/skypro/config → skypro_cli_tool-0.2.1/src/skypro/domain}/__init__.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/domain/enums.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/domain/errors.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/domain/models.py +0 -0
- {skypro_cli_tool-0.2.0/src/skypro/domain → skypro_cli_tool-0.2.1/src/skypro/infra}/__init__.py +0 -0
- {skypro_cli_tool-0.2.0/src/skypro/infra → skypro_cli_tool-0.2.1/src/skypro/infra/api}/__init__.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/api/base.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/api/errors.py +0 -0
- {skypro_cli_tool-0.2.0/src/skypro/infra/api → skypro_cli_tool-0.2.1/src/skypro/infra/api/skypro}/__init__.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/api/skypro/dto.py +0 -0
- {skypro_cli_tool-0.2.0/src/skypro/infra/api/skypro → skypro_cli_tool-0.2.1/src/skypro/infra/http}/__init__.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/http/errors.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/repositories/__init__.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/repositories/errors.py +0 -0
- /skypro_cli_tool-0.2.0/src/skypro/logs/skypro.log.1 → /skypro_cli_tool-0.2.1/src/skypro/logs/skypro.log.3 +0 -0
- /skypro_cli_tool-0.2.0/src/skypro/logs/skypro.log.2 → /skypro_cli_tool-0.2.1/src/skypro/logs/skypro.log.4 +0 -0
- /skypro_cli_tool-0.2.0/src/skypro/logs/skypro.log.3 → /skypro_cli_tool-0.2.1/src/skypro/logs/skypro.log.5 +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/use_cases/__init__.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/utils.py +0 -0
- {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/work_prices.json +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: skypro-cli-tool
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: SkyPro cli tool
|
|
5
5
|
Requires-Dist: aiohttp>=3.13.5
|
|
6
6
|
Requires-Dist: httpx>=0.28.1
|
|
7
7
|
Requires-Dist: pydantic>=2.13.3
|
|
8
|
-
Requires-Dist: pydantic-settings
|
|
8
|
+
Requires-Dist: pydantic-settings>=2.14.0
|
|
9
9
|
Requires-Dist: typer>=0.25.0
|
|
10
10
|
Requires-Python: >=3.13
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
@@ -14,13 +14,22 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
|
|
15
15
|
## How to start
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Show current config
|
|
18
18
|
|
|
19
19
|
```commandline
|
|
20
|
-
sky-cli
|
|
20
|
+
sky-cli config
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Reconfigure SkyPro access
|
|
24
|
+
|
|
25
|
+
```commandline
|
|
26
|
+
sky-cli config --skypro
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If SkyPro credentials are missing, the `summary` command exits with an error.
|
|
30
|
+
Configure them first with `sky-cli config --skypro`.
|
|
31
|
+
|
|
32
|
+
Check money you earned this month
|
|
24
33
|
|
|
25
34
|
```commandline
|
|
26
35
|
sky-cli summary
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# SkyPro cli tool
|
|
2
|
+
|
|
3
|
+
## How to start
|
|
4
|
+
|
|
5
|
+
Show current config
|
|
6
|
+
|
|
7
|
+
```commandline
|
|
8
|
+
sky-cli config
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Reconfigure SkyPro access
|
|
12
|
+
|
|
13
|
+
```commandline
|
|
14
|
+
sky-cli config --skypro
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If SkyPro credentials are missing, the `summary` command exits with an error.
|
|
18
|
+
Configure them first with `sky-cli config --skypro`.
|
|
19
|
+
|
|
20
|
+
Check money you earned this month
|
|
21
|
+
|
|
22
|
+
```commandline
|
|
23
|
+
sky-cli summary
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Another month or year
|
|
27
|
+
|
|
28
|
+
```commandline
|
|
29
|
+
sky-cli summary --month 3 --year 2026
|
|
30
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "skypro-cli-tool"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "SkyPro cli tool"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -8,11 +8,16 @@ dependencies = [
|
|
|
8
8
|
"aiohttp>=3.13.5",
|
|
9
9
|
"httpx>=0.28.1",
|
|
10
10
|
"pydantic>=2.13.3",
|
|
11
|
-
"pydantic-settings
|
|
11
|
+
"pydantic-settings>=2.14.0",
|
|
12
12
|
"typer>=0.25.0",
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
[dependency-groups]
|
|
16
|
+
dev = [
|
|
17
|
+
{ include-group = "test" },
|
|
18
|
+
{ include-group = "lint" },
|
|
19
|
+
"ipython>=9.13.0",
|
|
20
|
+
]
|
|
16
21
|
test = [
|
|
17
22
|
"freezegun>=1.5.5",
|
|
18
23
|
"pytest>=9.0.3",
|
|
@@ -65,6 +70,10 @@ preview = true
|
|
|
65
70
|
"PLR2004", # Magic value used in comparison
|
|
66
71
|
"RUF069", # Unreliable floating point equality comparison
|
|
67
72
|
]
|
|
73
|
+
"*/cli/app.py" = [
|
|
74
|
+
"FBT001", # Boolean-typed positional argument in function definition
|
|
75
|
+
"FBT003", # Boolean positional value in function call
|
|
76
|
+
]
|
|
68
77
|
|
|
69
78
|
[tool.ruff.lint.isort]
|
|
70
79
|
combine-as-imports = true
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from typer import Option
|
|
7
|
+
|
|
8
|
+
from skypro.config import (
|
|
9
|
+
configure_skypro_settings,
|
|
10
|
+
enable_console_logging,
|
|
11
|
+
get_settings,
|
|
12
|
+
is_skypro_configured,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from . import commands
|
|
16
|
+
|
|
17
|
+
app = typer.Typer()
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@app.command(name='summary')
|
|
22
|
+
def get_summary(
|
|
23
|
+
year: int | None = Option(None, help='Год для расчета'),
|
|
24
|
+
month: int | None = Option(None, help='Месяц для расчета'),
|
|
25
|
+
verbose: bool = typer.Option(False, '-v', '--verbose'),
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Расчет стоимости работ"""
|
|
28
|
+
if not is_skypro_configured():
|
|
29
|
+
console.print(
|
|
30
|
+
'[red]Настройки SkyPro не заданы. '
|
|
31
|
+
'Укажите их командой `sky-cli config --skypro`.[/]'
|
|
32
|
+
)
|
|
33
|
+
raise typer.Exit(code=1)
|
|
34
|
+
|
|
35
|
+
if verbose:
|
|
36
|
+
enable_console_logging(console)
|
|
37
|
+
|
|
38
|
+
asyncio.run(commands.show_summary(console, year, month))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@app.command(name='config')
|
|
42
|
+
def configure(
|
|
43
|
+
skypro: bool = Option(
|
|
44
|
+
False,
|
|
45
|
+
'--skypro',
|
|
46
|
+
help='Изменить настройки доступа к SkyPro',
|
|
47
|
+
),
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Просмотр текущего конфига или настройка доступа к SkyPro"""
|
|
50
|
+
if skypro:
|
|
51
|
+
configure_skypro_settings(console)
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
settings = get_settings().model_dump(mode='json')
|
|
55
|
+
console.print(json.dumps(settings, ensure_ascii=False, indent=2))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from calendar import IllegalMonthError
|
|
2
|
-
from datetime import UTC,
|
|
2
|
+
from datetime import UTC, datetime
|
|
3
3
|
|
|
4
4
|
from click.exceptions import Exit
|
|
5
5
|
from rich.console import Console
|
|
@@ -8,7 +8,6 @@ from skypro.cli.renders import render_summary_table
|
|
|
8
8
|
from skypro.domain.errors import DomainError
|
|
9
9
|
from skypro.infra.repositories.errors import RepositoryError
|
|
10
10
|
from skypro.use_cases import get_summary_info
|
|
11
|
-
from skypro.utils import get_month_range
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
async def show_summary(
|
|
@@ -16,23 +15,16 @@ async def show_summary(
|
|
|
16
15
|
year: int | None,
|
|
17
16
|
month: int | None,
|
|
18
17
|
) -> None:
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
now = datetime.now(UTC).astimezone()
|
|
19
|
+
year = year or now.year
|
|
20
|
+
month = month or now.month
|
|
21
21
|
|
|
22
|
+
try:
|
|
22
23
|
with console.status('[cyan]Расчет стоимости работ'):
|
|
23
|
-
report, total, after_tax = await get_summary_info(
|
|
24
|
+
report, total, after_tax = await get_summary_info(year, month)
|
|
24
25
|
|
|
25
26
|
table = render_summary_table(report, total, after_tax)
|
|
26
27
|
console.print(table)
|
|
27
28
|
except (RepositoryError, DomainError, IllegalMonthError) as e:
|
|
28
29
|
console.print(f'[bold red]Error: {e!s}[/]')
|
|
29
30
|
raise Exit(code=1) from e
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def resolve_period(year: int | None, month: int | None) -> tuple[date, date]:
|
|
33
|
-
now = datetime.now(UTC).astimezone()
|
|
34
|
-
target_year = year or now.year
|
|
35
|
-
target_month = month or now.month
|
|
36
|
-
|
|
37
|
-
start, end = get_month_range(target_year, target_month)
|
|
38
|
-
return start, end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .logging import configure_logging, enable_console_logging
|
|
2
|
+
from .service import (
|
|
3
|
+
configure_skypro_settings,
|
|
4
|
+
ensure_skypro_configured,
|
|
5
|
+
is_skypro_configured,
|
|
6
|
+
)
|
|
7
|
+
from .settings import BASE_DIR, get_settings, save_settings
|
|
8
|
+
|
|
9
|
+
__all__ = (
|
|
10
|
+
'BASE_DIR',
|
|
11
|
+
'configure_logging',
|
|
12
|
+
'configure_skypro_settings',
|
|
13
|
+
'enable_console_logging',
|
|
14
|
+
'ensure_skypro_configured',
|
|
15
|
+
'get_settings',
|
|
16
|
+
'is_skypro_configured',
|
|
17
|
+
'save_settings',
|
|
18
|
+
)
|
|
@@ -5,9 +5,9 @@ from functools import cache
|
|
|
5
5
|
from rich.console import Console
|
|
6
6
|
from rich.logging import RichHandler
|
|
7
7
|
|
|
8
|
-
from .settings import
|
|
8
|
+
from .settings import HOME_DIR, get_settings
|
|
9
9
|
|
|
10
|
-
LOG_DIR =
|
|
10
|
+
LOG_DIR = HOME_DIR / 'logs'
|
|
11
11
|
|
|
12
12
|
LOGGING_CONFIG = {
|
|
13
13
|
'version': 1,
|
|
@@ -27,10 +27,7 @@ LOGGING_CONFIG = {
|
|
|
27
27
|
'formatter': 'formatter',
|
|
28
28
|
},
|
|
29
29
|
},
|
|
30
|
-
'root': {
|
|
31
|
-
'handlers': ['file'],
|
|
32
|
-
'level': settings.log_level,
|
|
33
|
-
},
|
|
30
|
+
'root': {'handlers': ['file']},
|
|
34
31
|
'loggers': {
|
|
35
32
|
'httpx': {'handlers': ['file'], 'level': logging.WARNING, 'propagate': True},
|
|
36
33
|
},
|
|
@@ -39,7 +36,8 @@ LOGGING_CONFIG = {
|
|
|
39
36
|
|
|
40
37
|
@cache
|
|
41
38
|
def configure_logging() -> None:
|
|
42
|
-
LOG_DIR.mkdir(exist_ok=True)
|
|
39
|
+
LOG_DIR.mkdir(mode=0o700, exist_ok=True, parents=True)
|
|
40
|
+
LOGGING_CONFIG['root']['level'] = get_settings().log_level.upper()
|
|
43
41
|
logging.config.dictConfig(LOGGING_CONFIG)
|
|
44
42
|
|
|
45
43
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from pydantic import SecretStr
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
|
|
4
|
+
from .settings import Settings, get_settings, save_settings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def is_skypro_configured(settings: Settings | None = None) -> bool:
|
|
8
|
+
current_settings = settings or get_settings()
|
|
9
|
+
return bool(current_settings.skypro.email and current_settings.skypro.password)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def configure_skypro_settings(console: Console) -> Settings:
|
|
13
|
+
current_settings = get_settings()
|
|
14
|
+
|
|
15
|
+
console.print('[cyan]Настройка доступа к SkyPro[/]')
|
|
16
|
+
|
|
17
|
+
current_email = current_settings.skypro.email or ''
|
|
18
|
+
email_prompt = 'Email'
|
|
19
|
+
if current_email:
|
|
20
|
+
email_prompt = f'Email [{current_email}]'
|
|
21
|
+
|
|
22
|
+
email = console.input(f'{email_prompt}: ').strip() or current_email
|
|
23
|
+
password = console.input('Password: ', password=True).strip()
|
|
24
|
+
if not password and current_settings.skypro.password.get_secret_value():
|
|
25
|
+
password = current_settings.skypro.password.get_secret_value()
|
|
26
|
+
|
|
27
|
+
updated_settings = current_settings.model_copy(
|
|
28
|
+
update={
|
|
29
|
+
'skypro': current_settings.skypro.model_copy(
|
|
30
|
+
update={'email': email, 'password': SecretStr(password)}
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
save_settings(updated_settings)
|
|
35
|
+
return updated_settings
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def ensure_skypro_configured(console: Console) -> Settings:
|
|
39
|
+
settings = get_settings()
|
|
40
|
+
if is_skypro_configured(settings):
|
|
41
|
+
return settings
|
|
42
|
+
|
|
43
|
+
console.print('[yellow]Конфигурация SkyPro не найдена. Заполним ее сейчас.[/]')
|
|
44
|
+
return configure_skypro_settings(console)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from functools import cache
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field, SecretStr
|
|
7
|
+
from pydantic_settings import (
|
|
8
|
+
BaseSettings,
|
|
9
|
+
JsonConfigSettingsSource,
|
|
10
|
+
PydanticBaseSettingsSource,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
14
|
+
|
|
15
|
+
HOME_DIR = Path.home() / '.skypro'
|
|
16
|
+
|
|
17
|
+
SETTINGS_FILE_PATH = HOME_DIR / 'settings.json'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SkyProSettings(BaseModel):
|
|
21
|
+
email: str | None = None
|
|
22
|
+
password: SecretStr | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Settings(BaseSettings, json_file=SETTINGS_FILE_PATH):
|
|
26
|
+
tax_percent: float = 6.0
|
|
27
|
+
default_request_timeout: int = 3
|
|
28
|
+
log_level: Literal['debug', 'info'] = 'info'
|
|
29
|
+
|
|
30
|
+
skypro: SkyProSettings = Field(default_factory=SkyProSettings)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def settings_customise_sources(
|
|
34
|
+
cls,
|
|
35
|
+
settings_cls: type[BaseSettings],
|
|
36
|
+
**_: PydanticBaseSettingsSource,
|
|
37
|
+
) -> tuple[PydanticBaseSettingsSource, ...]:
|
|
38
|
+
return (JsonConfigSettingsSource(settings_cls),)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def save_settings(settings: Settings) -> None:
|
|
42
|
+
SETTINGS_FILE_PATH.parent.mkdir(mode=0o700, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
data = settings.model_dump(mode='json')
|
|
45
|
+
data['skypro']['password'] = settings.skypro.password.get_secret_value()
|
|
46
|
+
|
|
47
|
+
with SETTINGS_FILE_PATH.open(mode='w', encoding='utf-8') as f:
|
|
48
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
49
|
+
|
|
50
|
+
get_settings.cache_clear()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@cache
|
|
54
|
+
def get_settings() -> Settings:
|
|
55
|
+
return Settings()
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from decimal import Decimal
|
|
2
2
|
|
|
3
|
-
from skypro.config.settings import settings
|
|
4
3
|
from skypro.domain.errors import MissingWorkPriceError
|
|
5
4
|
from skypro.domain.models import WorkPrice, WorkReportItem, WorkSummary
|
|
6
5
|
|
|
@@ -32,6 +31,6 @@ def calculate_total(report: list[WorkReportItem]) -> Decimal:
|
|
|
32
31
|
return sum((item.total for item in report), Decimal(0))
|
|
33
32
|
|
|
34
33
|
|
|
35
|
-
def apply_tax(amount: Decimal) -> Decimal:
|
|
36
|
-
tax_rate = Decimal(str(
|
|
34
|
+
def apply_tax(amount: Decimal, tax_percent: float) -> Decimal:
|
|
35
|
+
tax_rate = Decimal(str(tax_percent)) / Decimal(100)
|
|
37
36
|
return amount * (Decimal(1) - tax_rate)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from datetime import date
|
|
3
3
|
|
|
4
|
-
from skypro.config
|
|
4
|
+
from skypro.config import get_settings
|
|
5
5
|
from skypro.infra.api.base import BaseApiClient
|
|
6
6
|
from skypro.infra.api.errors import AuthenticationError
|
|
7
7
|
from skypro.infra.http.client import HttpClient
|
|
@@ -52,8 +52,8 @@ class SkyProClient(BaseApiClient):
|
|
|
52
52
|
response = await self._post(
|
|
53
53
|
self.login_url,
|
|
54
54
|
data={
|
|
55
|
-
'email':
|
|
56
|
-
'password':
|
|
55
|
+
'email': get_settings().skypro.email,
|
|
56
|
+
'password': get_settings().skypro.password.get_secret_value(),
|
|
57
57
|
'csrfmiddlewaretoken': csrf_token,
|
|
58
58
|
},
|
|
59
59
|
headers={'Referer': str(self.cookies)},
|
|
@@ -5,7 +5,7 @@ from typing import Any, Self
|
|
|
5
5
|
import httpx
|
|
6
6
|
from httpx import AsyncClient, Request, Response
|
|
7
7
|
|
|
8
|
-
from skypro.config
|
|
8
|
+
from skypro.config import get_settings
|
|
9
9
|
|
|
10
10
|
from .errors import HttpError
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
|
|
|
15
15
|
class HttpClient:
|
|
16
16
|
def __init__(self, timeout: int | None = None) -> None:
|
|
17
17
|
self._client = AsyncClient(
|
|
18
|
-
timeout=timeout or
|
|
18
|
+
timeout=timeout or get_settings().default_request_timeout,
|
|
19
19
|
follow_redirects=True,
|
|
20
20
|
event_hooks={'request': [_log_request], 'response': [_log_response]},
|
|
21
21
|
)
|
|
@@ -4,7 +4,7 @@ from pydantic import ValidationError
|
|
|
4
4
|
|
|
5
5
|
from skypro.domain.enums import WorkType
|
|
6
6
|
from skypro.domain.models import WorkSummary
|
|
7
|
-
from skypro.infra.api.errors import ApiError
|
|
7
|
+
from skypro.infra.api.errors import ApiError, AuthenticationError
|
|
8
8
|
from skypro.infra.api.skypro.client import SkyProClient
|
|
9
9
|
from skypro.infra.http.client import HttpClient
|
|
10
10
|
from skypro.infra.http.errors import HttpError
|
|
@@ -16,8 +16,11 @@ async def get_summary(start_date: date, end_date: date) -> list[WorkSummary]:
|
|
|
16
16
|
async with HttpClient() as http_client:
|
|
17
17
|
skypro = SkyProClient(http_client)
|
|
18
18
|
account_data = await skypro.get_account_data(start_date, end_date)
|
|
19
|
+
except AuthenticationError as e:
|
|
20
|
+
message = 'SkyPro authentication failed. Check email and password.'
|
|
21
|
+
raise SummaryLoadError(message) from e
|
|
19
22
|
except (HttpError, ApiError, ValidationError) as e:
|
|
20
|
-
raise SummaryLoadError from e
|
|
23
|
+
raise SummaryLoadError(str(e) or 'Failed to load summary from SkyPro.') from e
|
|
21
24
|
|
|
22
25
|
return [
|
|
23
26
|
WorkSummary(WorkType.HOMEWORK, account_data.services_summary.homework),
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
2026-05-10 16:59:23 - INFO - SkyPro authentication successful
|
|
2
|
+
2026-05-10 16:59:23 - INFO - Fetching account data from 2026-04-01 to 2026-04-30
|
|
3
|
+
2026-05-10 16:59:23 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30
|
|
4
|
+
2026-05-10 16:59:23 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30 -> 200
|
|
5
|
+
2026-05-10 16:59:28 - INFO - Authenticating in SkyPro
|
|
6
|
+
2026-05-10 16:59:28 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
7
|
+
2026-05-10 16:59:28 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
8
|
+
2026-05-10 16:59:28 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
9
|
+
2026-05-10 16:59:28 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
10
|
+
2026-05-10 16:59:28 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
11
|
+
2026-05-10 16:59:29 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
12
|
+
2026-05-10 16:59:29 - INFO - SkyPro authentication successful
|
|
13
|
+
2026-05-10 16:59:29 - INFO - Fetching account data from 2026-04-01 to 2026-04-30
|
|
14
|
+
2026-05-10 16:59:29 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30
|
|
15
|
+
2026-05-10 16:59:29 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30 -> 200
|
|
16
|
+
2026-05-10 17:01:19 - INFO - Authenticating in SkyPro
|
|
17
|
+
2026-05-10 17:01:19 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
18
|
+
2026-05-10 17:01:20 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
19
|
+
2026-05-10 17:01:20 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
20
|
+
2026-05-10 17:01:20 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
21
|
+
2026-05-10 17:01:20 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
22
|
+
2026-05-10 17:01:20 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
23
|
+
2026-05-10 17:01:20 - INFO - SkyPro authentication successful
|
|
24
|
+
2026-05-10 17:01:20 - INFO - Fetching account data from 2026-04-01 to 2026-04-30
|
|
25
|
+
2026-05-10 17:01:20 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30
|
|
26
|
+
2026-05-10 17:01:21 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30 -> 200
|
|
27
|
+
2026-05-10 17:01:25 - INFO - Authenticating in SkyPro
|
|
28
|
+
2026-05-10 17:01:25 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
29
|
+
2026-05-10 17:01:25 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
30
|
+
2026-05-10 17:01:25 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
31
|
+
2026-05-10 17:01:25 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
32
|
+
2026-05-10 17:01:25 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
33
|
+
2026-05-10 17:01:25 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
34
|
+
2026-05-10 17:01:40 - INFO - Authenticating in SkyPro
|
|
35
|
+
2026-05-10 17:01:40 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
36
|
+
2026-05-10 17:01:41 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
37
|
+
2026-05-10 17:01:41 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
38
|
+
2026-05-10 17:01:41 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
39
|
+
2026-05-10 17:01:41 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
40
|
+
2026-05-10 17:01:41 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
41
|
+
2026-05-10 17:01:41 - INFO - SkyPro authentication successful
|
|
42
|
+
2026-05-10 17:01:41 - INFO - Fetching account data from 2026-04-01 to 2026-04-30
|
|
43
|
+
2026-05-10 17:01:41 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30
|
|
44
|
+
2026-05-10 17:01:42 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30 -> 200
|
|
45
|
+
2026-05-10 17:03:18 - INFO - Authenticating in SkyPro
|
|
46
|
+
2026-05-10 17:03:18 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
47
|
+
2026-05-10 17:03:18 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
48
|
+
2026-05-10 17:03:18 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
49
|
+
2026-05-10 17:03:19 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
50
|
+
2026-05-10 17:03:19 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
51
|
+
2026-05-10 17:03:19 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
52
|
+
2026-05-10 17:03:19 - INFO - SkyPro authentication successful
|
|
53
|
+
2026-05-10 17:03:19 - INFO - Fetching account data from 2026-04-01 to 2026-04-30
|
|
54
|
+
2026-05-10 17:03:19 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30
|
|
55
|
+
2026-05-10 17:03:19 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30 -> 200
|
|
56
|
+
2026-05-10 17:14:09 - INFO - Authenticating in SkyPro
|
|
57
|
+
2026-05-10 17:14:09 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
58
|
+
2026-05-10 17:14:10 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
59
|
+
2026-05-10 17:14:10 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
60
|
+
2026-05-10 17:14:10 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
61
|
+
2026-05-10 17:14:10 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
62
|
+
2026-05-10 17:14:10 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
63
|
+
2026-05-10 17:14:31 - INFO - Authenticating in SkyPro
|
|
64
|
+
2026-05-10 17:14:31 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
65
|
+
2026-05-10 17:14:32 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
66
|
+
2026-05-10 17:14:32 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
67
|
+
2026-05-10 17:14:33 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
68
|
+
2026-05-10 17:14:33 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
69
|
+
2026-05-10 17:14:33 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
70
|
+
2026-05-10 17:14:33 - INFO - SkyPro authentication successful
|
|
71
|
+
2026-05-10 17:14:33 - INFO - Fetching account data from 2026-04-01 to 2026-04-30
|
|
72
|
+
2026-05-10 17:14:33 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30
|
|
73
|
+
2026-05-10 17:14:33 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-04-01&end_date=2026-04-30 -> 200
|
|
74
|
+
2026-05-10 17:20:00 - INFO - Authenticating in SkyPro
|
|
75
|
+
2026-05-10 17:20:00 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
76
|
+
2026-05-10 17:38:44 - INFO - Authenticating in SkyPro
|
|
77
|
+
2026-05-10 17:38:44 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
78
|
+
2026-05-10 17:38:45 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
79
|
+
2026-05-10 17:38:45 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
80
|
+
2026-05-10 17:38:45 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
81
|
+
2026-05-10 17:38:45 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
82
|
+
2026-05-10 17:38:45 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
83
|
+
2026-05-10 17:38:45 - INFO - SkyPro authentication successful
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
2026-05-07 06:55:33 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
2
|
+
2026-05-07 06:55:33 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
3
|
+
2026-05-07 06:55:33 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
4
|
+
2026-05-07 06:55:33 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
5
|
+
2026-05-07 06:55:33 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
6
|
+
2026-05-07 06:55:33 - INFO - SkyPro authentication successful
|
|
7
|
+
2026-05-07 06:55:33 - INFO - Fetching account data from 2026-05-01 to 2026-05-31
|
|
8
|
+
2026-05-07 06:55:33 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31
|
|
9
|
+
2026-05-07 06:55:34 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31 -> 200
|
|
10
|
+
2026-05-07 06:55:55 - INFO - Authenticating in SkyPro
|
|
11
|
+
2026-05-07 06:55:55 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
12
|
+
2026-05-07 06:55:56 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
13
|
+
2026-05-07 06:55:56 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
14
|
+
2026-05-07 06:55:56 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
15
|
+
2026-05-07 06:55:56 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
16
|
+
2026-05-07 06:55:56 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
17
|
+
2026-05-07 06:55:56 - INFO - SkyPro authentication successful
|
|
18
|
+
2026-05-07 06:58:55 - INFO - Authenticating in SkyPro
|
|
19
|
+
2026-05-07 06:58:55 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
20
|
+
2026-05-07 06:58:55 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
21
|
+
2026-05-07 06:58:55 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
22
|
+
2026-05-07 06:58:56 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
23
|
+
2026-05-07 06:58:56 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
24
|
+
2026-05-07 06:58:56 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
25
|
+
2026-05-07 06:58:56 - INFO - SkyPro authentication successful
|
|
26
|
+
2026-05-07 06:58:56 - INFO - Fetching account data from 2026-05-01 to 2026-05-31
|
|
27
|
+
2026-05-07 06:58:56 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31
|
|
28
|
+
2026-05-07 06:58:56 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31 -> 200
|
|
29
|
+
2026-05-07 18:49:04 - INFO - Authenticating in SkyPro
|
|
30
|
+
2026-05-07 18:49:04 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
31
|
+
2026-05-07 18:49:04 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
32
|
+
2026-05-07 18:49:04 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
33
|
+
2026-05-07 18:49:05 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
34
|
+
2026-05-07 18:49:05 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
35
|
+
2026-05-07 18:49:05 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
36
|
+
2026-05-07 18:49:05 - INFO - SkyPro authentication successful
|
|
37
|
+
2026-05-07 18:49:05 - INFO - Fetching account data from 2026-05-01 to 2026-05-31
|
|
38
|
+
2026-05-07 18:49:05 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31
|
|
39
|
+
2026-05-07 18:49:05 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31 -> 200
|
|
40
|
+
2026-05-08 15:37:35 - INFO - Authenticating in SkyPro
|
|
41
|
+
2026-05-08 15:37:35 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
42
|
+
2026-05-08 15:37:36 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
43
|
+
2026-05-08 15:37:36 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
44
|
+
2026-05-08 15:37:37 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
45
|
+
2026-05-08 15:37:37 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
46
|
+
2026-05-08 15:37:37 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
47
|
+
2026-05-08 15:37:37 - INFO - SkyPro authentication successful
|
|
48
|
+
2026-05-08 15:37:37 - INFO - Fetching account data from 2026-05-01 to 2026-05-31
|
|
49
|
+
2026-05-08 15:37:37 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31
|
|
50
|
+
2026-05-08 15:37:37 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31 -> 200
|
|
51
|
+
2026-05-09 18:52:31 - INFO - Authenticating in SkyPro
|
|
52
|
+
2026-05-09 18:52:31 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
53
|
+
2026-05-09 18:52:31 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
54
|
+
2026-05-09 18:52:31 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
55
|
+
2026-05-09 18:52:32 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
56
|
+
2026-05-09 18:52:32 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
57
|
+
2026-05-09 18:52:32 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
58
|
+
2026-05-09 18:52:32 - INFO - SkyPro authentication successful
|
|
59
|
+
2026-05-09 18:52:32 - INFO - Fetching account data from 2026-05-01 to 2026-05-31
|
|
60
|
+
2026-05-09 18:52:32 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31
|
|
61
|
+
2026-05-09 18:52:32 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31 -> 200
|
|
62
|
+
2026-05-10 16:17:06 - INFO - Authenticating in SkyPro
|
|
63
|
+
2026-05-10 16:17:06 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
64
|
+
2026-05-10 16:17:06 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
65
|
+
2026-05-10 16:17:06 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
66
|
+
2026-05-10 16:17:07 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
67
|
+
2026-05-10 16:17:07 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
68
|
+
2026-05-10 16:17:07 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
69
|
+
2026-05-10 16:17:07 - INFO - SkyPro authentication successful
|
|
70
|
+
2026-05-10 16:17:07 - INFO - Fetching account data from 2026-05-01 to 2026-05-31
|
|
71
|
+
2026-05-10 16:17:07 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31
|
|
72
|
+
2026-05-10 16:17:07 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31 -> 200
|
|
73
|
+
2026-05-10 16:58:55 - INFO - Authenticating in SkyPro
|
|
74
|
+
2026-05-10 16:58:55 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
75
|
+
2026-05-10 16:58:56 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
76
|
+
2026-05-10 16:58:56 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
77
|
+
2026-05-10 16:58:56 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
78
|
+
2026-05-10 16:58:56 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
79
|
+
2026-05-10 16:58:56 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
80
|
+
2026-05-10 16:58:56 - INFO - SkyPro authentication successful
|
|
81
|
+
2026-05-10 16:58:56 - INFO - Fetching account data from 2026-05-01 to 2026-05-31
|
|
82
|
+
2026-05-10 16:58:56 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31
|
|
83
|
+
2026-05-10 16:58:57 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31 -> 200
|
|
84
|
+
2026-05-10 16:59:03 - INFO - Authenticating in SkyPro
|
|
85
|
+
2026-05-10 16:59:03 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
86
|
+
2026-05-10 16:59:03 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
87
|
+
2026-05-10 16:59:03 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
88
|
+
2026-05-10 16:59:04 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
89
|
+
2026-05-10 16:59:04 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
90
|
+
2026-05-10 16:59:04 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
91
|
+
2026-05-10 16:59:04 - INFO - SkyPro authentication successful
|
|
92
|
+
2026-05-10 16:59:04 - INFO - Fetching account data from 2026-05-01 to 2026-05-31
|
|
93
|
+
2026-05-10 16:59:04 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31
|
|
94
|
+
2026-05-10 16:59:04 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31 -> 200
|
|
95
|
+
2026-05-10 16:59:22 - INFO - Authenticating in SkyPro
|
|
96
|
+
2026-05-10 16:59:22 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
97
|
+
2026-05-10 16:59:23 - INFO - HTTP response: GET https://operation-planning.sky.pro/careusers/login/ -> 200
|
|
98
|
+
2026-05-10 16:59:23 - INFO - HTTP request: POST https://operation-planning.sky.pro/careusers/login/
|
|
99
|
+
2026-05-10 16:59:23 - INFO - HTTP response: POST https://operation-planning.sky.pro/careusers/login/ -> 302
|
|
100
|
+
2026-05-10 16:59:23 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/
|
|
101
|
+
2026-05-10 16:59:23 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/ -> 200
|
|
@@ -105,3 +105,5 @@
|
|
|
105
105
|
2026-05-07 06:50:51 - INFO - Fetching account data from 2026-05-01 to 2026-05-31
|
|
106
106
|
2026-05-07 06:50:51 - INFO - HTTP request: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31
|
|
107
107
|
2026-05-07 06:50:52 - INFO - HTTP response: GET https://operation-planning.sky.pro/mentor-cabinet/api/data/?start_date=2026-05-01&end_date=2026-05-31 -> 200
|
|
108
|
+
2026-05-07 06:55:32 - INFO - Authenticating in SkyPro
|
|
109
|
+
2026-05-07 06:55:32 - INFO - HTTP request: GET https://operation-planning.sky.pro/careusers/login/
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from asyncio import gather
|
|
3
|
-
from datetime import date
|
|
4
3
|
from decimal import Decimal
|
|
5
4
|
from typing import NamedTuple
|
|
6
5
|
|
|
6
|
+
from skypro.config import get_settings
|
|
7
7
|
from skypro.domain.models import WorkReportItem
|
|
8
8
|
from skypro.domain.services import apply_tax, build_work_report, calculate_total
|
|
9
9
|
from skypro.infra.repositories import get_summary, get_works_prices
|
|
10
|
+
from skypro.utils import get_month_range
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class SummaryInfo(NamedTuple):
|
|
@@ -15,7 +16,9 @@ class SummaryInfo(NamedTuple):
|
|
|
15
16
|
total_after_tax: Decimal
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
async def get_summary_info(
|
|
19
|
+
async def get_summary_info(year: int, month: int) -> SummaryInfo:
|
|
20
|
+
start_date, end_date = get_month_range(year, month)
|
|
21
|
+
|
|
19
22
|
work_prices, work_summaries = await gather(
|
|
20
23
|
asyncio.to_thread(get_works_prices),
|
|
21
24
|
get_summary(start_date, end_date),
|
|
@@ -23,6 +26,6 @@ async def get_summary_info(start_date: date, end_date: date) -> SummaryInfo:
|
|
|
23
26
|
|
|
24
27
|
report = build_work_report(work_prices, work_summaries)
|
|
25
28
|
total = calculate_total(report)
|
|
26
|
-
total_after_tax = apply_tax(total)
|
|
29
|
+
total_after_tax = apply_tax(total, get_settings().tax_percent)
|
|
27
30
|
|
|
28
31
|
return SummaryInfo(report, total, total_after_tax)
|
skypro_cli_tool-0.2.0/README.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# SkyPro cli tool
|
|
2
|
-
|
|
3
|
-
## How to start
|
|
4
|
-
|
|
5
|
-
Initialise app
|
|
6
|
-
|
|
7
|
-
```commandline
|
|
8
|
-
sky-cli init
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Check money you are earned this month
|
|
12
|
-
|
|
13
|
-
```commandline
|
|
14
|
-
sky-cli summary
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Another month or year
|
|
18
|
-
|
|
19
|
-
```commandline
|
|
20
|
-
sky-cli summary --month 3 --year 2026
|
|
21
|
-
```
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
|
|
3
|
-
import typer
|
|
4
|
-
from rich.console import Console
|
|
5
|
-
from typer import Option
|
|
6
|
-
|
|
7
|
-
from skypro.config.logging import enable_console_logging
|
|
8
|
-
|
|
9
|
-
from . import commands
|
|
10
|
-
from .commands import initialize_settings
|
|
11
|
-
|
|
12
|
-
app = typer.Typer()
|
|
13
|
-
console = Console()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@app.command(name='summary')
|
|
17
|
-
def get_summary(
|
|
18
|
-
year: int | None = Option(None, help='Год для расчета'),
|
|
19
|
-
month: int | None = Option(None, help='Месяц для расчета'),
|
|
20
|
-
*,
|
|
21
|
-
verbose: bool = typer.Option(False, '-v', '--verbose'), # noqa: FBT003
|
|
22
|
-
) -> None:
|
|
23
|
-
"""Расчет стоимости работ"""
|
|
24
|
-
if verbose:
|
|
25
|
-
enable_console_logging(console)
|
|
26
|
-
asyncio.run(commands.show_summary(console, year, month))
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@app.command(name='init')
|
|
30
|
-
def init_settings() -> None:
|
|
31
|
-
"""Установка настроек."""
|
|
32
|
-
asyncio.run(initialize_settings(console))
|
|
33
|
-
console.print('[green]OK[/]')
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
from rich.console import Console
|
|
2
|
-
|
|
3
|
-
from skypro.config.settings import save_settings, settings
|
|
4
|
-
from skypro.infra.api.errors import ApiError
|
|
5
|
-
from skypro.infra.api.skypro.client import SkyProClient
|
|
6
|
-
from skypro.infra.http.client import HttpClient
|
|
7
|
-
from skypro.infra.http.errors import HttpError
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
async def initialize_settings(console: Console) -> None:
|
|
11
|
-
while not await is_skypro_credentials_valid():
|
|
12
|
-
if settings.skypro.email:
|
|
13
|
-
console.print('[red]Некорретные данные для skypro[/]')
|
|
14
|
-
else:
|
|
15
|
-
console.print('Укажи данные для подключения к skypro')
|
|
16
|
-
|
|
17
|
-
_set_skypro_settings(console)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
async def is_skypro_credentials_valid() -> bool:
|
|
21
|
-
try:
|
|
22
|
-
async with HttpClient() as http_client:
|
|
23
|
-
skypro = SkyProClient(http_client)
|
|
24
|
-
await skypro.login()
|
|
25
|
-
except (HttpError, ApiError):
|
|
26
|
-
return False
|
|
27
|
-
|
|
28
|
-
return True
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _set_skypro_settings(console: Console) -> None:
|
|
32
|
-
email = console.input('Email: ')
|
|
33
|
-
password = console.input('Password: ', password=True)
|
|
34
|
-
settings.skypro.email = email
|
|
35
|
-
settings.skypro.password = password
|
|
36
|
-
save_settings(settings)
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from typing import Literal
|
|
3
|
-
|
|
4
|
-
from pydantic import BaseModel, Field
|
|
5
|
-
from pydantic_settings import (
|
|
6
|
-
BaseSettings,
|
|
7
|
-
PydanticBaseSettingsSource,
|
|
8
|
-
SettingsConfigDict,
|
|
9
|
-
TomlConfigSettingsSource,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
13
|
-
|
|
14
|
-
HOME_PATH = Path.home() / '.skypro'
|
|
15
|
-
SETTINGS_FILE_PATH = HOME_PATH / 'settings.toml'
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class SkyProSettings(BaseModel):
|
|
19
|
-
email: str = ''
|
|
20
|
-
password: str = ''
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class Settings(BaseSettings):
|
|
24
|
-
tax_percent: float = 6.0
|
|
25
|
-
default_request_timeout: int = 3
|
|
26
|
-
log_level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] = 'INFO'
|
|
27
|
-
|
|
28
|
-
skypro: SkyProSettings = Field(default_factory=SkyProSettings)
|
|
29
|
-
|
|
30
|
-
model_config = SettingsConfigDict(
|
|
31
|
-
env_nested_delimiter='__',
|
|
32
|
-
toml_file=SETTINGS_FILE_PATH,
|
|
33
|
-
extra='ignore',
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
@classmethod
|
|
37
|
-
def settings_customise_sources(
|
|
38
|
-
cls,
|
|
39
|
-
settings_cls: type[BaseSettings],
|
|
40
|
-
init_settings: PydanticBaseSettingsSource,
|
|
41
|
-
env_settings: PydanticBaseSettingsSource,
|
|
42
|
-
dotenv_settings: PydanticBaseSettingsSource,
|
|
43
|
-
file_secret_settings: PydanticBaseSettingsSource,
|
|
44
|
-
) -> tuple[PydanticBaseSettingsSource, ...]:
|
|
45
|
-
return (
|
|
46
|
-
init_settings,
|
|
47
|
-
env_settings,
|
|
48
|
-
TomlConfigSettingsSource(settings_cls),
|
|
49
|
-
dotenv_settings,
|
|
50
|
-
file_secret_settings,
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
@classmethod
|
|
54
|
-
def from_toml_file(cls, file_path: Path) -> 'Settings':
|
|
55
|
-
original_toml_file = cls.model_config.get('toml_file')
|
|
56
|
-
cls.model_config['toml_file'] = file_path
|
|
57
|
-
try:
|
|
58
|
-
return cls()
|
|
59
|
-
finally:
|
|
60
|
-
cls.model_config['toml_file'] = original_toml_file
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _serialize_settings(current_settings: Settings) -> str:
|
|
64
|
-
return _dump_toml(current_settings.model_dump())
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def _dump_toml(data: dict[str, object]) -> str:
|
|
68
|
-
root_lines: list[str] = []
|
|
69
|
-
table_chunks: list[str] = []
|
|
70
|
-
|
|
71
|
-
for key, value in data.items():
|
|
72
|
-
if isinstance(value, dict):
|
|
73
|
-
table_body = _dump_toml_table(value)
|
|
74
|
-
table_chunks.append(f'[{key}]\n{table_body}')
|
|
75
|
-
continue
|
|
76
|
-
|
|
77
|
-
root_lines.append(f'{key} = {_format_toml_value(value)}')
|
|
78
|
-
|
|
79
|
-
chunks = ['\n'.join(root_lines)] if root_lines else []
|
|
80
|
-
chunks.extend(table_chunks)
|
|
81
|
-
return '\n\n'.join(chunks) + '\n'
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _dump_toml_table(data: dict[str, object]) -> str:
|
|
85
|
-
lines = [f'{key} = {_format_toml_value(value)}' for key, value in data.items()]
|
|
86
|
-
return '\n'.join(lines)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def _format_toml_value(value: object) -> str:
|
|
90
|
-
if isinstance(value, str):
|
|
91
|
-
escaped_value = value.replace('\\', '\\\\').replace('"', '\\"')
|
|
92
|
-
return f'"{escaped_value}"'
|
|
93
|
-
if isinstance(value, bool):
|
|
94
|
-
return str(value).lower()
|
|
95
|
-
return str(value)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def get_default_settings() -> Settings:
|
|
99
|
-
return Settings.model_construct(
|
|
100
|
-
**{
|
|
101
|
-
field_name: field_info.get_default(call_default_factory=True)
|
|
102
|
-
for field_name, field_info in Settings.model_fields.items()
|
|
103
|
-
}
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def save_settings(
|
|
108
|
-
current_settings: Settings, file_path: Path = SETTINGS_FILE_PATH
|
|
109
|
-
) -> None:
|
|
110
|
-
file_path.parent.mkdir(mode=0o700, exist_ok=True)
|
|
111
|
-
file_path.write_text(_serialize_settings(current_settings), encoding='utf-8')
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def ensure_settings_file(file_path: Path = SETTINGS_FILE_PATH) -> None:
|
|
115
|
-
if file_path.exists():
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
save_settings(get_default_settings(), file_path)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def get_settings(file_path: Path = SETTINGS_FILE_PATH) -> Settings:
|
|
122
|
-
file_path.parent.mkdir(mode=0o700, exist_ok=True)
|
|
123
|
-
ensure_settings_file(file_path)
|
|
124
|
-
return Settings.from_toml_file(file_path)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
settings = get_settings()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{skypro_cli_tool-0.2.0/src/skypro/config → skypro_cli_tool-0.2.1/src/skypro/domain}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{skypro_cli_tool-0.2.0/src/skypro/domain → skypro_cli_tool-0.2.1/src/skypro/infra}/__init__.py
RENAMED
|
File without changes
|
{skypro_cli_tool-0.2.0/src/skypro/infra → skypro_cli_tool-0.2.1/src/skypro/infra/api}/__init__.py
RENAMED
|
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
|