42api 0.1.0__tar.gz → 0.1.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.
Files changed (80) hide show
  1. {42api-0.1.0 → 42api-0.1.2}/.github/workflows/ci.yml +1 -1
  2. 42api-0.1.2/.github/workflows/publish.yml +44 -0
  3. {42api-0.1.0 → 42api-0.1.2}/.gitignore +4 -1
  4. {42api-0.1.0 → 42api-0.1.2}/PKG-INFO +11 -8
  5. {42api-0.1.0 → 42api-0.1.2}/README.md +8 -6
  6. {42api-0.1.0 → 42api-0.1.2}/pyproject.toml +3 -2
  7. {42api-0.1.0 → 42api-0.1.2}/src/intra42/__init__.py +12 -2
  8. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_async/client.py +10 -0
  9. 42api-0.1.2/src/intra42/_async/resources/accreditations.py +16 -0
  10. 42api-0.1.2/src/intra42/_async/resources/cursus.py +23 -0
  11. 42api-0.1.2/src/intra42/_async/resources/exams.py +23 -0
  12. 42api-0.1.2/src/intra42/_async/resources/projects.py +19 -0
  13. 42api-0.1.2/src/intra42/_async/resources/projects_users.py +22 -0
  14. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_async/resources/users.py +4 -0
  15. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_sync/client.py +10 -0
  16. 42api-0.1.2/src/intra42/_sync/resources/accreditations.py +22 -0
  17. 42api-0.1.2/src/intra42/_sync/resources/cursus.py +29 -0
  18. 42api-0.1.2/src/intra42/_sync/resources/exams.py +27 -0
  19. 42api-0.1.2/src/intra42/_sync/resources/projects.py +25 -0
  20. 42api-0.1.2/src/intra42/_sync/resources/projects_users.py +26 -0
  21. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_sync/resources/users.py +4 -0
  22. 42api-0.1.2/src/intra42/models/accreditation.py +29 -0
  23. 42api-0.1.2/src/intra42/models/cursus.py +41 -0
  24. 42api-0.1.2/src/intra42/models/exam.py +32 -0
  25. 42api-0.1.2/src/intra42/models/project.py +106 -0
  26. 42api-0.1.2/src/intra42/models/project_user.py +74 -0
  27. {42api-0.1.0 → 42api-0.1.2}/src/intra42/models/user.py +6 -0
  28. 42api-0.1.2/tests/integration/resources/test_resources_accreditations.py +78 -0
  29. 42api-0.1.2/tests/integration/resources/test_resources_cursus.py +81 -0
  30. 42api-0.1.2/tests/integration/resources/test_resources_exams.py +159 -0
  31. 42api-0.1.2/tests/integration/resources/test_resources_projects.py +193 -0
  32. 42api-0.1.2/tests/integration/resources/test_resources_projects_users.py +176 -0
  33. {42api-0.1.0 → 42api-0.1.2}/tests/integration/test_pagination_walk.py +3 -3
  34. {42api-0.1.0 → 42api-0.1.2}/tests/unit/test_public_api.py +10 -0
  35. {42api-0.1.0 → 42api-0.1.2}/uv.lock +2 -2
  36. {42api-0.1.0 → 42api-0.1.2}/.pre-commit-config.yaml +0 -0
  37. {42api-0.1.0 → 42api-0.1.2}/LICENSE +0 -0
  38. {42api-0.1.0 → 42api-0.1.2}/scripts/unasync_generate.py +0 -0
  39. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_async/__init__.py +0 -0
  40. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_async/query.py +0 -0
  41. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_async/resources/__init__.py +0 -0
  42. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_async/resources/base.py +0 -0
  43. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_async/resources/campus_users.py +0 -0
  44. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_async/resources/campuses.py +0 -0
  45. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_async/resources/events.py +0 -0
  46. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_async/resources/locations.py +0 -0
  47. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_auth.py +0 -0
  48. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_config.py +0 -0
  49. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_pagination.py +0 -0
  50. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_query_params.py +0 -0
  51. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_rate_limit.py +0 -0
  52. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_sync/__init__.py +0 -0
  53. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_sync/query.py +0 -0
  54. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_sync/resources/__init__.py +0 -0
  55. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_sync/resources/base.py +0 -0
  56. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_sync/resources/campus_users.py +0 -0
  57. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_sync/resources/campuses.py +0 -0
  58. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_sync/resources/events.py +0 -0
  59. {42api-0.1.0 → 42api-0.1.2}/src/intra42/_sync/resources/locations.py +0 -0
  60. {42api-0.1.0 → 42api-0.1.2}/src/intra42/exceptions.py +0 -0
  61. {42api-0.1.0 → 42api-0.1.2}/src/intra42/models/__init__.py +0 -0
  62. {42api-0.1.0 → 42api-0.1.2}/src/intra42/models/base.py +0 -0
  63. {42api-0.1.0 → 42api-0.1.2}/src/intra42/models/campus.py +0 -0
  64. {42api-0.1.0 → 42api-0.1.2}/src/intra42/models/campus_user.py +0 -0
  65. {42api-0.1.0 → 42api-0.1.2}/src/intra42/models/event.py +0 -0
  66. {42api-0.1.0 → 42api-0.1.2}/src/intra42/models/location.py +0 -0
  67. {42api-0.1.0 → 42api-0.1.2}/src/intra42/py.typed +0 -0
  68. {42api-0.1.0 → 42api-0.1.2}/tests/conftest.py +0 -0
  69. {42api-0.1.0 → 42api-0.1.2}/tests/integration/resources/test_resources_campus_users.py +0 -0
  70. {42api-0.1.0 → 42api-0.1.2}/tests/integration/resources/test_resources_campuses.py +0 -0
  71. {42api-0.1.0 → 42api-0.1.2}/tests/integration/resources/test_resources_events.py +0 -0
  72. {42api-0.1.0 → 42api-0.1.2}/tests/integration/resources/test_resources_locations.py +0 -0
  73. {42api-0.1.0 → 42api-0.1.2}/tests/integration/test_client_integration.py +0 -0
  74. {42api-0.1.0 → 42api-0.1.2}/tests/integration/test_nested_access.py +0 -0
  75. {42api-0.1.0 → 42api-0.1.2}/tests/unit/test_auth.py +0 -0
  76. {42api-0.1.0 → 42api-0.1.2}/tests/unit/test_models_campus.py +0 -0
  77. {42api-0.1.0 → 42api-0.1.2}/tests/unit/test_models_user.py +0 -0
  78. {42api-0.1.0 → 42api-0.1.2}/tests/unit/test_pagination.py +0 -0
  79. {42api-0.1.0 → 42api-0.1.2}/tests/unit/test_query_builder.py +0 -0
  80. {42api-0.1.0 → 42api-0.1.2}/tests/unit/test_rate_limit.py +0 -0
