From e8ffa0b5baa388b3e77b76213be5ea4029f75227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Augusto?= Date: Thu, 31 Jul 2025 13:31:31 -0300 Subject: [PATCH 01/13] chore: inicializa projeto com estrutura de pastas e arquivos __init__.py --- fastapi-filmes/src/api/__init__.py | 0 fastapi-filmes/src/core/__init__.py | 0 fastapi-filmes/src/db/__init__.py | 0 fastapi-filmes/src/schemas/__init__.py | 0 fastapi-filmes/src/services/__init__.py | 0 fastapi-filmes/tests/__init__.py | 0 requirements.txt | 0 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 fastapi-filmes/src/api/__init__.py create mode 100644 fastapi-filmes/src/core/__init__.py create mode 100644 fastapi-filmes/src/db/__init__.py create mode 100644 fastapi-filmes/src/schemas/__init__.py create mode 100644 fastapi-filmes/src/services/__init__.py create mode 100644 fastapi-filmes/tests/__init__.py create mode 100644 requirements.txt diff --git a/fastapi-filmes/src/api/__init__.py b/fastapi-filmes/src/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/fastapi-filmes/src/core/__init__.py b/fastapi-filmes/src/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/fastapi-filmes/src/db/__init__.py b/fastapi-filmes/src/db/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/fastapi-filmes/src/schemas/__init__.py b/fastapi-filmes/src/schemas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/fastapi-filmes/src/services/__init__.py b/fastapi-filmes/src/services/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/fastapi-filmes/tests/__init__.py b/fastapi-filmes/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..e69de29bb From 8083eeb4132651aa2ba12d850f1b744a08863e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Augusto?= Date: Thu, 31 Jul 2025 14:29:42 -0300 Subject: [PATCH 02/13] =?UTF-8?q?[ADD]=20configura=C3=A7=C3=A3o=20inicial?= =?UTF-8?q?=20do=20banco=20de=20dados=20e=20settings=20do=20projeto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/core/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 155 bytes .../src/core/__pycache__/config.cpython-313.pyc | Bin 0 -> 640 bytes fastapi-filmes/src/core/config.py | 7 +++++++ fastapi-filmes/src/db/__init__.py | 1 + .../src/db/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 236 bytes .../src/db/__pycache__/database.cpython-313.pyc | Bin 0 -> 597 bytes fastapi-filmes/src/db/database.py | 8 ++++++++ 7 files changed, 16 insertions(+) create mode 100644 fastapi-filmes/src/core/__pycache__/__init__.cpython-313.pyc create mode 100644 fastapi-filmes/src/core/__pycache__/config.cpython-313.pyc create mode 100644 fastapi-filmes/src/core/config.py create mode 100644 fastapi-filmes/src/db/__pycache__/__init__.cpython-313.pyc create mode 100644 fastapi-filmes/src/db/__pycache__/database.cpython-313.pyc create mode 100644 fastapi-filmes/src/db/database.py diff --git a/fastapi-filmes/src/core/__pycache__/__init__.cpython-313.pyc b/fastapi-filmes/src/core/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff017dc17c01097a66342631f6ed98c2a421a6e6 GIT binary patch literal 155 zcmey&%ge<81dnHSXMkxC0RxOs#%C5FV=6;BgC?WjN`@jPAn!9s>XwzWRZM7cYEf}a zR(^46VoXwEa&~H7N=#Z}aYuwMAZ0vy>P^YPt8X_+abUjr-uu2c^X5&Lmx}}!|8nx;8=k+?V6nV8 znFdHs2_uX?B|yrgLYT8on7if796&j(kgd5LoQPJvi!^HY=eg{-yswMlDd7YtBj7L! z?j5`lJif$p%)N^$&v&l&lmEkW~_iIySnh5c7j4fqiM z7aqo&X}?l)4Sn8EKq9DN90iAOAf0*!=|FS^;XOGoZ@k_6aQn>LI4`e%Dts(_4!+-e za#n7o*&kjDt}lwJF03rL_4RzXSz|NP4OFfFU^bM8u!bSUt#2ayb}6MlN#U>O&|Uh6 PWayL9>Ph{bpOyauUWlDv literal 0 HcmV?d00001 diff --git a/fastapi-filmes/src/core/config.py b/fastapi-filmes/src/core/config.py new file mode 100644 index 000000000..cdfada5ba --- /dev/null +++ b/fastapi-filmes/src/core/config.py @@ -0,0 +1,7 @@ +import os + +class Settings: + PROJECT_NAME: str = "FastAPI Filmes" + DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///./films.db") + +settings = Settings() diff --git a/fastapi-filmes/src/db/__init__.py b/fastapi-filmes/src/db/__init__.py index e69de29bb..d2177c529 100644 --- a/fastapi-filmes/src/db/__init__.py +++ b/fastapi-filmes/src/db/__init__.py @@ -0,0 +1 @@ +from .database import Base, engine, SessionLocal diff --git a/fastapi-filmes/src/db/__pycache__/__init__.cpython-313.pyc b/fastapi-filmes/src/db/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45552ce86b6f14b6707b361edd42d524f4c0b8cf GIT binary patch literal 236 zcmey&%ge<81hbZOXBYwL#~=<2FhLogC4h{n48aV+jNS}hj75xIOhrsy%tg!!4Czdo z%r6;%(wfY-Sez1zQ*W`Q=A~!mrQYHRPAx9Z%+K@5PfpD7(`32Dk&;-Fm;_W(#0*ql z!~!B%fy7FN&p?vlmW8ubOej#cI3_E}7Ei)%KwK%4@ zC^;r2DJDKXGcU6wK3=b&@)n0pZhlH>PO4oI$S)w*6mtWK56p~=jQ1G~E-@GsaRB)M DyKg-> literal 0 HcmV?d00001 diff --git a/fastapi-filmes/src/db/__pycache__/database.cpython-313.pyc b/fastapi-filmes/src/db/__pycache__/database.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..450f238b83ed266fc91fdfdc36306f57246fbdfe GIT binary patch literal 597 zcmYjOyKWOf6rJ(Dyk?D)0xY13EF@$pSZNR>LA7C~QCAYY9?$Ob?k6F~~HKgP7*AGL$2>mIGv#?LW zVxqw(6rce6sD}Zsl(^zodIlKB*znDs1=cY(eY@v?vw{e2A>!;Vo!Ongffd+mIB?c0 z;1a8a0z0sY8&uZ~s1c)u?wm^DQtY;Cf6J+A7*E^{mKq&SXd-7Hvb-=vWr4$g)H# zx5R^VEQZSLCwcU=?Wvl0o6=NUWd~sBxzzP@qQfV$b73YhOTpy}f&wzIO0pq67o4oLQ!tt;b4xG!4K1`Dse6w$$e!RJ$eTiN zO8S(Icpi}f6_V~Jw+Bg@aY1x567@+$B`wd?**{R#&8&#VDSrS>ZKQu5iHAD1i_%lNzXV|&){54|I&`O~@f J-h?aK Date: Thu, 31 Jul 2025 14:33:14 -0300 Subject: [PATCH 03/13] =?UTF-8?q?[ADD]=20cria=C3=A7=C3=A3o=20dos=20schemas?= =?UTF-8?q?=20Film=20e=20FilmCreate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi-filmes/src/schemas/__init__.py | 1 + .../schemas/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 218 bytes .../src/schemas/__pycache__/film.cpython-313.pyc | Bin 0 -> 1125 bytes fastapi-filmes/src/schemas/film.py | 15 +++++++++++++++ 4 files changed, 16 insertions(+) create mode 100644 fastapi-filmes/src/schemas/__pycache__/__init__.cpython-313.pyc create mode 100644 fastapi-filmes/src/schemas/__pycache__/film.cpython-313.pyc create mode 100644 fastapi-filmes/src/schemas/film.py diff --git a/fastapi-filmes/src/schemas/__init__.py b/fastapi-filmes/src/schemas/__init__.py index e69de29bb..a133a4721 100644 --- a/fastapi-filmes/src/schemas/__init__.py +++ b/fastapi-filmes/src/schemas/__init__.py @@ -0,0 +1 @@ +from .film import Film, FilmCreate diff --git a/fastapi-filmes/src/schemas/__pycache__/__init__.cpython-313.pyc b/fastapi-filmes/src/schemas/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da47177539f2994b280e352724a35b7a1b9a18f7 GIT binary patch literal 218 zcmey&%ge<81UHs+XQ%+_#~=<2FhLogMSzT{48aV+jNS}hj75xIOhrrz4Czdo%r6;% zQkqP+Sllvma&K{gNav!|#FA7$P3BuHX+UlfGf+hl3y@gJ@EJ%l+_H7HiU}=FEh>)5 z$}dh$j7dsN&Q8rsiAhT=E=erN)CK8EEsiNJN{%T`&PdHoERKnf&&p*f%|tl%$GM4 zCa+OAV-bt^E4Iynud^1r%_8Rpi`8iw;Up9Gw=Q9>F;1L5S;^5w4v539Mxr>@Yatp~vPLLFNQ0e($#Rkul z{(#(%$(!}^B=2h*bP~|*JOe9+TEQ2#8wNIzGF1aDrL9Rh$U298l2@esaH#q-%>^mD z37EW}q&m$|?V21cvMNopT;)lY8e)?)w^yVzM75>Lb4d0MbB#KndQb3U_R-ek_^r`k z;&++RD&AA=_d4ywU4{1!lKWi@U7OfIJ2vfoJy0elYU5xC)S3b)E3=C5k&WujqwP_1 z^=M~QYaG9XwYkkgQ8}`-CYKQwiyAo~dTuCejf%DfTIG5ipji>~ZsafM`F_{FMxmEXF(y4DL(5Yodx&j z8MjdTZ^n@eUQdLct&@w5tt{;(y)mWsW|p_VEN>fRFrYEf%PL^~(huB3Kjh0)5RTbs z?b_+A4C_eD9tf5_0Mu2uNKlwAtMhfz5>VpvF$n5MFMbQ|wWbWAtU1|W18s0H?5GrH z5@>MBF)Er>cv|eD4W5*L!Be=6##Bag58*V1thFkIa9~N2QzyJUM literal 0 HcmV?d00001 diff --git a/fastapi-filmes/src/schemas/film.py b/fastapi-filmes/src/schemas/film.py new file mode 100644 index 000000000..a0998654d --- /dev/null +++ b/fastapi-filmes/src/schemas/film.py @@ -0,0 +1,15 @@ +from pydantic import BaseModel + +class FilmBase(BaseModel): + title: str + director: str + year: int + +class FilmCreate(FilmBase): + pass + +class Film(FilmBase): + id: int + + class Config: + orm_mode = True From 3244a5a591d9df8476070a93a4d427f905fde305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Augusto?= Date: Thu, 31 Jul 2025 14:33:51 -0300 Subject: [PATCH 04/13] =?UTF-8?q?[ADD]=20implementa=C3=A7=C3=A3o=20do=20se?= =?UTF-8?q?rvi=C3=A7o=20para=20manipular=20filmes=20em=20mem=C3=B3ria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi-filmes/src/services/__init__.py | 1 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 210 bytes .../__pycache__/film_service.cpython-313.pyc | Bin 0 -> 1354 bytes fastapi-filmes/src/services/film_service.py | 16 ++++++++++++++++ 4 files changed, 17 insertions(+) create mode 100644 fastapi-filmes/src/services/__pycache__/__init__.cpython-313.pyc create mode 100644 fastapi-filmes/src/services/__pycache__/film_service.cpython-313.pyc create mode 100644 fastapi-filmes/src/services/film_service.py diff --git a/fastapi-filmes/src/services/__init__.py b/fastapi-filmes/src/services/__init__.py index e69de29bb..b69b2867a 100644 --- a/fastapi-filmes/src/services/__init__.py +++ b/fastapi-filmes/src/services/__init__.py @@ -0,0 +1 @@ +from .film_service import FilmService diff --git a/fastapi-filmes/src/services/__pycache__/__init__.cpython-313.pyc b/fastapi-filmes/src/services/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7032198b796f0f779dd5ae5952a110ee315ffba0 GIT binary patch literal 210 zcmey&%ge<81fQ04XQ%_|#~=<2FhLog1%QmH48aV+jNS}hj75wJ4Czdo%r6;%!kUb? zxZN^ya)VQg$}*Ev{WO_w@uUIy@x>5_)QF07advQ#Bd}dx|NqoFsLFFwDo80`A(wtPgA`YN& VAnS`kj1SC=jEwgfREyYv8~`dGI643T literal 0 HcmV?d00001 diff --git a/fastapi-filmes/src/services/__pycache__/film_service.cpython-313.pyc b/fastapi-filmes/src/services/__pycache__/film_service.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78ee140e03f288255b0eb8c8c77e4e7daefbc3fd GIT binary patch literal 1354 zcmZuw&2QX96rUN79Vbp$P|{}EP+_4;t%({qBpezV0j)SBXb-XWgr$+|b+#@WXL;jA zYJ2IaN(EB6tQ2lNHn;u{oVa!QP@)lt1E<`g2B}xxv$yFc;z@pf-}ByY-ki+LOfjUN zpM8Jl7tYu}q`W3M5>A#;7_g9qe1olX;6BM>L#$h1txJ$TtFsSSXf;?US45QscE=8! zBT@oxbSwA@#uswh3~Q?J)+_Xj(AGcsD-=p32%P+3Dv+3i~*sPP<#0Vtn((&FfK1r=X*|J35V{ z+i~(unzy5^SQl|GyG8|49cf4+jH32*L^d_jmU{P$t5lX}g;Hj2Y>cJXRsuo4gX|$Y zyjb6rKX``=@9oMz-1-R`nQ3`y6djkLRc*Rz|1#2+s}ve`ki` z+6)dS-mFJUAZW5_MAim+PWvpdfw}?=WJb2Lq$u$;PTcF^A_NxH$XzepgomcVec!KO z_9}Erl+FA&$YTydS8pJD$o{O&4r^;a76+_LhHleY707^(E5O`R$8F;jUdAyzC3Qj4tz0>Y@ zNuE~f+kV^~d1?x~1YLBqJk58M0@_KSX(|LRl0! List[Film]: + return self._films + + def create_film(self, film_create: FilmCreate) -> Film: + film = Film(id=self._id_counter, **film_create.dict()) + self._films.append(film) + self._id_counter += 1 + return film From adf98a83258e25992f7574bf6ec7acbfb0923657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Augusto?= Date: Thu, 31 Jul 2025 14:34:22 -0300 Subject: [PATCH 05/13] =?UTF-8?q?[ADD]=20cria=C3=A7=C3=A3o=20das=20rotas?= =?UTF-8?q?=20da=20API=20para=20listar=20e=20adicionar=20filmes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi-filmes/src/api/__init__.py | 1 + .../src/api/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 184 bytes .../src/api/__pycache__/router.cpython-313.pyc | Bin 0 -> 1019 bytes fastapi-filmes/src/api/router.py | 15 +++++++++++++++ 4 files changed, 16 insertions(+) create mode 100644 fastapi-filmes/src/api/__pycache__/__init__.cpython-313.pyc create mode 100644 fastapi-filmes/src/api/__pycache__/router.cpython-313.pyc create mode 100644 fastapi-filmes/src/api/router.py diff --git a/fastapi-filmes/src/api/__init__.py b/fastapi-filmes/src/api/__init__.py index e69de29bb..237804332 100644 --- a/fastapi-filmes/src/api/__init__.py +++ b/fastapi-filmes/src/api/__init__.py @@ -0,0 +1 @@ +from .router import router diff --git a/fastapi-filmes/src/api/__pycache__/__init__.cpython-313.pyc b/fastapi-filmes/src/api/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ab72f21374d5412219092263eb3a519c79f3a09 GIT binary patch literal 184 zcmey&%ge<81a3>ZGh~4DV-N=hn4pZ$0zk%8hG2$ZMsEf$h9U+9hIA%P=9i2>;gyWH z*oyK?OHzycikLxshR+~fw=A8lVnT~ki;82i@{3awW0De+vs3d@V$u?eOA-q*b<;9) za#M?Aii?tCfZUk)_{_Y_lK6PNg34PQHo5sJr8%i~MI1nrK$aAP7$2A!85!>}NENXG FIRNecE&%`l literal 0 HcmV?d00001 diff --git a/fastapi-filmes/src/api/__pycache__/router.cpython-313.pyc b/fastapi-filmes/src/api/__pycache__/router.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e5f45db1763812ee1f36f83fd272d57ffe4b815e GIT binary patch literal 1019 zcma)4&rcIU6rR~%?RMLus8ri%8h!*y1R_ER#+Vq439T`P4F}^U&ALoWvCulR7$Gq! zdVqwp2}ih<|G__^go8Q>QKJ`bn4Y}(-gc=Ron+s8T$t>rqdGcXZkw};9ZUaX#DS5yP#UJd(1TjbF{Ttdo+c|= z_m*TikxQOpWsXYo;j{UL+Pcp;0PRUd_&G&tk1N$x8N)cond>vyvk=*gzo~d^)0ij$ z5>m%S0Su$gS*h|?YQ9?*n~`9f37{o86ZP7fV8zwi603R_wJHEUz}KFHFhH_k>5K0E zg4#{H$$1NdWJWE~L;4hMj-!&I=m?XEGV_aWwOS1H9g;S3<;>hYXAw$-vr-exbzZvO zE4H@elw9Gv^~x9)GvNsCIRG6V_%UAJkR~1%9L2A|K_-0SDuh$gAiu2CXKU!l8alRy zTiWm+p~1%cYbmf186G^I?!aDxPNyLGpH3Iabja`Y$`e4$DLe_2w7lS_1j2HZqacCR zS>6kg_nimi2t-}E14q`t{-a|n*V1yK;J)i%V~>o4UT~Bu4S%CvSu1mFNm}5`xbT+Q zsw+6YLT=!4JXr4dE_fuMb)4&{RKdHzHL2BW!tcnsg!Ic8P^ygHhgY#)We@l*fY^h0 z4dG0sl%9~ACnWWajGU0cA7<~~NOQP( Date: Thu, 31 Jul 2025 14:34:54 -0300 Subject: [PATCH 06/13] =?UTF-8?q?[ADD]=20configura=C3=A7=C3=A3o=20do=20Fas?= =?UTF-8?q?tAPI=20e=20inclus=C3=A3o=20das=20rotas=20principais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi-filmes/src/main.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 fastapi-filmes/src/main.py diff --git a/fastapi-filmes/src/main.py b/fastapi-filmes/src/main.py new file mode 100644 index 000000000..4396b932b --- /dev/null +++ b/fastapi-filmes/src/main.py @@ -0,0 +1,13 @@ +from fastapi import FastAPI +from src.api import router +from src.db import Base, engine + +app = FastAPI(title="API Filmes") + +Base.metadata.create_all(bind=engine) + +app.include_router(router) + +@app.get("/") +async def root(): + return {"message": "API Filmes rodando!"} From 17454cddfa070a14af33a3311714d69c0eb4aa06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Augusto?= Date: Thu, 31 Jul 2025 14:48:35 -0300 Subject: [PATCH 07/13] [ADD] testes para os endpoints da API FastAPI usando ASGITransport e httpx --- .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 152 bytes .../test_films.cpython-313-pytest-8.4.1.pyc | Bin 0 -> 8641 bytes fastapi-filmes/tests/test_films.py | 41 ++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 fastapi-filmes/tests/__pycache__/__init__.cpython-313.pyc create mode 100644 fastapi-filmes/tests/__pycache__/test_films.cpython-313-pytest-8.4.1.pyc create mode 100644 fastapi-filmes/tests/test_films.py diff --git a/fastapi-filmes/tests/__pycache__/__init__.cpython-313.pyc b/fastapi-filmes/tests/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed9a32a3044583c0084d97d3ff07a64ec9c823a0 GIT binary patch literal 152 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 1 + From 02cd6c264774f5bf6490effbe745b4ab80832708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Augusto?= Date: Thu, 31 Jul 2025 15:32:14 -0300 Subject: [PATCH 08/13] [ADD] adicionar rota GET /filmes/{id} para buscar filme por ID" --- fastapi-filmes/src/api/router.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/fastapi-filmes/src/api/router.py b/fastapi-filmes/src/api/router.py index 946feeb53..bc9a6b138 100644 --- a/fastapi-filmes/src/api/router.py +++ b/fastapi-filmes/src/api/router.py @@ -1,15 +1,31 @@ -from fastapi import APIRouter +from fastapi import APIRouter, Depends from typing import List +from src.db.database import SessionLocal +from sqlalchemy.orm import Session from src.schemas import Film, FilmCreate from src.services import FilmService -router = APIRouter(prefix="/films", tags=["Films"]) -film_service = FilmService() +router = APIRouter(prefix="/filmes", tags=["Filmes"]) -@router.get("/filmes", response_model=List[Film]) -async def get_films(): + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + +@router.get("/", response_model=List[Film]) +async def get_films(db: Session = Depends(get_db)): + film_service = FilmService(db) return film_service.get_all_films() -@router.post("/filmes", response_model=Film) -async def create_film(film: FilmCreate): +@router.post("/", response_model=Film) +async def create_film(film: FilmCreate,db: Session = Depends(get_db)): + film_service = FilmService(db) return film_service.create_film(film) + +@router.get("/{id}") +def get_film(id: int, db: Session = Depends(get_db)): + service = FilmService(db) + return service.get_film(id) From 4597de9dbd765104a2beea655b58b70d686a12b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Augusto?= Date: Thu, 31 Jul 2025 15:34:11 -0300 Subject: [PATCH 09/13] =?UTF-8?q?[FIX]=20corrigir=20importa=C3=A7=C3=B5es,?= =?UTF-8?q?=20remover=20uso=20de=20'films'=20no=20service=20e=20ajustes=20?= =?UTF-8?q?no=20banco=20SQLite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi-filmes/src/models/film.py | 10 ++++++++ fastapi-filmes/src/services/film_service.py | 26 ++++++++++++--------- 2 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 fastapi-filmes/src/models/film.py diff --git a/fastapi-filmes/src/models/film.py b/fastapi-filmes/src/models/film.py new file mode 100644 index 000000000..5cc7fca03 --- /dev/null +++ b/fastapi-filmes/src/models/film.py @@ -0,0 +1,10 @@ +from sqlalchemy import Column, Integer, String +from src.db.database import Base + +class FilmModel(Base): + __tablename__ = "films" + + id = Column(Integer, primary_key=True, index=True) + title = Column(String, index=True) + director = Column(String, index=True) + year = Column(Integer) diff --git a/fastapi-filmes/src/services/film_service.py b/fastapi-filmes/src/services/film_service.py index 8664fc188..14d7447fd 100644 --- a/fastapi-filmes/src/services/film_service.py +++ b/fastapi-filmes/src/services/film_service.py @@ -1,16 +1,20 @@ -from typing import List -from src.schemas import FilmCreate, Film +from sqlalchemy.orm import Session +from src.schemas import FilmCreate +from src.models.film import FilmModel class FilmService: - def __init__(self): - self._films = [] - self._id_counter = 1 + def __init__(self, db: Session): + self.db = db - def get_all_films(self) -> List[Film]: - return self._films + def get_all_films(self): + return self.db.query(FilmModel).all() - def create_film(self, film_create: FilmCreate) -> Film: - film = Film(id=self._id_counter, **film_create.dict()) - self._films.append(film) - self._id_counter += 1 + def create_film(self, film_create: FilmCreate): + film = FilmModel(**film_create.dict()) + self.db.add(film) + self.db.commit() + self.db.refresh(film) return film + + def get_film(self, id: int): + return self.db.query(FilmModel).filter(FilmModel.id == id).first() From ee997a887d1b2bd3d5472e7cc3e60181cb7facab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Augusto?= Date: Thu, 31 Jul 2025 15:34:41 -0300 Subject: [PATCH 10/13] [ADD] teste para rota GET por ID --- fastapi-filmes/tests/test_films.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fastapi-filmes/tests/test_films.py b/fastapi-filmes/tests/test_films.py index b61693b49..e6cefeede 100644 --- a/fastapi-filmes/tests/test_films.py +++ b/fastapi-filmes/tests/test_films.py @@ -39,3 +39,17 @@ async def test_get_films(): assert isinstance(data, list) assert len(data) >= 1 +@pytest.mark.asyncio +async def test_get_film_by_id(): + transport = ASGITransport(app=app) + async with AsyncClient(transport=transport, base_url="http://test") as ac: + # Cria um filme para garantir que existe + film_data = {"title": "Matrix", "director": "Wachowski", "year": 1999} + post_response = await ac.post("/filmes/", json=film_data) + id = post_response.json()["id"] # sobrescrevendo apenas aqui + + # Busca pelo ID + get_response = await ac.get(f"/filmes/{id}") + assert get_response.status_code == 200 + + From a44108c23e22b7013d0e5edb012416bd1df02178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Augusto?= Date: Thu, 31 Jul 2025 15:35:22 -0300 Subject: [PATCH 11/13] =?UTF-8?q?[ADD]=20Docker=20e=20docker-compose=20par?= =?UTF-8?q?a=20subir=20a=20aplica=C3=A7=C3=A3o=20em=20container?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi-filmes/Dockerfile | 20 ++++++++++++++++++++ fastapi-filmes/docker-compose.yml | 12 ++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 fastapi-filmes/Dockerfile create mode 100644 fastapi-filmes/docker-compose.yml diff --git a/fastapi-filmes/Dockerfile b/fastapi-filmes/Dockerfile new file mode 100644 index 000000000..2fbc1f8b6 --- /dev/null +++ b/fastapi-filmes/Dockerfile @@ -0,0 +1,20 @@ +# Use imagem oficial do Python 3.11 (ou outra versão compatível) +FROM python:3.11-slim + +# Defina diretório de trabalho no container +WORKDIR /app + +# Copie arquivos de dependências +COPY requirements.txt . + +# Instale dependências +RUN pip install --no-cache-dir -r requirements.txt + +# Copie o restante do código +COPY . . + +# Exponha a porta que a FastAPI vai rodar +EXPOSE 8000 + +# Comando para rodar a aplicação +CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/fastapi-filmes/docker-compose.yml b/fastapi-filmes/docker-compose.yml new file mode 100644 index 000000000..c71cff22a --- /dev/null +++ b/fastapi-filmes/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.8" + +services: + api: + build: . + ports: + - "8000:8000" + volumes: + - ./:/app + environment: + - PYTHONUNBUFFERED=1 + command: uvicorn src.main:app --host 0.0.0.0 --port 8000 --reload From d3b2d8eb96c337e22467fb80f6af6363e655cdf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Augusto?= Date: Thu, 31 Jul 2025 15:36:06 -0300 Subject: [PATCH 12/13] =?UTF-8?q?[ADD]=20git=20ignore=20basico=20para=20n?= =?UTF-8?q?=C3=A3o=20subir=20muitas=20coisas=20da=20aplica=C3=A7=C3=A3o=20?= =?UTF-8?q?e=20requirements.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi-filmes/.gitignore | 80 +++++++++++++++++++++++++++++++++ fastapi-filmes/requirements.txt | 7 +++ 2 files changed, 87 insertions(+) create mode 100644 fastapi-filmes/.gitignore create mode 100644 fastapi-filmes/requirements.txt diff --git a/fastapi-filmes/.gitignore b/fastapi-filmes/.gitignore new file mode 100644 index 000000000..e4d82faf2 --- /dev/null +++ b/fastapi-filmes/.gitignore @@ -0,0 +1,80 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Jupyter Notebook +.ipynb_checkpoints + +# Environments +.env +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# VSCode settings +.vscode/ + +# Docker +*.log +docker-compose.override.yml +docker-compose.override.yaml + +# SQLite database file +*.db +*.sqlite3 + +# Prisma client (se estiver usando) +node_modules/ +prisma/client/ + +# Logs +logs/ +*.log + +# MacOS +.DS_Store + +# Windows +Thumbs.db diff --git a/fastapi-filmes/requirements.txt b/fastapi-filmes/requirements.txt new file mode 100644 index 000000000..770e0ba53 --- /dev/null +++ b/fastapi-filmes/requirements.txt @@ -0,0 +1,7 @@ +fastapi +uvicorn +sqlalchemy +pydantic +pytest +pytest-asyncio +httpx From ad7b03bbd53769e699c794f1df49b30dac6a66f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Augusto?= Date: Thu, 31 Jul 2025 15:57:54 -0300 Subject: [PATCH 13/13] =?UTF-8?q?[ADD]=20Documenta=C3=A7=C3=A3o=20da=20apl?= =?UTF-8?q?ica=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 ------ fastapi-filmes/README.md | 109 ++++++++++++++++++ .../api/__pycache__/router.cpython-313.pyc | Bin 1019 -> 1877 bytes .../__pycache__/film_service.cpython-313.pyc | Bin 1354 -> 1862 bytes .../test_films.cpython-313-pytest-8.4.1.pyc | Bin 8641 -> 10187 bytes requirements.txt | 0 6 files changed, 109 insertions(+), 37 deletions(-) delete mode 100644 README.md create mode 100644 fastapi-filmes/README.md delete mode 100644 requirements.txt diff --git a/README.md b/README.md deleted file mode 100644 index 5c3393a97..000000000 --- a/README.md +++ /dev/null @@ -1,37 +0,0 @@ -![WATTIO](http://wattio.com.br/web/image/1204-212f47c3/Logo%20Wattio.png) - -#### Descrição - -O desafio consiste em implementar um CRUD de filmes, utilizando [python](https://www.python.org/ "python") integrando com uma API REST e uma possível persistência de dados. - -Rotas da API: - - - `/filmes` - [GET] deve retornar todos os filmes cadastrados. - - `/filmes` - [POST] deve cadastrar um novo filme. - - `/filmes/{id}` - [GET] deve retornar o filme com ID especificado. - -O Objetivo é te desafiar e reconhecer seu esforço para aprender e se adaptar. Qualquer código enviado, ficaremos muito felizes e avaliaremos com toda atenção! - -#### Sugestão de Ferramentas -Não é obrigatório utilizar todas as as tecnologias sugeridas, mas será um diferencial =] - -- Orientação a objetos (utilizar objetos, classes para manipular os filmes) -- [FastAPI](https://fastapi.tiangolo.com/) (API com documentação auto gerada) -- [Docker](https://www.docker.com/) / [Docker-compose](https://docs.docker.com/compose/install/) (Aplicação deverá ficar em um container docker, e o start deverá seer com o comando ``` docker-compose up ``` -- Integração com banco de dados (persistir as informações em json (iniciante) /[SqLite](https://www.sqlite.org/index.html) / [SQLAlchemy](https://fastapi.tiangolo.com/tutorial/sql-databases/#sql-relational-databases) / outros DB) - - -#### Como começar? - -- Fork do repositório -- Criar branch com seu nome ``` git checkout -b feature/ana ``` -- Faça os commits de suas alterações ``` git commit -m "[ADD] Funcionalidade" ``` -- Envie a branch para seu repositório ``` git push origin feature/ana ``` -- Navegue até o [Github](https://github.com/), crie seu Pull Request apontando para a branch **```main```** -- Atualize o README.md descrevendo como subir sua aplicação - -#### Dúvidas? - -Qualquer dúvida / sugestão / melhoria / orientação adicional só enviar email para hendrix@wattio.com.br - -Salve! diff --git a/fastapi-filmes/README.md b/fastapi-filmes/README.md new file mode 100644 index 000000000..5147e79b5 --- /dev/null +++ b/fastapi-filmes/README.md @@ -0,0 +1,109 @@ +# 🎬 FastAPI Filmes +API de gerenciamento de filmes desenvolvida em Python utilizando FastAPI, SQLAlchemy e SQLite. +O projeto foi estruturado seguindo boas práticas de orientação a objetos e inclui testes automatizados com pytest, além de suporte a Docker e docker-compose. + +--- + +## 🚀 Tecnologias Utilizadas +- FastAPI – Framework para APIs rápidas + +- SQLAlchemy – ORM para persistência de dados + +- SQLite – Banco de dados leve + +- Pydantic – Validação de dados + +- Docker – Containerização + +- Pytest – Testes automatizados + +--- + +## 📂 Estrutura do Projeto +```bash +fastapi-filmes/ +│── src/ +│ ├── api/ # Rotas da aplicação +│ ├── core/ # Configurações +│ ├── db/ # Banco de dados +│ ├── schemas/ # Schemas Pydantic +│ ├── models/ # Tabelas do banco +│ ├── services/ # Regras de negócio +│ └── main.py # Ponto de entrada da API +│ +│── tests/ # Testes automatizados +│── Dockerfile +│── docker-compose.yml +│── requirements.txt +│── README.md +└── .gitignore +``` + +## ⚙️ Configuração do Ambiente +### 1️⃣ Clonar o repositório +```bash +git clone https://github.com/seu-usuario/fastapi-filmes.git +cd fastapi-filmes +``` + +### 2️⃣ Criar ambiente virtual (opcional) +```bash +python -m venv venv +source venv/bin/activate # Linux/Mac +venv\Scripts\activate # Windows +``` + +### 3️⃣ Instalar dependências +```bash +pip install -r requirements.txt +``` + +--- + +### ▶️ Executando a API Localmente +```bash +uvicorn src.main:app --reload +``` +Acesse: + +- Swagger: http://localhost:8000/docs + +- ReDoc: http://localhost:8000/redoc + +## 🐳 Executando com Docker +### 1️⃣ Build da imagem +```bash +docker-compose up --build +``` + +A API estará disponível em http://localhost:8000 + +## 🧪 Rodando Testes +Para rodar os testes unitários: + +```bash +pytest tests/ --asyncio-mode=auto -v +``` + +##📌 Endpoints Disponíveis +- GET /filmes + +Retorna a lista de filmes. + +- POST /filmes + +Cria um novo filme. + +Exemplo: + +```json +{ + "title": "Matrix", + "director": "Wachowski", + "year": 1999 +} +``` + +- GET /filmes/{id} + +Retorna um filme específico pelo ID. \ No newline at end of file diff --git a/fastapi-filmes/src/api/__pycache__/router.cpython-313.pyc b/fastapi-filmes/src/api/__pycache__/router.cpython-313.pyc index e5f45db1763812ee1f36f83fd272d57ffe4b815e..a7d9ad3b57834850f70c5f64f7ef5e9b91930446 100644 GIT binary patch literal 1877 zcmb7E&2Jl35PxrXyqPc-kW(d^X4}n zTMq{P1Xlaw^_%xaLViJ}wdCfZK1T_8Ky;$hBuP+469N-7DncNGqQO(nnI{HhDlb*ylx1J+@k!8d)L( z|EZHDuZsO7OQ*yveMoP_1cnXgc+&YIM>s-ekz){=Gc|kJNO6mCV;QbYW6?~10I4#v zCb!K}?m`2p!CBe>)?jWzCrYc1ykyx6nMD=`H!6I549GK5CO`VR*ZaydH9_=*cSG%; zD?cgzuawV}FZ_S335~;FybF}?snhgJX^?!=F-2plTw zzU>5%TYxlS{kZ^&n#MX&--r6HE;|D}%J?}Tn2BG*eHHfY>*cv!wdaXCysZxJsH0VB z^iPgWaqny&v?H3P(P3*HK$`){325{HnvfDsgZ2XaDd5z!+s|&iF_A^!1s5NC8BQEu z)|$B3S-eBigIE1PP9hrIRtGn(?x?Y<6l-ES*H76+WAE7-Bx=mGdpU3RqC(9 zJ-HEk$?-NPfuS>og>z(kwUjF?G5lvbZ7>=WnZ=2WX`72CyzEX|h<}(j^XZ$`%IZXs ztuQaDs;J6QXmX~YJ0FSzuDID-jV_cMbLI}0BLYabxg4of#W4KrnknoeUoMRH`znO zwGa`MPa}6DYqJ~D#=GD7%aJNQzZZ>djcmrB6M>FZyuagC7m4snO@LEPL6hFQy=(eL zSGl)JNA@PKeE;5~1ymoeBx<;AO`+8|T8*OBc*6=Gy1qlZ_J+=HUE6#cEwr!0XrWa` P(LzIu?&n~kxiJ3*o9caX literal 1019 zcma)4&rcIU6rR~%?RMLus8ri%8h!*y1R_ER#+Vq439T`P4F}^U&ALoWvCulR7$Gq! zdVqwp2}ih<|G__^go8Q>QKJ`bn4Y}(-gc=Ron+s8T$t>rqdGcXZkw};9ZUaX#DS5yP#UJd(1TjbF{Ttdo+c|= z_m*TikxQOpWsXYo;j{UL+Pcp;0PRUd_&G&tk1N$x8N)cond>vyvk=*gzo~d^)0ij$ z5>m%S0Su$gS*h|?YQ9?*n~`9f37{o86ZP7fV8zwi603R_wJHEUz}KFHFhH_k>5K0E zg4#{H$$1NdWJWE~L;4hMj-!&I=m?XEGV_aWwOS1H9g;S3<;>hYXAw$-vr-exbzZvO zE4H@elw9Gv^~x9)GvNsCIRG6V_%UAJkR~1%9L2A|K_-0SDuh$gAiu2CXKU!l8alRy zTiWm+p~1%cYbmf186G^I?!aDxPNyLGpH3Iabja`Y$`e4$DLe_2w7lS_1j2HZqacCR zS>6kg_nimi2t-}E14q`t{-a|n*V1yK;J)i%V~>o4UT~Bu4S%CvSu1mFNm}5`xbT+Q zsw+6YLT=!4JXr4dE_fuMb)4&{RKdHzHL2BW!tcnsg!Ic8P^ygHhgY#)We@l*fY^h0 z4dG0sl%9~ACnWWajGU0cA7<~~NOQP(976ra(zEqj$VHt`2-w;>@2v)j zW{9WHLpBs{tQ)TR3{972rpJVcyB#n`zl@XiZO+1!fj5HQSKTH{BAi-Ek%XKsKxCKr zgj0`j#iLyH6kq#T@zfb|SK~Ss<%XyGCd3RGvpmxu0nd`&s5ipx^-I0?tMEU)0BV zxbW|G1q=PPF#3*lntm%3X?QQLwV*f?zTlA$v#)L`vdt48|_G^9m!6UMH*c zJOs+UEznPBM*-wsAp$81IS-Rnr9`ZXc%Qcl_cSDBwf|j%_zuZHCFKen!{vSA8aeJ- zGmg@n4mE5xGb8G@+i{wioV7R;8?Guj;g=w3rb!kMplY=s$j(ue{A8ET(sO2D3|5E+ zs7K^)XX?PI?|pVOJ9jWU*PpF{_gFi&#&*n}xnuY2UltCn>C&(CDu>oo={Y@TPdl{U z0fHafIg?7<93T*3lmtEj`Eu61anE5*6~o-vgDYaVOd31qMSQ8_gVyz)o2r>IkspChughyUeA#;7_g9qe1olX;6BM>L#$h1txJ$TtFsSSXf;?US45QscE=8! zBT@oxbSwA@#uswh3~Q?J)+_Xj(AGcsD-=p32%P+3Dv+3i~*sPP<#0Vtn((&FfK1r=X*|J35V{ z+i~(unzy5^SQl|GyG8|49cf4+jH32*L^d_jmU{P$t5lX}g;Hj2Y>cJXRsuo4gX|$Y zyjb6rKX``=@9oMz-1-R`nQ3`y6djkLRc*Rz|1#2+s}ve`ki` z+6)dS-mFJUAZW5_MAim+PWvpdfw}?=WJb2Lq$u$;PTcF^A_NxH$XzepgomcVec!KO z_9}Erl+FA&$YTydS8pJD$o{O&4r^;a76+_LhHleY707^(E5O`R$8F;jUdAyzC3Qj4tz0>Y@ zNuE~f+kV^~d1?x~1YLBqJk58M0@_KSX(|LRl0!La$-O)a(ftEm%jZ4!`cFG^Z`aNAg3yEu{= zxrIQ%X?=*Gy@Y|bfl`~EY??!Y2_&V3`XDIDp;Aqe(aAZL+?*JiLv!dn*^x^gyFcE? zf8NZ_%x<55G~qZpFrrcXL+)wX2hGeH{JG)4^ME7V6?Xfr4nr#m{oqfpLV2<>b+**`E=jA9`bc!=97U z*iha#(_BV4tQwAKH@#_z(Cc28?07lPl#fm-Uiz=!M!)41?4W1FC`94}W&@g`EN1Yb zoW+mTq-LlZ-y^aW+YG1S%GqAW&8TfU__FKtasN=W|9hg}A`auF8?@nx`Aj6CF`OM} zGAg0$GxjR9$JxDNqG3LY=I~qm98S5j1a^kNMiU$04L~0t4S@SX`T+xgVSoaF`H7RE zVK?*3GbCSHDi%t4;)c8raDpMF64)HVz6BA!>5Twiqe?ill9CBrRuTuCV`wN##nS(p zHK#M6ngGBlkQCrFpo^gd=NUUT(#zsT`*NPOfpyI-EMA@Y_}a|EEXlD7j4KIjv!9^r zi0*|uR`w$CI~RZV)I8a;Z8s8IGjEyepWM0jomq{H)cqr6WmgH*m1s?g?kFeMPt}w} zncEi;Q(n4tX?=c!Y|O4t?1aKY>8S9OFwrZ=QysbV>{~DGZA&1!%rpT&pcO;(yUTd$6l07 z_UT`S^dbBM*8Sod&%7T4`sGgk7pw=xUp2jx-|oVCMBMH^A7nwrDlJ w-zEzz2e-fUh}uv7l-%d6II!CSLsoyaG_bc%7K#wkXYbVf~%&3a5dd~BKL*%=r#Cm#^d zVbq-bRzQo2KczG$)vhQB$YliL;