schoolmospy 0.2.1__tar.gz → 0.2.3__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 (23) hide show
  1. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/PKG-INFO +4 -7
  2. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/README.md +4 -6
  3. schoolmospy-0.2.3/pyproject.toml +91 -0
  4. schoolmospy-0.2.3/schoolmospy/__init__.py +4 -0
  5. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/schoolmospy/core/basic_client.py +39 -15
  6. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/schoolmospy/core/events_client.py +9 -8
  7. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/schoolmospy/core/homeworks_client.py +4 -4
  8. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/schoolmospy/core/marks_client.py +4 -3
  9. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/schoolmospy/core/student_client.py +12 -15
  10. schoolmospy-0.2.3/schoolmospy/models/events.py +37 -0
  11. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/schoolmospy/models/homeworks.py +9 -9
  12. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/schoolmospy/models/marks.py +3 -3
  13. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/schoolmospy/models/profile.py +7 -7
  14. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/schoolmospy/models/userinfo.py +5 -7
  15. schoolmospy-0.2.3/schoolmospy/utils/exceptions.py +28 -0
  16. schoolmospy-0.2.3/schoolmospy/utils/logging.py +0 -0
  17. schoolmospy-0.2.1/pyproject.toml +0 -42
  18. schoolmospy-0.2.1/schoolmospy/__init__.py +0 -2
  19. schoolmospy-0.2.1/schoolmospy/models/events.py +0 -37
  20. schoolmospy-0.2.1/schoolmospy/utils/exceptions.py +0 -26
  21. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/LICENSE +0 -0
  22. {schoolmospy-0.2.1 → schoolmospy-0.2.3}/schoolmospy/__main__.py +0 -0
  23. /schoolmospy-0.2.1/schoolmospy/utils/logging.py → /schoolmospy-0.2.3/schoolmospy/models/enums/usertype.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schoolmospy
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: A lightweight async Python wrapper for school.mos.ru APIs
5
5
  License: GPL-3.0-only
6
6
  License-File: LICENSE
@@ -67,19 +67,19 @@ async def main():
67
67
  print(f"Name: {profile.name}")
68
68
 
69
69
  # Get marks
