42api 0.1.0__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 (59) hide show
  1. 42api-0.1.0/.github/workflows/ci.yml +41 -0
  2. 42api-0.1.0/.gitignore +221 -0
  3. 42api-0.1.0/.pre-commit-config.yaml +23 -0
  4. 42api-0.1.0/LICENSE +21 -0
  5. 42api-0.1.0/PKG-INFO +137 -0
  6. 42api-0.1.0/README.md +112 -0
  7. 42api-0.1.0/pyproject.toml +63 -0
  8. 42api-0.1.0/scripts/unasync_generate.py +158 -0
  9. 42api-0.1.0/src/intra42/__init__.py +53 -0
  10. 42api-0.1.0/src/intra42/_async/__init__.py +0 -0
  11. 42api-0.1.0/src/intra42/_async/client.py +126 -0
  12. 42api-0.1.0/src/intra42/_async/query.py +99 -0
  13. 42api-0.1.0/src/intra42/_async/resources/__init__.py +0 -0
  14. 42api-0.1.0/src/intra42/_async/resources/base.py +77 -0
  15. 42api-0.1.0/src/intra42/_async/resources/campus_users.py +9 -0
  16. 42api-0.1.0/src/intra42/_async/resources/campuses.py +23 -0
  17. 42api-0.1.0/src/intra42/_async/resources/events.py +9 -0
  18. 42api-0.1.0/src/intra42/_async/resources/locations.py +30 -0
  19. 42api-0.1.0/src/intra42/_async/resources/users.py +23 -0
  20. 42api-0.1.0/src/intra42/_auth.py +103 -0
  21. 42api-0.1.0/src/intra42/_config.py +17 -0
  22. 42api-0.1.0/src/intra42/_pagination.py +23 -0
  23. 42api-0.1.0/src/intra42/_query_params.py +54 -0
  24. 42api-0.1.0/src/intra42/_rate_limit.py +102 -0
  25. 42api-0.1.0/src/intra42/_sync/__init__.py +0 -0
  26. 42api-0.1.0/src/intra42/_sync/client.py +132 -0
  27. 42api-0.1.0/src/intra42/_sync/query.py +105 -0
  28. 42api-0.1.0/src/intra42/_sync/resources/__init__.py +0 -0
  29. 42api-0.1.0/src/intra42/_sync/resources/base.py +83 -0
  30. 42api-0.1.0/src/intra42/_sync/resources/campus_users.py +15 -0
  31. 42api-0.1.0/src/intra42/_sync/resources/campuses.py +29 -0
  32. 42api-0.1.0/src/intra42/_sync/resources/events.py +15 -0
  33. 42api-0.1.0/src/intra42/_sync/resources/locations.py +34 -0
  34. 42api-0.1.0/src/intra42/_sync/resources/users.py +29 -0
  35. 42api-0.1.0/src/intra42/exceptions.py +88 -0
  36. 42api-0.1.0/src/intra42/models/__init__.py +0 -0
  37. 42api-0.1.0/src/intra42/models/base.py +47 -0
  38. 42api-0.1.0/src/intra42/models/campus.py +66 -0
  39. 42api-0.1.0/src/intra42/models/campus_user.py +16 -0
  40. 42api-0.1.0/src/intra42/models/event.py +41 -0
  41. 42api-0.1.0/src/intra42/models/location.py +26 -0
  42. 42api-0.1.0/src/intra42/models/user.py +73 -0
  43. 42api-0.1.0/src/intra42/py.typed +0 -0
  44. 42api-0.1.0/tests/conftest.py +21 -0
  45. 42api-0.1.0/tests/integration/resources/test_resources_campus_users.py +57 -0
  46. 42api-0.1.0/tests/integration/resources/test_resources_campuses.py +33 -0
  47. 42api-0.1.0/tests/integration/resources/test_resources_events.py +96 -0
  48. 42api-0.1.0/tests/integration/resources/test_resources_locations.py +161 -0
  49. 42api-0.1.0/tests/integration/test_client_integration.py +129 -0
  50. 42api-0.1.0/tests/integration/test_nested_access.py +120 -0
  51. 42api-0.1.0/tests/integration/test_pagination_walk.py +82 -0
  52. 42api-0.1.0/tests/unit/test_auth.py +58 -0
  53. 42api-0.1.0/tests/unit/test_models_campus.py +23 -0
  54. 42api-0.1.0/tests/unit/test_models_user.py +40 -0
  55. 42api-0.1.0/tests/unit/test_pagination.py +25 -0
  56. 42api-0.1.0/tests/unit/test_public_api.py +35 -0
  57. 42api-0.1.0/tests/unit/test_query_builder.py +47 -0
  58. 42api-0.1.0/tests/unit/test_rate_limit.py +90 -0
  59. 42api-0.1.0/uv.lock +935 -0
