schoolmospy 0.0.2__tar.gz → 0.2.2__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.
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: schoolmospy
3
+ Version: 0.2.2
4
+ Summary: A lightweight async Python wrapper for school.mos.ru APIs
5
+ License: GPL-3.0-only
6
+ License-File: LICENSE
7
+ Keywords: school,mos,api,education,async,wrapper,python
8
+ Author: Ivan Kriventsev
9
+ Author-email: xd2dd@icloud.com
10
+ Requires-Python: >=3.12
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Dist: aiohttp (>=3.12.15,<4.0.0)
17
+ Requires-Dist: httpx (>=0.28.1,<0.29.0)
18
+ Requires-Dist: pydantic (>=2.11.9,<3.0.0)
19
+ Project-URL: Documentation, https://xd2dd.github.io/schoolmospy
20
+ Project-URL: Homepage, https://github.com/xd2dd/schoolmospy
21
+ Project-URL: Issues, https://github.com/xd2dd/schoolmospy/issues
22
+ Description-Content-Type: text/markdown
23
+
24
+ ![Badge](https://img.shields.io/badge/version-0.2.0-blue.svg)
25
+ ![Python](https://img.shields.io/badge/python-3.12+-blue.svg)
26
+ ![License](https://img.shields.io/badge/license-GPL--3.0-green.svg)
27
+
28
+ # 📕 SchoolMosPy
29
+
30
+ SchoolMosPy is a lightweight async Python wrapper for school.mos.ru APIs. It provides a simple and intuitive interface to interact with the Moscow School Electronic Diary platform.
31
+
32
+ ## ✨ Features
33
+
34
+ - **Async architecture** - built on `aiohttp` and `httpx` for high performance
35
+ - **Type safety** - uses `pydantic` for data validation
36
+ - **Simple API** - intuitive interface for working with data
37
+ - **Full-featured** - access to marks, homeworks, events, and profile data
38
+
39
+ ## 📋 Requirements
40
+
41
+ - Python 3.12+
42
+ - aiohttp >= 3.12.15
43
+ - pydantic >= 2.11.9
44
+ - httpx >= 0.28.1
45
+
46
+ ## 📦 Installation
47
+
48
+ ```bash
49
+ pip install schoolmospy
50
+ ```
51
+
52
+ ## 🚀 Quick Start
53
+
54
+ ```python
55
+ import asyncio
56
+ from schoolmospy import StudentClient
57
+
58
+ async def main():
59
+ # Create client
60
+ client = StudentClient(
61
+ token="your_token",
62
+ profile_id=12345
63
+ )
64
+
65
+ # Get profile
66
+ profile = await client.get_me()
67
+ print(f"Name: {profile.name}")
68
+
69
+ # Get marks
70
+ marks = await client.marks.get_marks(
71
+ from_date="2024-01-01",
72
+ to_date="2024-12-31"
73
+ )
74
+
75
+ # Get homeworks
76
+ homeworks = await client.homeworks.get_homeworks(
77
+ from_date="2024-01-01",
78
+ to_date="2024-12-31"
79
+ )
80
+
81
+ # Get events/schedule
82
+ events = await client.events.get_events(
83
+ from_date="2024-01-01",
84
+ to_date="2024-12-31"
85
+ )
86
+
87
+ await client.close()
88
+
89
+ asyncio.run(main())
90
+ ```
91
+
92
+ ## 📝 License
93
+
94
+ This project is licensed under the GPL-3.0 License. See the [LICENSE](LICENSE) file for details.
95
+
96
+ ## 🔗 Links
97
+
98
+ - [Documentation](https://xd2dd.github.io/schoolmospy)
99
+ - [Report an Issue](https://github.com/xd2dd/schoolmospy/issues)
100
+
101
+ ## 👨‍💻 Author
102
+
103
+ Ivan Kriventsev - [xd2dd@icloud.com](mailto:xd2dd@icloud.com)
104
+
105
+ ---
106
+
107
+ ⭐ If you like this project, please give it a star on GitHub!
108
+
@@ -0,0 +1,84 @@
1
+ ![Badge](https://img.shields.io/badge/version-0.2.0-blue.svg)
2
+ ![Python](https://img.shields.io/badge/python-3.12+-blue.svg)
3
+ ![License](https://img.shields.io/badge/license-GPL--3.0-green.svg)
4
+
5
+ # 📕 SchoolMosPy
6
+
7
+ SchoolMosPy is a lightweight async Python wrapper for school.mos.ru APIs. It provides a simple and intuitive interface to interact with the Moscow School Electronic Diary platform.
8
+
9
+ ## ✨ Features
10
+
11
+ - **Async architecture** - built on `aiohttp` and `httpx` for high performance
12
+ - **Type safety** - uses `pydantic` for data validation
13
+ - **Simple API** - intuitive interface for working with data
14
+ - **Full-featured** - access to marks, homeworks, events, and profile data
15
+
16
+ ## 📋 Requirements
17
+
18
+ - Python 3.12+
19
+ - aiohttp >= 3.12.15
20
+ - pydantic >= 2.11.9
21
+ - httpx >= 0.28.1
22
+
23
+ ## 📦 Installation
24
+
25
+ ```bash
26
+ pip install schoolmospy
27
+ ```
28
+
29
+ ## 🚀 Quick Start
30
+
31
+ ```python
32
+ import asyncio
33
+ from schoolmospy import StudentClient
34
+
35
+ async def main():
36
+ # Create client
37
+ client = StudentClient(
38
+ token="your_token",
39
+ profile_id=12345
40
+ )
41
+
42
+ # Get profile
43
+ profile = await client.get_me()
44
+ print(f"Name: {profile.name}")
45
+
46
+ # Get marks
47
+ marks = await client.marks.get_marks(
48
+ from_date="2024-01-01",
49
+ to_date="2024-12-31"
50
+ )
51
+
52
+ # Get homeworks
53
+ homeworks = await client.homeworks.get_homeworks(
54
+ from_date="2024-01-01",
55
+ to_date="2024-12-31"
56
+ )
57
+
58
+ # Get events/schedule
59
+ events = await client.events.get_events(
60
+ from_date="2024-01-01",
61
+ to_date="2024-12-31"
62
+ )
63
+
64
+ await client.close()
65
+
66
+ asyncio.run(main())
67
+ ```
68
+
69
+ ## 📝 License
70
+
71
+ This project is licensed under the GPL-3.0 License. See the [LICENSE](LICENSE) file for details.
72
+
73
+ ## 🔗 Links
74
+
75
+ - [Documentation](https://xd2dd.github.io/schoolmospy)
76
+ - [Report an Issue](https://github.com/xd2dd/schoolmospy/issues)
77
+
78
+ ## 👨‍💻 Author
79
+
80
+ Ivan Kriventsev - [xd2dd@icloud.com](mailto:xd2dd@icloud.com)
81
+
82
+ ---
83
+
84
+ ⭐ If you like this project, please give it a star on GitHub!
@@ -0,0 +1,91 @@
1
+ [project]
2
+ name = "schoolmospy"
3
+ version = "0.2.2"
4
+ description = "A lightweight async Python wrapper for school.mos.ru APIs"
5
+ authors = [{ name = "Ivan Kriventsev", email = "xd2dd@icloud.com" }]
6
+ readme = "README.md"
7
+ license = { text = "GPL-3.0-only" }
8
+ requires-python = ">=3.12"
9
+
10
+ keywords = ["school", "mos", "api", "education", "async", "wrapper", "python"]
11
+
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3.12",
14
+ "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
15
+ "Operating System :: OS Independent",
16
+ "Intended Audience :: Developers",
17
+ "Topic :: Software Development :: Libraries :: Python Modules",
18
+ ]
19
+
20
+ dependencies = [
21
+ "aiohttp >=3.12.15,<4.0.0",
22
+ "pydantic >=2.11.9,<3.0.0",
23
+ "httpx (>=0.28.1,<0.29.0)",
24
+ ]
25
+
26
+ packages = [{ include = "schoolmospy" }]
27
+
28
+ [project.urls]
29
+ "Homepage" = "https://github.com/xd2dd/schoolmospy"
30
+ "Documentation" = "https://xd2dd.github.io/schoolmospy"
31
+ "Issues" = "https://github.com/xd2dd/schoolmospy/issues"
32
+
33
+
34
+ [build-system]
35
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
36
+ build-backend = "poetry.core.masonry.api"
37
+
38
+ [tool.poetry.group.dev.dependencies]
39
+ ruff = "^0.13.2"
40
+ mypy = "^1.18.2"
41
+ black = "^25.9.0"
42
+ isort = "^6.0.1"
43
+ pre-commit = "^4.0.0"
44
+
45
+ [tool.black]
46
+ line-length = 100
47
+ target-version = ["py312"]
48
+
49
+ [tool.isort]
50
+ profile = "black"
51
+ line_length = 100
52
+
53
+ [tool.ruff]
54
+ line-length = 100
55
+ target-version = "py312"
56
+
57
+ [tool.ruff.lint]
58
+ select = [
59
+ "E", # pycodestyle errors
60
+ "F", # pyflakes
61
+ "W", # pycodestyle warnings
62
+ "I", # isort
63
+ "N", # pep8-naming
64
+ "UP", # pyupgrade
65
+ "ANN", # flake8-annotations
66
+ "B", # flake8-bugbear
67
+ "A", # flake8-builtins
68
+ "C4", # flake8-comprehensions
69
+ "RET", # flake8-return
70
+ "SIM", # flake8-simplify
71
+ "ARG", # flake8-unused-arguments
72
+ "PTH", # flake8-use-pathlib
73
+ "RUF", # Ruff-specific rules
74
+ ]
75
+ ignore = [
76
+ "ANN101", # Missing type annotation for self in method
77
+ "ANN102", # Missing type annotation for cls in classmethod
78
+ "ANN401", # Dynamically typed expressions (typing.Any) are disallowed
79
+ "TRY003", # Avoid specifying long messages outside the exception class
80
+ "EM101", # Exception must not use a string literal
81
+ "PLR2004", # Magic value used in comparison
82
+ "COM812", # Trailing comma missing (conflicts with formatter)
83
+ ]
84
+
85
+ [tool.mypy]
86
+ python_version = "3.12"
87
+ ignore_missing_imports = true
88
+ warn_return_any = false
89
+ warn_unused_ignores = true
90
+ disallow_untyped_defs = false
91
+ check_untyped_defs = true
@@ -0,0 +1,4 @@
1
+ from schoolmospy.core.basic_client import BasicClient as BasicClient
2
+ from schoolmospy.core.student_client import StudentClient as StudentClient
3
+
4
+ __all__ = ["BasicClient", "StudentClient"]
@@ -1,18 +1,20 @@
1
+ from typing import Any
2
+
1
3
  import httpx
2
- from typing import Any, Optional, Type
3
4
  from pydantic import BaseModel
4
- from schoolmospy.utils.exceptions import APIError, AuthError, NotFoundError, ServerError, HTTPError
5
+
6
+ from schoolmospy.utils.exceptions import APIError, AuthError, HTTPError, NotFoundError, ServerError
5
7
 
6
8
 
7
9
  class BasicClient:
8
10
  def __init__(
9
11
  self,
10
12
  base_url: str,
11
- token: Optional[str] = None,
12
- profile_id: Optional[int] = None,
13
+ token: str | None = None,
14
+ profile_id: int | None = None,
13
15
  profile_type: str = "student",
14
16
  timeout: float = 15.0,
15
- ):
17
+ ) -> None:
16
18
  self.base_url = base_url.rstrip("/")
17
19
  self.token = token
18
20
  self.profile_id = profile_id
@@ -33,29 +35,36 @@ class BasicClient:
33
35
  headers["Profile-Id"] = str(self.profile_id)
34
36
  if self.profile_type:
35
37
  headers["Profile-Type"] = self.profile_type
38
+ headers["X-Mes-Role"] = self.profile_type
36
39
  return headers
37
40
 
38
- async def _handle_response(self, response: httpx.Response, response_model: Optional[Type[BaseModel]] = None):
39
- """Обработка ответа и ошибок"""
41
+ async def _handle_response(
42
+ self,
43
+ response: httpx.Response,
44
+ response_model: type[BaseModel] | None = None,
45
+ ) -> Any:
40
46
  if response.is_success:
41
47
  if response_model:
42
48
  return response_model.model_validate(response.json())
43
49
  return response.json()
44
50
 
45
- # Ошибки
46
51
  text = response.text.strip()
47
52
  status = response.status_code
48
53
 
49
54
  if status == 401:
50
55
  raise AuthError("Unauthorized or invalid token", status, text)
51
- elif status == 404:
56
+ if status == 404:
52
57
  raise NotFoundError("Resource not found", status, text)
53
- elif status >= 500:
58
+ if status >= 500:
54
59
  raise ServerError("Server error", status, text)
55
- else:
56
- raise HTTPError(f"Unexpected response ({status})", status, text)
60
+ raise HTTPError(f"Unexpected response ({status})", status, text)
57
61
 
58
- async def get(self, endpoint: str, response_model: Optional[Type[BaseModel]] = None, **kwargs: Any):
62
+ async def get(
63
+ self,
64
+ endpoint: str,
65
+ response_model: type[BaseModel] | None = None,
66
+ **kwargs: Any,
67
+ ) -> Any:
59
68
  url = f"{self.base_url}/{endpoint.lstrip('/')}"
60
69
  async with httpx.AsyncClient(headers=self.headers, timeout=self.timeout) as client:
61
70
  try:
@@ -64,7 +73,13 @@ class BasicClient:
64
73
  raise APIError(f"Request failed: {e}") from e
65
74
  return await self._handle_response(resp, response_model)
66
75
 
67
- async def post(self, endpoint: str, data: Any = None, response_model: Optional[Type[BaseModel]] = None, **kwargs: Any):
76
+ async def post(
77
+ self,
78
+ endpoint: str,
79
+ data: Any = None,
80
+ response_model: type[BaseModel] | None = None,
81
+ **kwargs: Any,
82
+ ) -> Any:
68
83
  url = f"{self.base_url}/{endpoint.lstrip('/')}"
69
84
  async with httpx.AsyncClient(headers=self.headers, timeout=self.timeout) as client:
70
85
  try:
@@ -0,0 +1,62 @@
1
+ from datetime import datetime
2
+
3
+ from schoolmospy.core.basic_client import BasicClient
4
+ from schoolmospy.models.events import Events
5
+
6
+
7
+ class EventClient:
8
+ def __init__(self, client: BasicClient) -> None:
9
+ """
10
+ Initialization the EventClient instance.
11
+
12
+ Args:
13
+ client (BasicClient): An instance of BasicClient or any compatible subclass,
14
+ used for making API requests.
15
+ """
16
+ self.client = client
17
+
18
+ async def get(self, from_date: datetime, to_date: datetime, contingent_guid: str) -> Events:
19
+ """
20
+ Method for getting events done within a certain period of time
21
+
22
+ Args:
23
+ from_date (datetime): The start date of the period (inclusive).
24
+ to_date (datetime): The end date of the period (inclusive).
25
+ contingent_guid (str): Person ID
26
+
27
+ Returns:
28
+ Events: A Pydantic model containing a list of events items.
29
+
30
+ Raises:
31
+ APIError: If the request fails or returns an unexpected response.
32
+ AuthError: If the provided token is invalid or expired.
33
+
34
+ Example:
35
+ ```python
36
+ from datetime import datetime
37
+ from schoolmospy.clients.student_client import StudentClient
38
+
39
+ client = StudentClient(token="YOUR_TOKEN", profile_id=17234613)
40
+
41
+ me = await client.get_me()
42
+
43
+ events = await client.events.get(
44
+ from_date=datetime(2025, 10, 1),
45
+ to_date=datetime(2025, 10, 24),
46
+ contingent_guid=me.contingent_guid
47
+ )
48
+ print(events.response)
49
+ ```
50
+ """
51
+
52
+ return await self.client.get(
53
+ "/api/eventcalendar/v1/api/events",
54
+ Events,
55
+ params={
56
+ "from": from_date.strftime("%Y-%m-%d"),
57
+ "to": to_date.strftime("%Y-%m-%d"),
58
+ "person_ids": contingent_guid,
59
+ "expand": "marks,homework,absence_reason_id,health_status,nonattendance_reason_id",
60
+ "source_types": "PLAN,AE,EC,EVENTS,AFISHA,ORGANIZER,OLYMPIAD,PROF",
61
+ },
62
+ )
@@ -1,10 +1,11 @@
1
1
  from datetime import datetime
2
+
2
3
  from schoolmospy.core.basic_client import BasicClient
3
4
  from schoolmospy.models.homeworks import Homeworks
4
5
 
5
6
 
6
7
  class HomeworkClient:
7
- def __init__(self, client: BasicClient):
8
+ def __init__(self, client: BasicClient) -> None:
8
9
  """
9
10
  Initialization the HomeworkClient instance.
10
11
 
@@ -43,13 +44,12 @@ class HomeworkClient:
43
44
  ```
44
45
  """
45
46
 
46
-
47
47
  return await self.client.get(
48
48
  "/api/family/web/v1/homeworks",
49
49
  Homeworks,
50
50
  params={
51
51
  "from": from_date.strftime("%Y-%m-%d"),
52
52
  "to": to_date.strftime("%Y-%m-%d"),
53
- "student_id": self.client.profile_id
54
- }
53
+ "student_id": self.client.profile_id,
54
+ },
55
55
  )
@@ -1,10 +1,11 @@
1
1
  from datetime import datetime
2
+
2
3
  from schoolmospy.core.basic_client import BasicClient
3
4
  from schoolmospy.models.marks import Marks
4
5
 
5
6
 
6
7
  class MarksClient:
7
- def __init__(self, client: BasicClient):
8
+ def __init__(self, client: BasicClient) -> None:
8
9
  self.client = client
9
10
 
10
11
  async def get(self, from_date: datetime, to_date: datetime) -> Marks:
@@ -41,6 +42,6 @@ class MarksClient:
41
42
  params={
42
43
  "from": from_date.strftime("%Y-%m-%d"),
43
44
  "to": to_date.strftime("%Y-%m-%d"),
44
- "student_id": self.client.profile_id
45
- }
45
+ "student_id": self.client.profile_id,
46
+ },
46
47
  )
@@ -0,0 +1,42 @@
1
+ from schoolmospy.core.basic_client import BasicClient
2
+ from schoolmospy.core.events_client import EventClient
3
+ from schoolmospy.core.homeworks_client import HomeworkClient
4
+ from schoolmospy.core.marks_client import MarksClient
5
+ from schoolmospy.models.profile import Profile
6
+ from schoolmospy.models.userinfo import Userinfo
7
+
8
+
9
+ class StudentClient(BasicClient):
10
+ def __init__(
11
+ self,
12
+ base_url: str = "https://school.mos.ru",
13
+ token: str | None = None,
14
+ profile_id: int | None = None,
15
+ profile_type: str = "student",
16
+ timeout: float = 15.0,
17
+ ) -> None:
18
+ super().__init__(base_url, token, profile_id, profile_type, timeout)
19
+ self.homeworks = HomeworkClient(self)
20
+ self.marks = MarksClient(self)
21
+ self.events = EventClient(self)
22
+
23
+ async def get_me(self) -> Profile:
24
+ """
25
+ Get the current user's profile information.
26
+
27
+ Returns:
28
+ Profile: Profile object containing user data
29
+ """
30
+ return await self.get(
31
+ "/api/family/web/v1/profile",
32
+ Profile,
33
+ )
34
+
35
+ async def userinfo(self) -> Userinfo:
36
+ """
37
+ Get basic user information from OAuth.
38
+
39
+ Returns:
40
+ Userinfo: Object with basic user information
41
+ """
42
+ return await self.get("/v1/oauth/userinfo", Userinfo)
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import BaseModel
6
+
7
+
8
+ class EventItem(BaseModel):
9
+ id: int
10
+ source_id: str
11
+ source: str
12
+ start_at: str
13
+ finish_at: str
14
+ cancelled: bool | None = None
15
+ lesson_type: str | None = None
16
+ course_lesson_type: Any | None = None
17
+ lesson_form: Any | None = None
18
+ replaced: bool | None = None
19
+ room_name: str | None
20
+ room_number: str | None
21
+ subject_id: int | None = None
22
+ subject_name: str
23
+ link_to_join: Any | None = None
24
+ health_status: Any
25
+ absence_reason_id: Any
26
+ nonattendance_reason_id: Any
27
+ homework: Any | None = None
28
+ marks: Any | None = None
29
+ is_missed_lesson: bool
30
+ esz_field_id: int | None = None
31
+ lesson_theme: Any | None = None
32
+
33
+
34
+ class Events(BaseModel):
35
+ total_count: int
36
+ response: list[EventItem]
37
+ errors: Any
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, List, Optional
3
+ from typing import Any
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
@@ -11,12 +11,12 @@ class Url(BaseModel):
11
11
 
12
12
 
13
13
  class Material(BaseModel):
14
- uuid: Optional[str]
14
+ uuid: str | None
15
15
  type: str
16
- selected_mode: Optional[str]
16
+ selected_mode: str | None
17
17
  type_name: str
18
- id: Optional[int]
19
- urls: List[Url]
18
+ id: int | None
19
+ urls: list[Url]
20
20
  description: Any
21
21
  content_type: Any
22
22
  title: str
@@ -24,14 +24,14 @@ class Material(BaseModel):
24
24
  action_name: str
25
25
 
26
26
 
27
- class PayloadItem(BaseModel):
27
+ class HomeworkItem(BaseModel):
28
28
  type: str
29
29
  description: str
30
- comments: List
31
- materials: List[Material]
30
+ comments: list
31
+ materials: list[Material]
32
32
  homework: str
33
33
  homework_entry_student_id: int
34
- attachments: List
34
+ attachments: list
35
35
  subject_id: int
36
36
  group_id: int
37
37
  date: str
@@ -49,4 +49,4 @@ class PayloadItem(BaseModel):
49
49
 
50
50
 
51
51
  class Homeworks(BaseModel):
52
- payload: List[PayloadItem]
52
+ payload: list[HomeworkItem]
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, List
3
+ from typing import Any
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
@@ -20,10 +20,10 @@ class Value(BaseModel):
20
20
  grade_system_type: str
21
21
 
22
22
 
23
- class PayloadItem(BaseModel):
23
+ class MarkItem(BaseModel):
24
24
  id: int
25
25
  value: str
26
- values: List[Value]
26
+ values: list[Value]
27
27
  comment: str
28
28
  weight: int
29
29
  point_date: Any
@@ -42,4 +42,4 @@ class PayloadItem(BaseModel):
42
42
 
43
43
 
44
44
  class Marks(BaseModel):
45
- payload: List[PayloadItem]
45
+ payload: list[MarkItem]
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, List, Optional
3
+ from typing import Any
4
4
 
5
5
  from pydantic import BaseModel
6
6
 
@@ -44,7 +44,7 @@ class Representative(BaseModel):
44
44
  middle_name: str
45
45
  type_id: int
46
46
  type: str
47
- email: Optional[str]
47
+ email: str | None
48
48
  phone: str
49
49
  snils: str
50
50
 
@@ -74,9 +74,9 @@ class Child(BaseModel):
74
74
  class_unit_id: int
75
75
  class_uid: str
76
76
  age: int
77
- groups: List[Group]
78
- representatives: List[Representative]
79
- sections: List[Section]
77
+ groups: list[Group]
78
+ representatives: list[Representative]
79
+ sections: list[Section]
80
80
  sudir_account_exists: bool
81
81
  sudir_login: Any
82
82
  is_legal_representative: bool
@@ -90,5 +90,5 @@ class Child(BaseModel):
90
90
 
91
91
  class Profile(BaseModel):
92
92
  profile: _Profile
93
- children: List[Child]
94
- hash: str
93
+ children: list[Child]
94
+ hash: str
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import List
4
-
5
3
  from pydantic import BaseModel, Field
6
4
 
7
5
 
@@ -23,7 +21,7 @@ class Class(BaseModel):
23
21
  parallel: Parallel
24
22
  organization: Organization
25
23
  education_stage_id: int
26
- staff_ids: List[int]
24
+ staff_ids: list[int]
27
25
 
28
26
 
29
27
  class ServiceType(BaseModel):
@@ -34,7 +32,7 @@ class ServiceType(BaseModel):
34
32
  class EducationItem(BaseModel):
35
33
  training_begin_at: str
36
34
  training_end_at: str
37
- class_: Class = Field(..., alias='class')
35
+ class_: Class = Field(..., alias="class")
38
36
  service_type: ServiceType
39
37
 
40
38
 
@@ -48,9 +46,9 @@ class Userinfo(BaseModel):
48
46
  phone: str
49
47
  name: str
50
48
  gender: str
51
- education: List[EducationItem]
52
- children: List
53
- agents: List[str]
49
+ education: list[EducationItem]
50
+ children: list
51
+ agents: list[str]
54
52
  mesh_id: str
55
53
  given_name: str
56
54
  family_name: str
@@ -0,0 +1,28 @@
1
+ from typing import Any
2
+
3
+
4
+ class APIError(Exception):
5
+ def __init__(
6
+ self,
7
+ message: str,
8
+ status_code: int | None = None,
9
+ response: Any | None = None,
10
+ ) -> None:
11
+ super().__init__(message)
12
+ self.status_code = status_code
13
+ self.response = response
14
+
15
+ def __str__(self) -> str:
16
+ return f"{self.__class__.__name__}: {self.args[0]} ({self.status_code}) {self.response}"
17
+
18
+
19
+ class AuthError(APIError): ...
20
+
21
+
22
+ class NotFoundError(APIError): ...
23
+
24
+
25
+ class ServerError(APIError): ...
26
+
27
+
28
+ class HTTPError(APIError): ...
@@ -1,28 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: schoolmospy
3
- Version: 0.0.2
4
- Summary: A lightweight async Python wrapper for school.mos.ru APIs
5
- License: GPL-3.0-only
6
- License-File: LICENSE
7
- Keywords: school,mos,api,education,async,wrapper,python
8
- Author: Ivan Kriventsev
9
- Author-email: xd2dd@icloud.com
10
- Requires-Python: >=3.12
11
- Classifier: Programming Language :: Python :: 3.12
12
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
13
- Classifier: Operating System :: OS Independent
14
- Classifier: Intended Audience :: Developers
15
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
- Requires-Dist: aiohttp (>=3.12.15,<4.0.0)
17
- Requires-Dist: pydantic (>=2.11.9,<3.0.0)
18
- Project-URL: Documentation, https://xd2dd.github.io/schoolmospy
19
- Project-URL: Homepage, https://github.com/xd2dd/schoolmospy
20
- Project-URL: Issues, https://github.com/xd2dd/schoolmospy/issues
21
- Description-Content-Type: text/markdown
22
-
23
- # 📕 SchoolMosPy
24
-
25
- SchoolMosPy - a lightweight Python wrapper for school.mos.py APIs.
26
-
27
-
28
-
@@ -1,5 +0,0 @@
1
- # 📕 SchoolMosPy
2
-
3
- SchoolMosPy - a lightweight Python wrapper for school.mos.py APIs.
4
-
5
-
@@ -1,41 +0,0 @@
1
- [project]
2
- name = "schoolmospy"
3
- version = "0.0.2"
4
- description = "A lightweight async Python wrapper for school.mos.ru APIs"
5
- authors = [{ name = "Ivan Kriventsev", email = "xd2dd@icloud.com" }]
6
- readme = "README.md"
7
- license = { text = "GPL-3.0-only" }
8
- requires-python = ">=3.12"
9
-
10
- keywords = ["school", "mos", "api", "education", "async", "wrapper", "python"]
11
-
12
- classifiers = [
13
- "Programming Language :: Python :: 3.12",
14
- "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
15
- "Operating System :: OS Independent",
16
- "Intended Audience :: Developers",
17
- "Topic :: Software Development :: Libraries :: Python Modules",
18
- ]
19
-
20
- dependencies = [
21
- "aiohttp >=3.12.15,<4.0.0",
22
- "pydantic >=2.11.9,<3.0.0",
23
- ]
24
-
25
- packages = [{ include = "schoolmospy" }]
26
-
27
- [project.urls]
28
- "Homepage" = "https://github.com/xd2dd/schoolmospy"
29
- "Documentation" = "https://xd2dd.github.io/schoolmospy"
30
- "Issues" = "https://github.com/xd2dd/schoolmospy/issues"
31
-
32
-
33
- [build-system]
34
- requires = ["poetry-core>=2.0.0,<3.0.0"]
35
- build-backend = "poetry.core.masonry.api"
36
-
37
- [tool.poetry.group.dev.dependencies]
38
- ruff = "^0.13.2"
39
- mypy = "^1.18.2"
40
- black = "^25.9.0"
41
- isort = "^6.0.1"
@@ -1,2 +0,0 @@
1
- from schoolmospy.core.student_client import StudentClient
2
- from schoolmospy.core.basic_client import BasicClient
@@ -1,31 +0,0 @@
1
- from typing import Optional
2
- from schoolmospy.core.basic_client import BasicClient
3
- from schoolmospy.models.profile import Profile
4
- from schoolmospy.models.userinfo import Userinfo
5
- from schoolmospy.core.homeworks_client import HomeworkClient
6
- from schoolmospy.core.marks_client import MarksClient
7
-
8
-
9
- class StudentClient(BasicClient):
10
- def __init__(self,
11
- base_url: str = "https://school.mos.ru",
12
- token: Optional[str] = None,
13
- profile_id: Optional[int] = None,
14
- profile_type: str = "student",
15
- timeout: float = 15.0):
16
- super().__init__(base_url, token, profile_id, profile_type, timeout)
17
- self.homeworks = HomeworkClient(self)
18
- self.marks = MarksClient(self)
19
-
20
-
21
- async def get_me(self) -> Profile:
22
- return await self.get(
23
- "/api/family/web/v1/profile",
24
- Profile,
25
- )
26
-
27
- async def userinfo(self) -> Userinfo:
28
- return await self.get(
29
- "/v1/oauth/userinfo",
30
- Userinfo
31
- )
@@ -1,26 +0,0 @@
1
- from typing import Optional, Any
2
-
3
-
4
- class APIError(Exception):
5
- def __init__(self, message: str, status_code: Optional[int] = None, response: Optional[Any] = None):
6
- super().__init__(message)
7
- self.status_code = status_code
8
- self.response = response
9
-
10
- def __str__(self):
11
- return f"{self.__class__.__name__}: {self.args[0]} (status={self.status_code})"
12
-
13
-
14
- class AuthError(APIError):
15
- ...
16
-
17
- class NotFoundError(APIError):
18
- ...
19
-
20
-
21
- class ServerError(APIError):
22
- ...
23
-
24
-
25
- class HTTPError(APIError):
26
- ...
File without changes