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.
Files changed (50) hide show
  1. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/PKG-INFO +14 -5
  2. skypro_cli_tool-0.2.1/README.md +30 -0
  3. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/pyproject.toml +11 -2
  4. skypro_cli_tool-0.2.1/src/skypro/cli/app.py +55 -0
  5. skypro_cli_tool-0.2.1/src/skypro/cli/commands/__init__.py +3 -0
  6. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/cli/commands/summary.py +6 -14
  7. skypro_cli_tool-0.2.1/src/skypro/config/__init__.py +18 -0
  8. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/config/logging.py +5 -7
  9. skypro_cli_tool-0.2.1/src/skypro/config/service.py +44 -0
  10. skypro_cli_tool-0.2.1/src/skypro/config/settings.py +55 -0
  11. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/domain/services.py +2 -3
  12. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/api/skypro/client.py +3 -3
  13. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/http/client.py +2 -2
  14. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/repositories/summary.py +5 -2
  15. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/repositories/work.py +1 -1
  16. skypro_cli_tool-0.2.1/src/skypro/logs/skypro.log +83 -0
  17. skypro_cli_tool-0.2.1/src/skypro/logs/skypro.log.1 +101 -0
  18. 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
  19. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/main.py +1 -1
  20. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/use_cases/get_summary_info.py +6 -3
  21. skypro_cli_tool-0.2.0/README.md +0 -21
  22. skypro_cli_tool-0.2.0/src/skypro/cli/app.py +0 -33
  23. skypro_cli_tool-0.2.0/src/skypro/cli/commands/__init__.py +0 -4
  24. skypro_cli_tool-0.2.0/src/skypro/cli/commands/initialize.py +0 -36
  25. skypro_cli_tool-0.2.0/src/skypro/config/settings.py +0 -127
  26. skypro_cli_tool-0.2.0/src/skypro/infra/http/__init__.py +0 -0
  27. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/__init__.py +0 -0
  28. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/cli/__init__.py +0 -0
  29. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/cli/renders/__init__.py +0 -0
  30. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/cli/renders/summary_table.py +0 -0
  31. {skypro_cli_tool-0.2.0/src/skypro/config → skypro_cli_tool-0.2.1/src/skypro/domain}/__init__.py +0 -0
  32. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/domain/enums.py +0 -0
  33. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/domain/errors.py +0 -0
  34. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/domain/models.py +0 -0
  35. {skypro_cli_tool-0.2.0/src/skypro/domain → skypro_cli_tool-0.2.1/src/skypro/infra}/__init__.py +0 -0
  36. {skypro_cli_tool-0.2.0/src/skypro/infra → skypro_cli_tool-0.2.1/src/skypro/infra/api}/__init__.py +0 -0
  37. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/api/base.py +0 -0
  38. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/api/errors.py +0 -0
  39. {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
  40. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/api/skypro/dto.py +0 -0
  41. {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
  42. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/http/errors.py +0 -0
  43. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/repositories/__init__.py +0 -0
  44. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/infra/repositories/errors.py +0 -0
  45. /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
  46. /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
  47. /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
  48. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/use_cases/__init__.py +0 -0
  49. {skypro_cli_tool-0.2.0 → skypro_cli_tool-0.2.1}/src/skypro/utils.py +0 -0
  50. {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.0
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[toml]>=2.14.0
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
- Initialise app
17
+ Show current config
18
18
 
19
19
  ```commandline
20
- sky-cli init
20
+ sky-cli config
21
21
  ```
22
22
 
23
- Check money you are earned this month
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.0"
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[toml]>=2.14.0",
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))
@@ -0,0 +1,3 @@
1
+ from .summary import show_summary
2
+
3
+ __all__ = ('show_summary',)
@@ -1,5 +1,5 @@
1
1
  from calendar import IllegalMonthError
2
- from datetime import UTC, date, datetime
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
- try:
20
- start, end = resolve_period(year, month)
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(start, end)
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 BASE_DIR, settings
8
+ from .settings import HOME_DIR, get_settings
9
9
 
10
- LOG_DIR = BASE_DIR / 'logs'
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(settings.tax_percent)) / Decimal(100)
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.settings import settings
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': settings.skypro.email,
56
- 'password': settings.skypro.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.settings import settings
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 settings.default_request_timeout,
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),
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  from decimal import Decimal, InvalidOperation
3
3
 
4
- from skypro.config.settings import BASE_DIR
4
+ from skypro.config import BASE_DIR
5
5
  from skypro.domain.enums import WorkType
6
6
  from skypro.domain.models import WorkPrice
7
7
 
@@ -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,7 +1,7 @@
1
1
  import locale
2
2
 
3
3
  from skypro.cli.app import app
4
- from skypro.config.logging import configure_logging
4
+ from skypro.config import configure_logging
5
5
 
6
6
 
7
7
  def main() -> None:
@@ -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(start_date: date, end_date: date) -> SummaryInfo:
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)
@@ -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,4 +0,0 @@
1
- from .initialize import initialize_settings
2
- from .summary import show_summary
3
-
4
- __all__ = ('initialize_settings', 'show_summary')
@@ -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