@@ -0,0 +1,41 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v3
19
+ with:
20
+ version: "latest"
21
+
22
+ - name: Set up Python ${{ matrix.python-version }}
23
+ run: uv python install ${{ matrix.python-version }}
24
+
25
+ - name: Install dependencies
26
+ run: uv sync --all-extras --python ${{ matrix.python-version }}
27
+
28
+ - name: Ruff lint
29
+ run: uv run ruff check .
30
+
31
+ - name: Ruff format check
32
+ run: uv run ruff format --check .
33
+
34
+ - name: Check generated sync package is up to date
35
+ run: uv run python scripts/unasync_generate.py --check
36
+
37
+ - name: mypy
38
+ run: uv run mypy
39
+
40
+ - name: pytest
41
+ run: uv run pytest
42api-0.1.0/.gitignore ADDED
@@ -0,0 +1,221 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ .env*
155
+ env/
156
+ venv/
157
+ ENV/
158
+ env.bak/
159
+ venv.bak/
160
+
161
+ # Spyder project settings
162
+ .spyderproject
163
+ .spyproject
164
+
165
+ # Rope project settings
166
+ .ropeproject
167
+
168
+ # mkdocs documentation
169
+ /site
170
+
171
+ # mypy
172
+ .mypy_cache/
173
+ .dmypy.json
174
+ dmypy.json
175
+
176
+ # Pyre type checker
177
+ .pyre/
178
+
179
+ # pytype static type analyzer
180
+ .pytype/
181
+
182
+ # Cython debug symbols
183
+ cython_debug/
184
+
185
+ # PyCharm
186
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
187
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
188
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
189
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
190
+ # .idea/
191
+
192
+ # Abstra
193
+ # Abstra is an AI-powered process automation framework.
194
+ # Ignore directories containing user credentials, local state, and settings.
195
+ # Learn more at https://abstra.io/docs
196
+ .abstra/
197
+
198
+ # Visual Studio Code
199
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
200
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
201
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
202
+ # you could uncomment the following to ignore the entire vscode folder
203
+ # .vscode/
204
+ # Temporary file for partial code execution
205
+ tempCodeRunnerFile.py
206
+
207
+ # Ruff stuff:
208
+ .ruff_cache/
209
+
210
+ # PyPI configuration file
211
+ .pypirc
212
+
213
+ # Marimo
214
+ marimo/_static/
215
+ marimo/_lsp/
216
+ __marimo__/
217
+
218
+ # Streamlit
219
+ .streamlit/secrets.toml
220
+
221
+ assets/
@@ -0,0 +1,23 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.16.5
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: local
10
+ hooks:
11
+ - id: unasync-check
12
+ name: check _sync is generated from _async
13
+ entry: uv run python scripts/unasync_generate.py --check
14
+ language: system
15
+ pass_filenames: false
16
+ files: ^src/intra42/_(async|sync)/
17
+
18
+ - id: mypy
19
+ name: mypy
20
+ entry: uv run mypy
21
+ language: system
22
+ pass_filenames: false
23
+ types: [python]
42api-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
42api-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.5
2
+ Name: 42api
3
+ Version: 0.1.0
4
+ Summary: Object-oriented Python client for the 42 School API (api.intra.42.fr)
5
+ Project-URL: Homepage, https://github.com/Shadowaker/42api
6
+ Author-email: Dan <danyridolfo98@gmail.com>
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: 42,42school,api,client,intra
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: pydantic>=2.0
24
+ Description-Content-Type: text/markdown
25
+
26
+ # 42api
27
+
28
+ An object-oriented Python client for the [42 School API](https://api.intra.42.fr)
29
+ (`api.intra.42.fr`) that handles OAuth2 authentication, request pacing, and
30
+ pagination for you, so you can work with typed resources instead of raw JSON
31
+ and HTTP plumbing.
32
+
33
+ - **Sync and async** — `Client` and `AsyncClient` share one codebase and the
34
+ same interface.
35
+ - **Typed models** — resources are [Pydantic v2](https://docs.pydantic.dev/)
36
+ models with IDE-friendly autocomplete.
37
+ - **Automatic rate limiting** — requests are paced to stay under 42's limits;
38
+ `429`s are retried with backoff automatically.
39
+ - **Lazy pagination** — iterate a query and it walks every page for you.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ uv add 42api
45
+ # or: pip install 42api
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ```python
51
+ from intra42 import Client
52
+
53
+ with Client(client_id="...", client_secret="...") as client:
54
+ user = client.users.get("jdoe")
55
+ print(user.login, user.email)
56
+
57
+ for user in client.users.filter(campus_id=1).sort("-level"):
58
+ print(user.login)
59
+ ```
60
+
61
+ Async is the same shape:
62
+
63
+ ```python
64
+ import asyncio
65
+ from intra42 import AsyncClient
66
+
67
+
68
+ async def main() -> None:
69
+ async with AsyncClient(client_id="...", client_secret="...") as client:
70
+ user = await client.users.get("jdoe")
71
+ async for user in client.users.filter(campus_id=1).sort("-level"):
72
+ print(user.login)
73
+
74
+
75
+ asyncio.run(main())
76
+ ```
77
+
78
+ Fetched instances expose their nested resources directly — no need to
79
+ build the scoped path yourself:
80
+
81
+ ```python
82
+ campus = client.campuses.get(1)
83
+ for event in campus.events: # GET /campus/1/events
84
+ print(event.name)
85
+
86
+ user = client.users.get("jdoe")
87
+ for cu in user.campus_users: # GET /users/jdoe/campus_users
88
+ print(cu.campus_id, cu.is_primary)
89
+ ```
90
+
91
+ These (`campus.events`, `campus.users`, `user.events`, `user.campus_users`)
92
+ are only available on instances fetched via a client — a manually
93
+ constructed model raises a clear `RuntimeError` if accessed.
94
+
95
+ Get your `client_id`/`client_secret` by registering an app at
96
+ https://profile.intra.42.fr/oauth/applications. This library uses the
97
+ **client credentials** flow, so it accesses the API as your app rather than
98
+ as a specific logged-in user.
99
+
100
+ ## Errors
101
+
102
+ All errors subclass `intra42.FortyTwoAPIError`, with subclasses for common
103
+ HTTP statuses: `AuthenticationError` (401), `PermissionDeniedError` (403),
104
+ `NotFoundError` (404), `ValidationError` (422), `RateLimitError` (429, only
105
+ raised once the built-in retry budget is exhausted), `ServerError` (5xx),
106
+ and `NetworkError` (connection/timeout failures).
107
+
108
+ ## Status
109
+
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.
114
+
115
+ ## Development
116
+
117
+ Uses [uv](https://docs.astral.sh/uv/) for dependency management.
118
+
119
+ ```bash
120
+ uv sync # install dependencies
121
+ uv run pytest # run tests
122
+ uv run ruff check . && uv run ruff format --check . # lint
123
+ uv run mypy # type check
124
+ ```
125
+
126
+ The sync client (`intra42._sync`) is generated from the async client
127
+ (`intra42._async`) via [`unasync`](https://github.com/python-trio/unasync) —
128
+ edit the async source and regenerate:
129
+
130
+ ```bash
131
+ uv run python scripts/unasync_generate.py # regenerate
132
+ uv run python scripts/unasync_generate.py --check # verify no drift (CI)
133
+ ```
134
+
135
+ ## License
136
+
137
+ MIT
42api-0.1.0/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # 42api
2
+
3
+ An object-oriented Python client for the [42 School API](https://api.intra.42.fr)
4
+ (`api.intra.42.fr`) that handles OAuth2 authentication, request pacing, and
5
+ pagination for you, so you can work with typed resources instead of raw JSON
6
+ and HTTP plumbing.
7
+
8
+ - **Sync and async** — `Client` and `AsyncClient` share one codebase and the
9
+ same interface.
10
+ - **Typed models** — resources are [Pydantic v2](https://docs.pydantic.dev/)
11
+ models with IDE-friendly autocomplete.
12
+ - **Automatic rate limiting** — requests are paced to stay under 42's limits;
13
+ `429`s are retried with backoff automatically.
14
+ - **Lazy pagination** — iterate a query and it walks every page for you.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ uv add 42api
20
+ # or: pip install 42api
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+ from intra42 import Client
27
+
28
+ with Client(client_id="...", client_secret="...") as client:
29
+ user = client.users.get("jdoe")
30
+ print(user.login, user.email)
31
+
32
+ for user in client.users.filter(campus_id=1).sort("-level"):
33
+ print(user.login)
34
+ ```
35
+
36
+ Async is the same shape:
37
+
38
+ ```python
39
+ import asyncio
40
+ from intra42 import AsyncClient
41
+
42
+
43
+ async def main() -> None:
44
+ async with AsyncClient(client_id="...", client_secret="...") as client:
45
+ user = await client.users.get("jdoe")
46
+ async for user in client.users.filter(campus_id=1).sort("-level"):
47
+ print(user.login)
48
+
49
+
50
+ asyncio.run(main())
51
+ ```
52
+
53
+ Fetched instances expose their nested resources directly — no need to
54
+ build the scoped path yourself:
55
+
56
+ ```python
57
+ campus = client.campuses.get(1)
58
+ for event in campus.events: # GET /campus/1/events
59
+ print(event.name)
60
+
61
+ user = client.users.get("jdoe")
62
+ for cu in user.campus_users: # GET /users/jdoe/campus_users
63
+ print(cu.campus_id, cu.is_primary)
64
+ ```
65
+
66
+ These (`campus.events`, `campus.users`, `user.events`, `user.campus_users`)
67
+ are only available on instances fetched via a client — a manually
68
+ constructed model raises a clear `RuntimeError` if accessed.
69
+
70
+ Get your `client_id`/`client_secret` by registering an app at
71
+ https://profile.intra.42.fr/oauth/applications. This library uses the
72
+ **client credentials** flow, so it accesses the API as your app rather than
73
+ as a specific logged-in user.
74
+
75
+ ## Errors
76
+
77
+ All errors subclass `intra42.FortyTwoAPIError`, with subclasses for common
78
+ HTTP statuses: `AuthenticationError` (401), `PermissionDeniedError` (403),
79
+ `NotFoundError` (404), `ValidationError` (422), `RateLimitError` (429, only
80
+ raised once the built-in retry budget is exhausted), `ServerError` (5xx),
81
+ and `NetworkError` (connection/timeout failures).
82
+
83
+ ## Status
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.
89
+
90
+ ## Development
91
+
92
+ Uses [uv](https://docs.astral.sh/uv/) for dependency management.
93
+
94
+ ```bash
95
+ uv sync # install dependencies
96
+ uv run pytest # run tests
97
+ uv run ruff check . && uv run ruff format --check . # lint
98
+ uv run mypy # type check
99
+ ```
100
+
101
+ The sync client (`intra42._sync`) is generated from the async client
102
+ (`intra42._async`) via [`unasync`](https://github.com/python-trio/unasync) —
103
+ edit the async source and regenerate:
104
+
105
+ ```bash
106
+ uv run python scripts/unasync_generate.py # regenerate
107
+ uv run python scripts/unasync_generate.py --check # verify no drift (CI)
108
+ ```
109
+
110
+ ## License
111
+
112
+ MIT
@@ -0,0 +1,63 @@
1
+ [project]
2
+ name = "42api"
3
+ version = "0.1.0"
4
+ description = "Object-oriented Python client for the 42 School API (api.intra.42.fr)"
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ requires-python = ">=3.10"
8
+ authors = [{ name = "Dan", email = "danyridolfo98@gmail.com" }]
9
+ dependencies = ["httpx>=0.27", "pydantic>=2.0"]
10
+
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Typing :: Typed",
22
+ "Framework :: AsyncIO",
23
+ ]
24
+ keywords = ["42", "42school", "api", "client", "intra"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/Shadowaker/42api"
28
+
29
+ [dependency-groups]
30
+ dev = [
31
+ "pytest>=8.0",
32
+ "pytest-asyncio>=0.24",
33
+ "respx>=0.21",
34
+ "ruff>=0.6",
35
+ "mypy>=1.11",
36
+ "unasync>=0.6",
37
+ "pre-commit>=3.8",
38
+ ]
39
+
40
+ [build-system]
41
+ requires = ["hatchling"]
42
+ build-backend = "hatchling.build"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/intra42"]
46
+
47
+ [tool.pytest.ini_options]
48
+ asyncio_mode = "auto"
49
+ testpaths = ["tests"]
50
+
51
+ [tool.ruff]
52
+ line-length = 100
53
+ target-version = "py310"
54
+
55
+ [tool.ruff.lint]
56
+ select = ["E", "F", "I", "UP", "B"]
57
+
58
+ [tool.mypy]
59
+ python_version = "3.10"
60
+ packages = ["intra42"]
61
+ mypy_path = "src"
62
+ disallow_untyped_defs = true
63
+ warn_unused_ignores = true