@@ -10,7 +10,7 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
  strategy:
12
12
  matrix:
13
- python-version: ["3.10", "3.11", "3.12", "3.13"]
13
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
14
14
  steps:
15
15
  - uses: actions/checkout@v4
16
16
 
@@ -0,0 +1,44 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ environment:
16
+ name: pypi
17
+ url: https://pypi.org/project/42api/
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v3
23
+ with:
24
+ version: "latest"
25
+
26
+ - name: Set up Python
27
+ run: uv python install 3.12
28
+
29
+ - name: Install dependencies
30
+ run: uv sync --all-extras
31
+
32
+ - name: Re-run checks before publishing
33
+ run: |
34
+ uv run ruff check .
35
+ uv run ruff format --check .
36
+ uv run python scripts/unasync_generate.py --check
37
+ uv run mypy
38
+ uv run pytest
39
+
40
+ - name: Build
41
+ run: uv build
42
+
43
+ - name: Publish to PyPI
44
+ run: uv publish --trusted-publishing always
@@ -218,4 +218,7 @@ __marimo__/
218
218
  # Streamlit
219
219
  .streamlit/secrets.toml
220
220
 
221
- assets/
221
+ assets/
222
+
223
+ # agent
224
+ .claude
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: 42api
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Object-oriented Python client for the 42 School API (api.intra.42.fr)
5
5
  Project-URL: Homepage, https://github.com/Shadowaker/42api
6
6
  Author-email: Dan <danyridolfo98@gmail.com>
7
7
  License: MIT
8
8
  License-File: LICENSE
9
- Keywords: 42,42school,api,client,intra
9
+ Keywords: 42,42school,42staff,api,client,intra,intra42
10
10
  Classifier: Development Status :: 3 - Alpha
11
11
  Classifier: Framework :: AsyncIO
12
12
  Classifier: Intended Audience :: Developers
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
20
21
  Classifier: Typing :: Typed
21
22
  Requires-Python: >=3.10
