Skip to content

Commit 5bf2dc7

Browse files
committed
Naive, initial implementation
1 parent d5e5301 commit 5bf2dc7

File tree

6 files changed

+191
-29
lines changed

6 files changed

+191
-29
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""newest
2+
3+
Revision ID: db0d3f70134d
4+
Revises:
5+
Create Date: 2022-04-30 15:34:25.368352
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'db0d3f70134d'
14+
down_revision = None
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table('owners',
22+
sa.Column('id', sa.BigInteger().with_variant(sa.Integer(), 'sqlite'), nullable=False),
23+
sa.Column('client_id', sa.String(length=255), nullable=False),
24+
sa.Column('name', sa.String(length=255), nullable=False),
25+
sa.PrimaryKeyConstraint('id'),
26+
sa.UniqueConstraint('client_id')
27+
)
28+
op.create_table('resources',
29+
sa.Column('id', sa.BigInteger().with_variant(sa.Integer(), 'sqlite'), nullable=False),
30+
sa.Column('owner_id', sa.Integer(), nullable=False),
31+
sa.Column('name', sa.String(length=255), nullable=False),
32+
sa.ForeignKeyConstraint(['owner_id'], ['owners.id'], ),
33+
sa.PrimaryKeyConstraint('id')
34+
)
35+
# ### end Alembic commands ###
36+
37+
38+
def downgrade():
39+
# ### commands auto generated by Alembic - please adjust! ###
40+
op.drop_table('resources')
41+
op.drop_table('owners')
42+
# ### end Alembic commands ###

mrmat_python_api_fastapi/apis/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,13 @@
1919
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
# SOFTWARE.
22+
23+
from pydantic import BaseModel
24+
25+
26+
class StatusSchema(BaseModel):
27+
"""
28+
A generic message class
29+
"""
30+
code: int
31+
msg: str

mrmat_python_api_fastapi/apis/resource/v1/api.py

Lines changed: 124 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,53 +20,154 @@
2020
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
# SOFTWARE.
2222

23-
from fastapi import APIRouter, Depends
23+
from typing import Union, List
24+
from fastapi import APIRouter, Depends, status
25+
from fastapi.responses import JSONResponse, Response
2426
from sqlalchemy.orm import Session
2527

2628
from mrmat_python_api_fastapi.db import get_db
27-
from .db import Resource
28-
from .schema import ResourceSchema
29+
from mrmat_python_api_fastapi.apis import StatusSchema
30+
from .db import Resource, Owner
31+
from .schema import ResourceSchema, ResourceInputSchema, OwnerSchema, OwnerInputSchema
2932

3033
router = APIRouter()
3134

3235

33-
@router.get('/',
36+
@router.get('/resources',
3437
name='list_resources',
3538
summary='List all known resources',
3639
description='Returns all currently known resources and their metadata',
37-
response_model=list[ResourceSchema])
40+
response_model=List[ResourceSchema])
3841
async def get_all(db: Session = Depends(get_db)):
39-
resources = db.query(Resource).all()
40-
return resources
42+
return db.query(Resource).all()
4143

4244

43-
@router.get('/{resource_id}',
45+
@router.get('/resources/{identity}',
4446
name='get_resource',
4547
summary='Get a single resource',
46-
description='Return a single resource identified by its resource id')
47-
async def get(resource_id: int):
48-
pass
48+
description='Return a single resource identified by its resource id',
49+
response_model=Union[ResourceSchema, StatusSchema])
50+
async def get(identity: int, db: Session = Depends(get_db)):
51+
resource = db.query(Resource).get(identity).one_or_none()
52+
if not resource:
53+
return JSONResponse(status_code=404,
54+
content=StatusSchema(code=404, msg='Not found').dict())
55+
return resource
4956

5057

51-
@router.post('/',
58+
@router.post('/resources',
5259
name='create_resource',
5360
summary='Create a resource',
54-
description='Create a resource owned by the authenticated user')
55-
async def create():
56-
pass
61+
description='Create a resource owned by the authenticated user',
62+
response_model=Union[ResourceSchema, StatusSchema])
63+
async def create(data: ResourceInputSchema, db: Session = Depends(get_db)):
64+
resource = Resource(name=data.name)
65+
db.add(resource)
66+
db.commit()
67+
return resource
5768

5869

59-
@router.put('/{resource_id}',
70+
@router.put('/resources/{identity}',
6071
name='modify_resource',
6172
summary='Modify a resource',
62-
description='Modify a resource by its resource id')
63-
async def modify(resource_id: int):
64-
pass
73+
description='Modify a resource by its resource id',
74+
response_model=Union[ResourceSchema, StatusSchema])
75+
async def modify(identity: int, data: ResourceInputSchema, db: Session = Depends(get_db)):
76+
resource = db.query(Resource).get(identity)
77+
if not resource:
78+
return JSONResponse(status_code=404,
79+
content=StatusSchema(code=404, msg='Not found').dict())
80+
resource.name = data.name
81+
db.add(resource)
82+
db.commit()
83+
return resource
6584

6685

