jsonplaceholder-clone 1.0.0__py3-none-any.whl
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.
- jsonplaceholder_clone/__init__.py +3 -0
- jsonplaceholder_clone/__main__.py +15 -0
- jsonplaceholder_clone/crud.py +54 -0
- jsonplaceholder_clone/database.py +6 -0
- jsonplaceholder_clone/main.py +11 -0
- jsonplaceholder_clone/routers/__init__.py +1 -0
- jsonplaceholder_clone/routers/posts.py +61 -0
- jsonplaceholder_clone/schemas.py +15 -0
- jsonplaceholder_clone-1.0.0.dist-info/METADATA +111 -0
- jsonplaceholder_clone-1.0.0.dist-info/RECORD +14 -0
- jsonplaceholder_clone-1.0.0.dist-info/WHEEL +5 -0
- jsonplaceholder_clone-1.0.0.dist-info/entry_points.txt +2 -0
- jsonplaceholder_clone-1.0.0.dist-info/licenses/Licence +21 -0
- jsonplaceholder_clone-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import uvicorn
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Démarrage du serveur (commande jsonplaceholder-clone ou bouton Play ▷ de VS Code)
|
|
5
|
+
def main():
|
|
6
|
+
uvicorn.run(
|
|
7
|
+
"jsonplaceholder_clone.main:app",
|
|
8
|
+
host="127.0.0.1",
|
|
9
|
+
port=8000,
|
|
10
|
+
reload=True,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
main()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from .database import db_posts
|
|
4
|
+
from .schemas import Post, UpdatePost
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Récupérer tous les posts (avec filtre optionnel par userId)
|
|
8
|
+
def list_posts(user_id: Optional[int] = None) -> list[dict]:
|
|
9
|
+
if user_id is not None:
|
|
10
|
+
return [p for p in db_posts if p["userId"] == user_id]
|
|
11
|
+
return db_posts
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Récupérer UN SEUL post par son ID (None s'il n'existe pas)
|
|
15
|
+
def get_post(post_id: int) -> Optional[dict]:
|
|
16
|
+
return next((p for p in db_posts if p["id"] == post_id), None)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Créer un nouveau post (None si l'ID existe déjà)
|
|
20
|
+
def create_post(post: Post) -> Optional[dict]:
|
|
21
|
+
# On vérifie si l'ID existe déjà pour éviter les doublons
|
|
22
|
+
if get_post(post.id) is not None:
|
|
23
|
+
return None
|
|
24
|
+
new_post = post.model_dump()
|
|
25
|
+
db_posts.append(new_post)
|
|
26
|
+
return new_post
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Remplacer complètement un post existant (None s'il n'existe pas)
|
|
30
|
+
def replace_post(post_id: int, post: Post) -> Optional[dict]:
|
|
31
|
+
for index, existing in enumerate(db_posts):
|
|
32
|
+
if existing["id"] == post_id:
|
|
33
|
+
db_posts[index] = post.model_dump()
|
|
34
|
+
return db_posts[index]
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Modifier partiellement un post existant (None s'il n'existe pas)
|
|
39
|
+
def update_post(post_id: int, data: UpdatePost) -> Optional[dict]:
|
|
40
|
+
post = get_post(post_id)
|
|
41
|
+
if post is None:
|
|
42
|
+
return None
|
|
43
|
+
# On ne met à jour que les champs envoyés
|
|
44
|
+
post.update(data.model_dump(exclude_unset=True))
|
|
45
|
+
return post
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Supprimer un post (False s'il n'existe pas)
|
|
49
|
+
def delete_post(post_id: int) -> bool:
|
|
50
|
+
post = get_post(post_id)
|
|
51
|
+
if post is None:
|
|
52
|
+
return False
|
|
53
|
+
db_posts.remove(post)
|
|
54
|
+
return True
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Base de données fictive en mémoire
|
|
2
|
+
db_posts: list[dict] = [
|
|
3
|
+
{"userId": 1, "id": 1, "title": "Premier Post", "body": "Ceci est le contenu du premier post."},
|
|
4
|
+
{"userId": 1, "id": 2, "title": "Deuxième Post", "body": "Ceci est le contenu du deuxième post."},
|
|
5
|
+
{"userId": 2, "id": 3, "title": "Troisième Post", "body": "Un post écrit par l'utilisateur 2."},
|
|
6
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .posts import router as posts_router
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, HTTPException, status
|
|
4
|
+
|
|
5
|
+
from .. import crud
|
|
6
|
+
from ..schemas import Post, UpdatePost
|
|
7
|
+
|
|
8
|
+
router = APIRouter(prefix="/posts", tags=["posts"])
|
|
9
|
+
|
|
10
|
+
NOT_FOUND = "Post non trouvé"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# [GET] Récupérer TOUS les posts (avec filtre optionnel par userId)
|
|
14
|
+
@router.get("", response_model=List[Post])
|
|
15
|
+
def get_posts(userId: Optional[int] = None):
|
|
16
|
+
return crud.list_posts(userId)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# [GET] Récupérer UN SEUL post par son ID
|
|
20
|
+
@router.get("/{post_id}", response_model=Post)
|
|
21
|
+
def get_post_by_id(post_id: int):
|
|
22
|
+
post = crud.get_post(post_id)
|
|
23
|
+
if post is None:
|
|
24
|
+
raise HTTPException(status_code=404, detail=NOT_FOUND)
|
|
25
|
+
return post
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# [POST] Créer un nouveau post
|
|
29
|
+
@router.post("", status_code=status.HTTP_201_CREATED, response_model=Post)
|
|
30
|
+
def create_post(post: Post):
|
|
31
|
+
created = crud.create_post(post)
|
|
32
|
+
# crud renvoie None si l'ID existe déjà
|
|
33
|
+
if created is None:
|
|
34
|
+
raise HTTPException(status_code=400, detail="Un post avec cet ID existe déjà")
|
|
35
|
+
return created
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# [PUT] Remplacer complètement un post existant
|
|
39
|
+
@router.put("/{post_id}", response_model=Post)
|
|
40
|
+
def update_post_complete(post_id: int, updated_post: Post):
|
|
41
|
+
result = crud.replace_post(post_id, updated_post)
|
|
42
|
+
if result is None:
|
|
43
|
+
raise HTTPException(status_code=404, detail=NOT_FOUND)
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# [PATCH] Modifier partiellement un post existant
|
|
48
|
+
@router.patch("/{post_id}", response_model=Post)
|
|
49
|
+
def update_post_partial(post_id: int, partial_data: UpdatePost):
|
|
50
|
+
result = crud.update_post(post_id, partial_data)
|
|
51
|
+
if result is None:
|
|
52
|
+
raise HTTPException(status_code=404, detail=NOT_FOUND)
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# [DELETE] Supprimer un post
|
|
57
|
+
@router.delete("/{post_id}")
|
|
58
|
+
def delete_post(post_id: int):
|
|
59
|
+
if not crud.delete_post(post_id):
|
|
60
|
+
raise HTTPException(status_code=404, detail=NOT_FOUND)
|
|
61
|
+
return {"message": f"Le post {post_id} a été supprimé avec succès"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Post(BaseModel):
|
|
6
|
+
id: int
|
|
7
|
+
userId: int
|
|
8
|
+
title: str
|
|
9
|
+
body: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UpdatePost(BaseModel):
|
|
13
|
+
userId: Optional[int] = None
|
|
14
|
+
title: Optional[str] = None
|
|
15
|
+
body: Optional[str] = None
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jsonplaceholder-clone
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Reconstruction de l'API JSONPlaceholder avec FastAPI
|
|
5
|
+
Author-email: Tom Doizon <tom.doizon1@gmail.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Framework :: FastAPI
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: Licence
|
|
12
|
+
Requires-Dist: fastapi
|
|
13
|
+
Requires-Dist: uvicorn[standard]
|
|
14
|
+
Requires-Dist: pydantic>=2
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# JSONPlaceholder Clone
|
|
18
|
+
|
|
19
|
+
Reconstruction de l'API [JSONPlaceholder](https://jsonplaceholder.typicode.com) avec FastAPI.
|
|
20
|
+
L'API gère des posts (CRUD complet) avec une base de données fictive en mémoire.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Depuis la racine du projet (là où se trouve `pyproject.toml`) :
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
py -m venv .venv
|
|
28
|
+
.\.venv\Scripts\Activate.ps1 # Windows (PowerShell)
|
|
29
|
+
# source .venv/bin/activate # macOS / Linux
|
|
30
|
+
pip install -e .
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Le mode `-e` (éditable) prend en compte vos modifications sans réinstaller.
|
|
34
|
+
|
|
35
|
+
## Lancer l'API
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
jsonplaceholder-clone
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
ou :
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
python -m jsonplaceholder_clone
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
L'API est disponible sur http://127.0.0.1:8000 et la documentation interactive sur http://127.0.0.1:8000/docs.
|
|
48
|
+
|
|
49
|
+
## Routes
|
|
50
|
+
|
|
51
|
+
| Méthode | Route | Description |
|
|
52
|
+
|---------|----------------|-----------------------------------------------------|
|
|
53
|
+
| GET | `/posts` | Liste tous les posts (filtre optionnel `?userId=1`) |
|
|
54
|
+
| GET | `/posts/{id}` | Récupère un post par son ID |
|
|
55
|
+
| POST | `/posts` | Crée un nouveau post |
|
|
56
|
+
| PUT | `/posts/{id}` | Remplace complètement un post |
|
|
57
|
+
| PATCH | `/posts/{id}` | Modifie partiellement un post |
|
|
58
|
+
| DELETE | `/posts/{id}` | Supprime un post |
|
|
59
|
+
|
|
60
|
+
Exemple de post :
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"userId": 1,
|
|
65
|
+
"id": 1,
|
|
66
|
+
"title": "Premier Post",
|
|
67
|
+
"body": "Ceci est le contenu du premier post."
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Utiliser le package dans un autre script
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from fastapi import FastAPI
|
|
75
|
+
from jsonplaceholder_clone import crud
|
|
76
|
+
from jsonplaceholder_clone.routers import posts_router
|
|
77
|
+
|
|
78
|
+
app = FastAPI()
|
|
79
|
+
app.include_router(posts_router)
|
|
80
|
+
|
|
81
|
+
print(crud.list_posts())
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Structure du projet
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
jsonplaceholder_clone/
|
|
88
|
+
├── pyproject.toml
|
|
89
|
+
├── README.md
|
|
90
|
+
├── LICENSE
|
|
91
|
+
├── MANIFEST.in
|
|
92
|
+
└── src/
|
|
93
|
+
└── jsonplaceholder_clone/
|
|
94
|
+
├── __init__.py # expose app, Post, UpdatePost, crud
|
|
95
|
+
├── __main__.py # démarrage du serveur
|
|
96
|
+
├── main.py # création de l'application FastAPI
|
|
97
|
+
├── schemas.py # modèles Pydantic
|
|
98
|
+
├── database.py # base de données fictive en mémoire
|
|
99
|
+
├── crud.py # logique métier
|
|
100
|
+
└── routers/
|
|
101
|
+
├── __init__.py # expose posts_router
|
|
102
|
+
└── posts.py # routes /posts
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Remarque
|
|
106
|
+
|
|
107
|
+
Les données sont stockées en mémoire : elles sont réinitialisées à chaque redémarrage du serveur.
|
|
108
|
+
|
|
109
|
+
## Licence
|
|
110
|
+
|
|
111
|
+
Ce projet est distribué sous licence MIT. Voir le fichier [LICENSE](LICENSE).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
jsonplaceholder_clone/__init__.py,sha256=y2edlRqDkjJcqk0N-Or__UBqoJ0eP9KKR4NBud6qw2I,78
|
|
2
|
+
jsonplaceholder_clone/__main__.py,sha256=oxQq-dULN0pQp_xpqSQX3_ck39-Q_c-3dsHgal1dzZo,286
|
|
3
|
+
jsonplaceholder_clone/crud.py,sha256=GiLVCFpQXz3Fu7RKEUg80tNonXiu8Vq8wDJ2pxy5xrc,1663
|
|
4
|
+
jsonplaceholder_clone/database.py,sha256=BUvax0CNCRVdWK17bGUXnDHKVX8c0I4WE4APHiNtABU,375
|
|
5
|
+
jsonplaceholder_clone/main.py,sha256=1isf_B-_7DTashq14biX299jJPcV1lubW17a2LEk2O0,242
|
|
6
|
+
jsonplaceholder_clone/schemas.py,sha256=epUdBs55QV_KkibG2WHiLDKxAw277c_gFJiN6hC7rP8,268
|
|
7
|
+
jsonplaceholder_clone/routers/__init__.py,sha256=1BIv7wzIL5wml7j2FF8EcClxYqFELctD5rinO_GLoGI,41
|
|
8
|
+
jsonplaceholder_clone/routers/posts.py,sha256=HHpHNVjoNItLF04wvz7vRthm4W4UeZteUCLOUdWhoVk,1948
|
|
9
|
+
jsonplaceholder_clone-1.0.0.dist-info/licenses/Licence,sha256=N4EwQxjuar6nuK9T0hS8AIoomg52p_MbeCW2xufou8w,1086
|
|
10
|
+
jsonplaceholder_clone-1.0.0.dist-info/METADATA,sha256=ZHWLLO5M9QWOTSOYhKqXmKeE0uSpWgFAQKyKuMW73F0,3346
|
|
11
|
+
jsonplaceholder_clone-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
12
|
+
jsonplaceholder_clone-1.0.0.dist-info/entry_points.txt,sha256=8fiMaLzqrdk20K5BBA7cCnrU3LLIqLBBmHtZ6cmZTpI,78
|
|
13
|
+
jsonplaceholder_clone-1.0.0.dist-info/top_level.txt,sha256=BCznWgu92u0v3sLmNvyVQMZIxn6f77kSePUBToDSCVQ,22
|
|
14
|
+
jsonplaceholder_clone-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tom Doizon
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jsonplaceholder_clone
|