22
23
  Requires-Dist: httpx>=0.27
@@ -54,7 +55,7 @@ with Client(client_id="...", client_secret="...") as client:
54
55
  user = client.users.get("jdoe")
55
56
  print(user.login, user.email)
56
57
 
57
- for user in client.users.filter(campus_id=1).sort("-level"):
58
+ for user in client.users.filter(primary_campus_id=1).sort("-pool_year"):
58
59
  print(user.login)
59
60
  ```
60
61
 
@@ -68,7 +69,7 @@ from intra42 import AsyncClient
68
69
  async def main() -> None:
69
70
  async with AsyncClient(client_id="...", client_secret="...") as client:
70
71
  user = await client.users.get("jdoe")
71
- async for user in client.users.filter(campus_id=1).sort("-level"):
72
+ async for user in client.users.filter(primary_campus_id=1).sort("-pool_year"):
72
73
  print(user.login)
73
74
 
74
75
 
@@ -107,10 +108,12 @@ and `NetworkError` (connection/timeout failures).
107
108
 
108
109
  ## Status
109
110
 
110
- Early-stage: currently covers `users`, `campuses`, `campus_users`,
111
- `events`, and `locations` (including its `graph` analytics endpoint). More
112
- resources are added incrementally on top of the same client/model/
113
- query-builder pattern.
111
+ Early-stage: currently covers `users`, `accreditations`, `campuses`,
112
+ `campus_users`, `cursus`, `events`, `exams`, `locations`, `projects`, and
113
+ `projects_users` (`exams`, `locations`, and `projects_users` each include
114
+ their `graph` analytics endpoint). More resources are added incrementally
115
+ on top of the same client/model/query-builder pattern.
116
+ The api with restricted access are not implemented currently.
114
117
 
115
118
  ## Development
116
119
 
@@ -29,7 +29,7 @@ with Client(client_id="...", client_secret="...") as client:
29
29
  user = client.users.get("jdoe")
30
30
  print(user.login, user.email)
31
31
 
32
- for user in client.users.filter(campus_id=1).sort("-level"):
32
+ for user in client.users.filter(primary_campus_id=1).sort("-pool_year"):
33
33
  print(user.login)
34
34
  ```
35
35
 
@@ -43,7 +43,7 @@ from intra42 import AsyncClient
43
43
  async def main() -> None:
44
44
  async with AsyncClient(client_id="...", client_secret="...") as client:
45
45
  user = await client.users.get("jdoe")
46
- async for user in client.users.filter(campus_id=1).sort("-level"):
46
+ async for user in client.users.filter(primary_campus_id=1).sort("-pool_year"):
47
47
  print(user.login)
48
48
 
49
49
 
@@ -82,10 +82,12 @@ and `NetworkError` (connection/timeout failures).
82
82
 
83
83
  ## Status
84
84
 
85
- Early-stage: currently covers `users`, `campuses`, `campus_users`,
86
- `events`, and `locations` (including its `graph` analytics endpoint). More
87
- resources are added incrementally on top of the same client/model/
88
- query-builder pattern.
85
+ Early-stage: currently covers `users`, `accreditations`, `campuses`,
86
+ `campus_users`, `cursus`, `events`, `exams`, `locations`, `projects`, and
87
+ `projects_users` (`exams`, `locations`, and `projects_users` each include
88
+ their `graph` analytics endpoint). More resources are added incrementally
89
+ on top of the same client/model/query-builder pattern.
90
+ The api with restricted access are not implemented currently.
89
91
 
90
92
  ## Development
91
93
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "42api"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "Object-oriented Python client for the 42 School API (api.intra.42.fr)"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -18,10 +18,11 @@ classifiers = [
18
18
  "Programming Language :: Python :: 3.11",
19
19
  "Programming Language :: Python :: 3.12",
20
20
  "Programming Language :: Python :: 3.13",
21
+ "Programming Language :: Python :: 3.14",
21
22
  "Typing :: Typed",
22
23
  "Framework :: AsyncIO",
23
24
  ]
24
- keywords = ["42", "42school", "api", "client", "intra"]
25
+ keywords = ["42", "42school", "api", "client", "intra", "intra42", "42staff"]
25
26
 
26
27
  [project.urls]
27
28
  Homepage = "https://github.com/Shadowaker/42api"
@@ -4,7 +4,7 @@
4
4
 
5
5
  with Client(client_id, client_secret) as client:
6
6
  user = client.users.get("jdoe")
7
- for user in client.users.filter(campus_id=1).sort("-level"):
7
+ for user in client.users.filter(primary_campus_id=1).sort("-pool_year"):
8
8
  ...
9
9
 
10
10
  An async client with the same interface is available as ``AsyncClient``.
@@ -25,10 +25,15 @@ from .exceptions import (
25
25
  ServerError,
26
26
  ValidationError,
27
27
  )
28
+ from .models.accreditation import Accreditation
28
29
  from .models.campus import Campus
29
30
  from .models.campus_user import CampusUser
31
+ from .models.cursus import Cursus
30
32
  from .models.event import Event
33
+ from .models.exam import Exam
31
34
  from .models.location import Location
35
+ from .models.project import Project
36
+ from .models.project_user import ProjectUser
32
37
  from .models.user import User
33
38
 
34
39
  __all__ = [
@@ -36,10 +41,15 @@ __all__ = [
36
41
  "Client",
37
42
  "ClientConfig",
38
43
  "User",
44
+ "Accreditation",
39
45
  "Campus",
40
46
  "CampusUser",
47
+ "Cursus",
41
48
  "Event",
49
+ "Exam",
42
50
  "Location",
51
+ "Project",
52
+ "ProjectUser",
43
53
  "FortyTwoAPIError",
44
54
  "AuthenticationError",
45
55
  "PermissionDeniedError",
@@ -50,4 +60,4 @@ __all__ = [
50
60
  "NetworkError",
51
61
  ]
52
62
 
53
- __version__ = "0.1.0"
63
+ __version__ = "0.1.2"
@@ -12,10 +12,15 @@ from .._auth import TokenManager
12
12
  from .._config import DEFAULT_BASE_URL, ClientConfig
13
13
  from .._rate_limit import RateLimiter
14
14
  from ..exceptions import NetworkError, raise_for_status
15
+ from .resources.accreditations import AsyncAccreditationsResource
15
16
  from .resources.campus_users import AsyncCampusUsersResource
16
17
  from .resources.campuses import AsyncCampusesResource
18
+ from .resources.cursus import AsyncCursusResource
17
19
  from .resources.events import AsyncEventsResource
20
+ from .resources.exams import AsyncExamsResource
18
21
  from .resources.locations import AsyncLocationsResource
22
+ from .resources.projects import AsyncProjectsResource
23
+ from .resources.projects_users import AsyncProjectUsersResource
19
24
  from .resources.users import AsyncUsersResource
20
25
 
21
26
 
@@ -57,10 +62,15 @@ class AsyncClient:
57
62
  )
58
63
 
59
64
  self.users = AsyncUsersResource(self)
65
+ self.accreditations = AsyncAccreditationsResource(self)
60
66
  self.campuses = AsyncCampusesResource(self)
61
67
  self.campus_users = AsyncCampusUsersResource(self)
68
+ self.cursus = AsyncCursusResource(self)
62
69
  self.events = AsyncEventsResource(self)
70
+ self.exams = AsyncExamsResource(self)
63
71
  self.locations = AsyncLocationsResource(self)
72
+ self.projects = AsyncProjectsResource(self)
73
+ self.projects_users = AsyncProjectUsersResource(self)
64
74
 
65
75
  @property
66
76
  def base_url(self) -> str:
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from ...models.accreditation import Accreditation
4
+ from .base import AsyncResource
5
+
6
+
7
+ class AsyncAccreditationsResource(AsyncResource[Accreditation]):
8
+ path = "/accreditations"
9
+ model = Accreditation
10
+
11
+ def _bind_relations(self, instance: Accreditation) -> None:
12
+ # Referenced in the users apidoc page: GET /accreditations/:id/users.
13
+ instance._bind_relation(
14
+ "users",
15
+ lambda: self._client.users._queryset(f"/accreditations/{instance.id}/users"),
16
+ )
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from ...models.cursus import Cursus
4
+ from .base import AsyncResource
5
+
6
+
7
+ class AsyncCursusResource(AsyncResource[Cursus]):
8
+ path = "/cursus"
9
+ model = Cursus
10
+
11
+ def _bind_relations(self, instance: Cursus) -> None:
12
+ instance._bind_relation(
13
+ "users",
14
+ lambda: self._client.users._queryset(f"/cursus/{instance.id}/users"),
15
+ )
16
+ instance._bind_relation(
17
+ "events",
18
+ lambda: self._client.events._queryset(f"/cursus/{instance.id}/events"),
19
+ )
20
+ instance._bind_relation(
21
+ "projects",
22
+ lambda: self._client.projects._queryset(f"/cursus/{instance.id}/projects"),
23
+ )
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from ...models.exam import Exam
4
+ from .base import AsyncResource
5
+
6
+
7
+ class AsyncExamsResource(AsyncResource[Exam]):
8
+ path = "/exams"
9
+ model = Exam
10
+
11
+ async def graph(
12
+ self, *, field: str | None = None, interval: str | None = None
13
+ ) -> dict[str, int]:
14
+ """Grouped temporal counts (``GET /exams/graph...``)."""
15
+ path = "/exams/graph"
16
+ if interval is not None and field is None:
17
+ field = "created_at"
18
+ if field is not None:
19
+ path += f"/on/{field}"
20
+ if interval is not None:
21
+ path += f"/by/{interval}"
22
+ response = await self._client.request("GET", path)
23
+ return response.json()
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from ...models.project import Project
4
+ from .base import AsyncResource
5
+
6
+
7
+ class AsyncProjectsResource(AsyncResource[Project]):
8
+ path = "/projects"
9
+ model = Project
10
+
11
+ def _bind_relations(self, instance: Project) -> None:
12
+ # Referenced in the projects_users apidoc page:
13
+ # GET /projects/:id/projects_users.
14
+ instance._bind_relation(
15
+ "projects_users",
16
+ lambda: self._client.projects_users._queryset(
17
+ f"/projects/{instance.id}/projects_users"
18
+ ),
19
+ )
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from ...models.project_user import ProjectUser
4
+ from .base import AsyncResource
5
+
6
+
7
+ class AsyncProjectUsersResource(AsyncResource[ProjectUser]):
8
+ path = "/projects_users"
9
+ model = ProjectUser
10
+
11
+ async def graph(
12
+ self, *, field: str | None = None, interval: str | None = None
13
+ ) -> dict[str, int]:
14
+ path = "/projects_users/graph"
15
+ if interval is not None and field is None:
16
+ field = "created_at"
17
+ if field is not None:
18
+ path += f"/on/{field}"
19
+ if interval is not None:
20
+ path += f"/by/{interval}"
21
+ response = await self._client.request("GET", path)
22
+ return response.json()
@@ -21,3 +21,7 @@ class AsyncUsersResource(AsyncResource[User]):
21
21
  "locations",
22
22
  lambda: self._client.locations._queryset(f"/users/{instance.id}/locations"),
23
23
  )