70
- marks = await client.marks.get_marks(
70
+ marks = await client.marks.get(
71
71
  from_date="2024-01-01",
72
72
  to_date="2024-12-31"
73
73
  )
74
74
 
75
75
  # Get homeworks
76
- homeworks = await client.homeworks.get_homeworks(
76
+ homeworks = await client.homeworks.get(
77
77
  from_date="2024-01-01",
78
78
  to_date="2024-12-31"
79
79
  )
80
80
 
81
81
  # Get events/schedule
82
- events = await client.events.get_events(
82
+ events = await client.events.get(
83
83
  from_date="2024-01-01",
84
84
  to_date="2024-12-31"
85
85
  )
@@ -103,6 +103,3 @@ This project is licensed under the GPL-3.0 License. See the [LICENSE](LICENSE) f
103
103
  Ivan Kriventsev - [xd2dd@icloud.com](mailto:xd2dd@icloud.com)
104
104
 
105
105
  ---
106
-
107
- ⭐ If you like this project, please give it a star on GitHub!
108
-
@@ -44,19 +44,19 @@ async def main():
44
44
  print(f"Name: {profile.name}")
45
45
 
46
46
  # Get marks
47
- marks = await client.marks.get_marks(
47
+ marks = await client.marks.get(
48
48
  from_date="2024-01-01",
49
49
  to_date="2024-12-31"
50
50
  )
51
51
 
52
52
  # Get homeworks
53
- homeworks = await client.homeworks.get_homeworks(
53
+ homeworks = await client.homeworks.get(
54
54
  from_date="2024-01-01",
55
55
  to_date="2024-12-31"
56
56
  )
57
57
 
58
58
  # Get events/schedule
59
- events = await client.events.get_events(
59
+ events = await client.events.get(
60
60
  from_date="2024-01-01",
61
61
  to_date="2024-12-31"
62
62
  )
@@ -79,6 +79,4 @@ This project is licensed under the GPL-3.0 License. See the [LICENSE](LICENSE) f
79
79
 
80
80
  Ivan Kriventsev - [xd2dd@icloud.com](mailto:xd2dd@icloud.com)
81
81
 
82
- ---
83
-
84
- ⭐ If you like this project, please give it a star on GitHub!
82
+ ---
@@ -0,0 +1,91 @@
1
+ [project]
2
+ name = "schoolmospy"
3
+ version = "0.2.3"
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,23 +1,26 @@
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
19
21
  self.profile_type = profile_type
20
22
  self.timeout = timeout
23
+ self.extra_headers: dict[str, str] = {}
21
24
 
22
25
  @property
23
26
  def headers(self) -> dict[str, str]:
@@ -33,10 +36,15 @@ class BasicClient:
33
36
  headers["Profile-Id"] = str(self.profile_id)
34
37
  if self.profile_type:
35
38
  headers["Profile-Type"] = self.profile_type
36
- headers["X-Mes-Role"] = self.profile_type
39
+ if self.extra_headers:
40
+ headers.update(self.extra_headers)
37
41
  return headers
38
42
 
39
- async def _handle_response(self, response: httpx.Response, response_model: Optional[Type[BaseModel]] = None):
43
+ async def _handle_response(
44
+ self,
45
+ response: httpx.Response,
46
+ response_model: type[BaseModel] | None = None,
47
+ ) -> Any:
40
48
  if response.is_success:
41
49
  if response_model:
42
50
  return response_model.model_validate(response.json())
@@ -47,25 +55,41 @@ class BasicClient:
47
55
 
48
56
  if status == 401:
49
57
  raise AuthError("Unauthorized or invalid token", status, text)
50
- elif status == 404:
58
+ if status == 404:
51
59
  raise NotFoundError("Resource not found", status, text)
52
- elif status >= 500:
60
+ if status >= 500:
53
61
  raise ServerError("Server error", status, text)
54
- else:
55
- raise HTTPError(f"Unexpected response ({status})", status, text)
62
+ raise HTTPError(f"Unexpected response ({status})", status, text)
56
63
 
57
- async def get(self, endpoint: str, response_model: Optional[Type[BaseModel]] = None, **kwargs: Any):
64
+ async def get(
65
+ self,
66
+ endpoint: str,
67
+ response_model: type[BaseModel] | None = None,
68
+ **kwargs: Any,
69
+ ) -> Any:
58
70
  url = f"{self.base_url}/{endpoint.lstrip('/')}"
59
- async with httpx.AsyncClient(headers=self.headers, timeout=self.timeout) as client:
71
+ headers = self.headers
72
+ if "headers" in kwargs:
73
+ headers = {**headers, **kwargs.pop("headers")}
74
+ async with httpx.AsyncClient(headers=headers, timeout=self.timeout) as client:
60
75
  try:
61
76
  resp = await client.get(url, **kwargs)
62
77
  except httpx.RequestError as e:
63
78
  raise APIError(f"Request failed: {e}") from e
64
79
  return await self._handle_response(resp, response_model)
65
80
 
66
- async def post(self, endpoint: str, data: Any = None, response_model: Optional[Type[BaseModel]] = None, **kwargs: Any):
81
+ async def post(
82
+ self,
83
+ endpoint: str,
84
+ data: Any = None,
85
+ response_model: type[BaseModel] | None = None,
86
+ **kwargs: Any,
87
+ ) -> Any:
67
88
  url = f"{self.base_url}/{endpoint.lstrip('/')}"
68
- async with httpx.AsyncClient(headers=self.headers, timeout=self.timeout) as client:
89
+ headers = self.headers
90
+ if "headers" in kwargs:
91
+ headers = {**headers, **kwargs.pop("headers")}
92
+ async with httpx.AsyncClient(headers=headers, timeout=self.timeout) as client:
69
93
  try:
70
94
  resp = await client.post(url, json=data, **kwargs)
71
95
  except httpx.RequestError as e:
@@ -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.events import Events
4
5
 
5
6
 
6
7
  class EventClient:
7
- def __init__(self, client: BasicClient):
8
+ def __init__(self, client: BasicClient) -> None:
8
9
  """
9
10
  Initialization the EventClient instance.
10
11
 
@@ -14,7 +15,7 @@ class EventClient:
14
15
  """
15
16
  self.client = client
16
17
 
17
- async def get(self, from_date: datetime, to_date: datetime, contingent_guid: str = None) -> Events:
18
+ async def get(self, from_date: datetime, to_date: datetime, contingent_guid: str) -> Events:
18
19
  """
19
20
  Method for getting events done within a certain period of time
20
21
 
@@ -36,7 +37,7 @@ class EventClient:
36
37
  from schoolmospy.clients.student_client import StudentClient
37
38
 
38
39
  client = StudentClient(token="YOUR_TOKEN", profile_id=17234613)
39
-
40
+
40
41
  me = await client.get_me()
41
42
 
42
43
  events = await client.events.get(
@@ -48,15 +49,15 @@ class EventClient:
48
49
  ```
49
50
  """
50
51
 
51
-
52
52
  return await self.client.get(
53
53
  "/api/eventcalendar/v1/api/events",
54
54
  Events,
55
55
  params={
56
- "from": from_date.strftime("%Y-%m-%d"),
57
- "to": to_date.strftime("%Y-%m-%d"),
56
+ "begin_date": from_date.strftime("%Y-%m-%d"),
57
+ "end_date": to_date.strftime("%Y-%m-%d"),
58
58
  "person_ids": contingent_guid,
59
59
  "expand": "marks,homework,absence_reason_id,health_status,nonattendance_reason_id",
60
- "source_types": "PLAN,AE,EC,EVENTS,AFISHA,ORGANIZER,OLYMPIAD,PROF"
61
- }
60
+ "source_types": "PLAN,AE,EC,EVENTS,AFISHA,ORGANIZER,OLYMPIAD,PROF",
61
+ },
62
+ headers={"X-Mes-Role": "student"},
62
63
  )
@@ -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
  )
@@ -1,25 +1,25 @@
1
- from typing import Optional
2
1
  from schoolmospy.core.basic_client import BasicClient
3
- from schoolmospy.models.profile import Profile
4
- from schoolmospy.models.userinfo import Userinfo
2
+ from schoolmospy.core.events_client import EventClient
5
3
  from schoolmospy.core.homeworks_client import HomeworkClient
6
4
  from schoolmospy.core.marks_client import MarksClient
7
- from schoolmospy.core.events_client import EventClient
5
+ from schoolmospy.models.profile import Profile
6
+ from schoolmospy.models.userinfo import Userinfo
8
7
 
9
8
 
10
9
  class StudentClient(BasicClient):
11
- def __init__(self,
12
- base_url: str = "https://school.mos.ru",
13
- token: Optional[str] = None,
14
- profile_id: Optional[int] = None,
15
- profile_type: str = "student",
16
- timeout: float = 15.0):
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:
17
18
  super().__init__(base_url, token, profile_id, profile_type, timeout)
18
19
  self.homeworks = HomeworkClient(self)
19
20
  self.marks = MarksClient(self)
20
21
  self.events = EventClient(self)
21
22
 
22
-
23
23
  async def get_me(self) -> Profile:
24
24
  """
25
25
  Get the current user's profile information.
@@ -39,7 +39,4 @@ class StudentClient(BasicClient):
39
39
  Returns:
40
40
  Userinfo: Object with basic user information
41
41
  """
42
- return await self.get(
43
- "/v1/oauth/userinfo",
44
- Userinfo
45
- )
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
@@ -27,11 +27,11 @@ class Material(BaseModel):
27
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 HomeworkItem(BaseModel):
49
49
 
50
50
 
51
51
  class Homeworks(BaseModel):
52
- payload: List[HomeworkItem]
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
 
@@ -23,7 +23,7 @@ class Value(BaseModel):
23
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 MarkItem(BaseModel):
42
42
 
43
43
 
44
44
  class Marks(BaseModel):
45
- payload: List[MarkItem]
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): ...
File without changes
@@ -1,42 +0,0 @@
1
- [project]
2
- name = "schoolmospy"
3
- version = "0.2.1"
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"
@@ -1,2 +0,0 @@
1
- from schoolmospy.core.student_client import StudentClient
2
- from schoolmospy.core.basic_client import BasicClient
@@ -1,37 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, List, Optional
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: Optional[bool] = None
15
- lesson_type: Optional[str] = None
16
- course_lesson_type: Optional[Any] = None
17
- lesson_form: Optional[Any] = None
18
- replaced: Optional[bool] = None
19
- room_name: Optional[str]
20
- room_number: Optional[str]
21
- subject_id: Optional[int] = None
22
- subject_name: str
23
- link_to_join: Optional[Any] = None
24
- health_status: Any
25
- absence_reason_id: Any
26
- nonattendance_reason_id: Any
27
- homework: Optional[Any] = None
28
- marks: Optional[Any] = None
29
- is_missed_lesson: bool
30
- esz_field_id: Optional[int] = None
31
- lesson_theme: Optional[Any] = None
32
-
33
-
34
- class Events(BaseModel):
35
- total_count: int
36
- response: List[EventItem]
37
- errors: Any
@@ -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