67-
@router.delete('/{resource_id}',
86+
@router.delete('/resources/{identity}',
6887
name='remove_resource',
6988
summary='Remove a resource',
70-
description='Remove a resource by its resource id')
71-
async def remove(resource_id: int):
72-
pass
89+
description='Remove a resource by its resource id',
90+
status_code=204,
91+
responses={
92+
status.HTTP_204_NO_CONTENT: {'description': 'The resource was removed'},
93+
status.HTTP_410_GONE: {'description': 'The resource was already gone'}
94+
})
95+
async def remove(identity: int, response: Response, db: Session = Depends(get_db)):
96+
resource = db.query(Resource).get(identity)
97+
if not resource:
98+
return JSONResponse(status_code=204,
99+
content=StatusSchema(code=404, msg='Not found').dict())
100+
db.delete(resource)
101+
db.commit()
102+
response.status_code = 410
103+
return {}
104+
105+
106+
107+
@router.get('/owners',
108+
name='list_owners',
109+
summary='List all known owners',
110+
description='Returns all currently known owners and their metadata',
111+
response_model=List[OwnerSchema])
112+
async def get_all(db: Session = Depends(get_db)):
113+
return db.query(Owner).all()
114+
115+
116+
@router.get('/owners/{identity}',
117+
name='get_owner',
118+
summary='Get a single owner',
119+
description='Return a single owner identified by its owner id',
120+
response_model=Union[OwnerSchema, StatusSchema])
121+
async def get(identity: int, db: Session = Depends(get_db)):
122+
owner = db.query(Owner).get(identity)
123+
if not owner:
124+
return JSONResponse(status_code=404,
125+
content=StatusSchema(code=404, msg='Not found').dict())
126+
return owner
127+
128+
129+
@router.post('/owners',
130+
name='create_owner',
131+
summary='Create a owner',
132+
description='Create a owner',
133+
response_model=Union[OwnerSchema, StatusSchema])
134+
async def create(data: OwnerInputSchema, db: Session = Depends(get_db)):
135+
owner = Owner(name=data.name, client_id='TODO')
136+
db.add(owner)
137+
db.commit()
138+
return owner
139+
140+
141+
@router.put('/owners/{identity}',
142+
name='modify_owner',
143+
summary='Modify a owner',
144+
description='Modify a owner by its owner id',
145+
response_model=Union[OwnerSchema, StatusSchema])
146+
async def modify(identity: int, data: ResourceInputSchema, db: Session = Depends(get_db)):
147+
owner = db.query(Owner).get(identity)
148+
if not owner:
149+
return JSONResponse(status_code=404,
150+
content=StatusSchema(code=404, msg='Not found').dict())
151+
owner.name = data.name
152+
db.add(owner)
153+
db.commit()
154+
return owner
155+
156+
157+
@router.delete('/owners/{identity}',
158+
name='remove_owner',
159+
summary='Remove a owner',
160+
description='Remove a owner by its owner id',
161+
status_code=204,
162+
responses={
163+
status.HTTP_204_NO_CONTENT: {'description': 'The owner was removed'},
164+
status.HTTP_410_GONE: {'description': 'The owner was already gone'}
165+
})
166+
async def remove(identity: int, response: Response, db: Session = Depends(get_db)):
167+
owner = db.query(Owner).get(identity)
168+
if not owner:
169+
response.status_code = status.HTTP_410_GONE
170+
return
171+
db.delete(owner)
172+
db.commit()
173+
response.status_code = status.HTTP_204_NO_CONTENT

mrmat_python_api_fastapi/apis/resource/v1/db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# SOFTWARE.
2222

2323
from sqlalchemy import ForeignKey, Column, Integer, String, UniqueConstraint, BigInteger
24-
from sqlalchemy.orm import relationship
24+
from sqlalchemy.orm import relationship, Session
2525

2626
from mrmat_python_api_fastapi import Base
2727

mrmat_python_api_fastapi/apis/resource/v1/schema.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,22 @@
2323
from pydantic import BaseModel
2424

2525

26-
class OwnerSchema(BaseModel):
27-
id: int
26+
class OwnerInputSchema(BaseModel):
2827
name: str
2928

29+
30+
class OwnerSchema(OwnerInputSchema):
31+
id: int
32+
3033
class Config:
3134
orm_mode = True
3235

3336

34-
class ResourceSchema(BaseModel):
35-
id: int
37+
class ResourceInputSchema(BaseModel):
38+
name: str
39+
40+
41+
class ResourceSchema(ResourceInputSchema):
3642
name: str
3743

3844
class Config:

mrmat_python_api_fastapi/config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,17 @@
2727

2828

2929
class Config:
30+
"""
31+
A class to deal with application configuration
32+
"""
3033
secret_key: str = secrets.token_urlsafe(16)
3134
db_url: str = 'sqlite://'
3235

3336
@staticmethod
3437
def from_json_file(file: Optional[os.PathLike] = os.getenv('APP_CONFIG')):
3538
runtime_config = Config()
3639
if file and os.path.exists(file):
37-
with open(file, 'r') as c:
40+
with open(file, 'r', encoding='UTF-8') as c:
3841
file_config = json.load(c)
3942
runtime_config.secret_key = file_config.get('secret_key', secrets.token_urlsafe(16))
4043
runtime_config.db_url = file_config.get('db_url', 'sqlite://')

0 commit comments

Comments
 (0)