24
+ instance._bind_relation(
25
+ "projects_users",
26
+ lambda: self._client.projects_users._queryset(f"/users/{instance.id}/projects_users"),
27
+ )
@@ -18,10 +18,15 @@ from .._auth import TokenManager
18
18
  from .._config import DEFAULT_BASE_URL, ClientConfig
19
19
  from .._rate_limit import RateLimiter
20
20
  from ..exceptions import NetworkError, raise_for_status
21
+ from .resources.accreditations import AccreditationsResource
21
22
  from .resources.campus_users import CampusUsersResource
22
23
  from .resources.campuses import CampusesResource
24
+ from .resources.cursus import CursusResource
23
25
  from .resources.events import EventsResource
26
+ from .resources.exams import ExamsResource
24
27
  from .resources.locations import LocationsResource
28
+ from .resources.projects import ProjectsResource
29
+ from .resources.projects_users import ProjectUsersResource
25
30
  from .resources.users import UsersResource
26
31
 
27
32
 
@@ -63,10 +68,15 @@ class Client:
63
68
  )
64
69
 
65
70
  self.users = UsersResource(self)
71
+ self.accreditations = AccreditationsResource(self)
66
72
  self.campuses = CampusesResource(self)
67
73
  self.campus_users = CampusUsersResource(self)
74
+ self.cursus = CursusResource(self)
68
75
  self.events = EventsResource(self)
76
+ self.exams = ExamsResource(self)
69
77
  self.locations = LocationsResource(self)
78
+ self.projects = ProjectsResource(self)
79
+ self.projects_users = ProjectUsersResource(self)
70
80
 
71
81
  @property
72
82
  def base_url(self) -> str:
@@ -0,0 +1,22 @@
1
+ # GENERATED FILE — DO NOT EDIT BY HAND.
2
+ #
3
+ # Generated from the corresponding module under intra42._async by
4
+ # scripts/unasync_generate.py (via the `unasync` library). Edit the
5
+ # async source and re-run that script instead.
6
+
7
+ from __future__ import annotations
8
+
9
+ from ...models.accreditation import Accreditation
10
+ from .base import Resource
11
+
12
+
13
+ class AccreditationsResource(Resource[Accreditation]):
14
+ path = "/accreditations"
15
+ model = Accreditation
16
+
17
+ def _bind_relations(self, instance: Accreditation) -> None:
18
+ # Referenced in the users apidoc page: GET /accreditations/:id/users.
19
+ instance._bind_relation(
20
+ "users",
21
+ lambda: self._client.users._queryset(f"/accreditations/{instance.id}/users"),
22
+ )
@@ -0,0 +1,29 @@
1
+ # GENERATED FILE — DO NOT EDIT BY HAND.
2
+ #
3
+ # Generated from the corresponding module under intra42._async by
4
+ # scripts/unasync_generate.py (via the `unasync` library). Edit the
5
+ # async source and re-run that script instead.
6
+
7
+ from __future__ import annotations
8
+
9
+ from ...models.cursus import Cursus
10
+ from .base import Resource
11
+
12
+
13
+ class CursusResource(Resource[Cursus]):
14
+ path = "/cursus"
15
+ model = Cursus
16
+
17
+ def _bind_relations(self, instance: Cursus) -> None:
18
+ instance._bind_relation(
19
+ "users",
20
+ lambda: self._client.users._queryset(f"/cursus/{instance.id}/users"),
21
+ )
22
+ instance._bind_relation(
23
+ "events",
24
+ lambda: self._client.events._queryset(f"/cursus/{instance.id}/events"),
25
+ )
26
+ instance._bind_relation(
27
+ "projects",
28
+ lambda: self._client.projects._queryset(f"/cursus/{instance.id}/projects"),
29
+ )
@@ -0,0 +1,27 @@
1
+ # GENERATED FILE — DO NOT EDIT BY HAND.
2
+ #
3
+ # Generated from the corresponding module under intra42._async by
4
+ # scripts/unasync_generate.py (via the `unasync` library). Edit the
5
+ # async source and re-run that script instead.
6
+
7
+ from __future__ import annotations
8
+
9
+ from ...models.exam import Exam
10
+ from .base import Resource
11
+
12
+
13
+ class ExamsResource(Resource[Exam]):
14
+ path = "/exams"
15
+ model = Exam
16
+
17
+ def graph(self, *, field: str | None = None, interval: str | None = None) -> dict[str, int]:
18
+ """Grouped temporal counts (``GET /exams/graph...``)."""
19
+ path = "/exams/graph"
20
+ if interval is not None and field is None:
21
+ field = "created_at"
22
+ if field is not None:
23
+ path += f"/on/{field}"
24
+ if interval is not None:
25
+ path += f"/by/{interval}"
26
+ response = self._client.request("GET", path)
27
+ return response.json()
@@ -0,0 +1,25 @@
1
+ # GENERATED FILE — DO NOT EDIT BY HAND.
2
+ #
3
+ # Generated from the corresponding module under intra42._async by
4
+ # scripts/unasync_generate.py (via the `unasync` library). Edit the
5
+ # async source and re-run that script instead.
6
+
7
+ from __future__ import annotations
8
+
9
+ from ...models.project import Project
10
+ from .base import Resource
11
+
12
+
13
+ class ProjectsResource(Resource[Project]):
14
+ path = "/projects"
15
+ model = Project
16
+
17
+ def _bind_relations(self, instance: Project) -> None:
18
+ # Referenced in the projects_users apidoc page:
19
+ # GET /projects/:id/projects_users.
20
+ instance._bind_relation(
21
+ "projects_users",
22
+ lambda: self._client.projects_users._queryset(
23
+ f"/projects/{instance.id}/projects_users"
24
+ ),
25
+ )
@@ -0,0 +1,26 @@
1
+ # GENERATED FILE — DO NOT EDIT BY HAND.
2
+ #
3
+ # Generated from the corresponding module under intra42._async by
4
+ # scripts/unasync_generate.py (via the `unasync` library). Edit the
5
+ # async source and re-run that script instead.
6
+
7
+ from __future__ import annotations
8
+
9
+ from ...models.project_user import ProjectUser
10
+ from .base import Resource
11
+
12
+
13
+ class ProjectUsersResource(Resource[ProjectUser]):
14
+ path = "/projects_users"
15
+ model = ProjectUser
16
+
17
+ def graph(self, *, field: str | None = None, interval: str | None = None) -> dict[str, int]:
18
+ path = "/projects_users/graph"
19
+ if interval is not None and field is None:
20
+ field = "created_at"
21
+ if field is not None:
22
+ path += f"/on/{field}"
23
+ if interval is not None:
24
+ path += f"/by/{interval}"
25
+ response = self._client.request("GET", path)
26
+ return response.json()
@@ -27,3 +27,7 @@ class UsersResource(Resource[User]):
27
27
  "locations",
28
28
  lambda: self._client.locations._queryset(f"/users/{instance.id}/locations"),
29
29
  )
30
+ instance._bind_relation(
31
+ "projects_users",
32
+ lambda: self._client.projects_users._queryset(f"/users/{instance.id}/projects_users"),
33
+ )
@@ -0,0 +1,29 @@
1
+ """The ``Accreditation`` resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import TYPE_CHECKING
7
+
8
+ from .base import FortyTwoModel
9
+
10
+ if TYPE_CHECKING:
11
+ from .._async.query import AsyncQuerySet
12
+ from .._sync.query import QuerySet
13
+ from .user import User
14
+
15
+
16
+ class Accreditation(FortyTwoModel):
17
+ id: int
18
+ name: str | None = None
19
+ user_id: int | None = None
20
+ cursus_id: int | None = None
21
+ difficulty: int | None = None
22
+ validated: bool | None = None
23
+ created_at: datetime | None = None
24
+ updated_at: datetime | None = None
25
+
26
+ @property
27
+ def users(self) -> AsyncQuerySet[User] | QuerySet[User]:
28
+ """Users holding this accreditation (``GET /accreditations/{id}/users``)."""
29
+ return self._relation("users")
@@ -0,0 +1,41 @@
1
+ """The ``Cursus`` resource (a curriculum/track, e.g. "42", "Piscine C")."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import TYPE_CHECKING
7
+
8
+ from .base import FortyTwoModel
9
+
10
+ if TYPE_CHECKING:
11
+ from .._async.query import AsyncQuerySet
12
+ from .._sync.query import QuerySet
13
+ from .event import Event
14
+ from .project import Project
15
+ from .user import User
16
+
17
+
18
+ class Cursus(FortyTwoModel):
19
+ id: int
20
+ name: str | None = None
21
+ slug: str | None = None
22
+ kind: str | None = None
23
+ created_at: datetime | None = None
24
+ updated_at: datetime | None = None
25
+ restricted: bool | None = None
26
+ is_subscriptable: bool | None = None
27
+
28
+ @property
29
+ def users(self) -> AsyncQuerySet[User] | QuerySet[User]:
30
+ """Users enrolled in this cursus (``GET /cursus/{id}/users``)."""
31
+ return self._relation("users")
32
+
33
+ @property
34
+ def events(self) -> AsyncQuerySet[Event] | QuerySet[Event]:
35
+ """Events for this cursus (``GET /cursus/{id}/events``)."""
36
+ return self._relation("events")
37
+
38
+ @property
39
+ def projects(self) -> AsyncQuerySet[Project] | QuerySet[Project]:
40
+ """Projects belonging to this cursus (``GET /cursus/{id}/projects``)."""
41
+ return self._relation("projects")
@@ -0,0 +1,32 @@
1
+ """The ``Exam`` resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+
7
+ from .base import FortyTwoModel
8
+ from .campus import Campus
9
+ from .cursus import Cursus
10
+ from .project import Project
11
+
12
+
13
+ class Exam(FortyTwoModel):
14
+ id: int
15
+ name: str | None = None
16
+ ip_range: str | None = None
17
+ begin_at: datetime | None = None
18
+ end_at: datetime | None = None
19
+ location: str | None = None
20
+ max_people: int | None = None
21
+ nbr_subscribers: int | None = None
22
+ created_at: datetime | None = None
23
+ updated_at: datetime | None = None
24
+ campus_id: int | None = None
25
+ campus: Campus | None = None
26
+ cursus: list[Cursus] | None = None
27
+ projects: list[Project] | None = None
28
+ visible: bool | None = None
29
+ validated: bool | None = None
30
+ validated_at: datetime | None = None
31
+ validator_id: int | None = None
32
+ prohibition_of_cancellation: int | None = None