How to get separately column from sqlalchemy relationship using pydantic schema - python

I have 4 tables: Hardware, SoftwareName, SoftwareVersion, and Software.
The Software table has an one-to-many relationship with SoftwareName table and SoftwareVersion table. Finally, the Hardware model has an one-to-many relationship with Software table.
I'm trying to get just a specific column from a model relationship using Pydantic Schema.
Now I'm getting this output:
[
{
"id": 1,
"hostname": "hostname2",
"softwares": [
{
"id": 1,
"software_name": {
"id": 1,
"name": "nginx"
},
"software_version": {
"id": 1,
"version": "2.9"
}
},
{
"id": 2,
"software_name": {
"id": 2,
"name": "vim"
},
"software_version": {
"id": 2,
"version": "0.3"
}
},
{
"id": 3,
"software_name": {
"id": 3,
"name": "apache"
},
"software_version": {
"id": 3,
"version": "1.0"
}
}
]
}
]
But what I expect is this output:
[
{
"id": 1,
"hostname": "hostname2",
"softwares": [
{
"id": 1,
"name": "nginx",
"version": "2.9"
},
{
"id": 2,
"name": "vim",
"version": "0.3"
},
{
"id": 3,
"name": "apache",
"version": "1.0"
}
]
}
]
I have the file main.py:
import uvicorn
from typing import Any, Iterator, List, Optional
from faker import Faker
from fastapi import Depends, FastAPI
from pydantic import BaseModel
from sqlalchemy import Column, ForeignKey, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session, sessionmaker, relationship
from faker.providers import DynamicProvider
software_name = DynamicProvider(
provider_name="software_name",
elements=["bash", "vim", "vscode", "nginx", "apache"],
)
software_version = DynamicProvider(
provider_name="software_version",
elements=["1.0", "2.9", "1.1", "0.3", "2.0"],
)
hardware = DynamicProvider(
provider_name="hardware",
elements=["hostname1", "hostname2", "hostname3", "hostname4", "hostname5"],
)
fake = Faker()
# then add new provider to faker instance
fake.add_provider(software_name)
fake.add_provider(software_version)
fake.add_provider(hardware)
engine = create_engine("sqlite:///.db", connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=True, autoflush=True, bind=engine)
Base = declarative_base(bind=engine)
class Software(Base):
__tablename__ = 'software'
id = Column(Integer, primary_key=True)
hardware_id = Column(Integer, ForeignKey('hardware.id'))
name_id = Column(Integer, ForeignKey('software_name.id'))
version_id = Column(Integer, ForeignKey('software_version.id'))
software_name = relationship('SoftwareName', backref='software_name')
software_version = relationship('SoftwareVersion',
backref='software_version')
class SoftwareName(Base):
__tablename__ = 'software_name'
id = Column(Integer, primary_key=True)
name = Column(String)
class SoftwareVersion(Base):
__tablename__ = 'software_version'
id = Column(Integer, primary_key=True)
version = Column(String)
class Hardware(Base):
__tablename__ = "hardware"
id = Column(Integer, primary_key=True, autoincrement=True)
hostname = Column(String, nullable=False)
softwares = relationship(Software)
Base.metadata.drop_all()
Base.metadata.create_all()
class BaseSchema(BaseModel):
id: int
class Config:
orm_mode = True
class SoftwareNameSchema(BaseSchema):
name: str
class SoftwareVersionSchema(BaseSchema):
version: str
class SoftwareSchema(BaseSchema):
software_name: SoftwareNameSchema
software_version: SoftwareVersionSchema
class HardwareOut(BaseSchema):
hostname: str
softwares: List[SoftwareSchema]
app = FastAPI()
#app.on_event("startup")
def on_startup() -> None:
session = SessionLocal()
for _ in range(10):
software_list = []
for _ in range(3):
sn = SoftwareName(name=fake.software_name())
sv = SoftwareVersion(version=fake.software_version())
s = Software(software_name=sn, software_version=sv)
software_list.append(s)
h = Hardware(hostname=fake.hardware(), softwares=software_list)
session.add(h)
session.flush()
session.close()
def get_db() -> Iterator[Session]:
db = SessionLocal()
try:
yield db
finally:
db.close()
#app.get("/hardwares", response_model=List[HardwareOut])
def get_hardwares(db: Session = Depends(get_db)) -> Any:
return [HardwareOut.from_orm(hardware) for hardware in db.query(Hardware).all()]
How can I change the HardwareOut Schema to return what I expect?

I finally got the answer I wanted.
I added 2 changes to get it:
Use the Union type from typing lib for the attributes software_name e software_version like that:
Add a Pydantic validator for each field to change the returned value, like that:
from typing import Union
from pydantic import validator
...
class SoftwareSchema(BaseSchema):
software_name: Union[str, SoftwareNameSchema]
software_version: Union[str, SoftwareVersionSchema]
#validator('software_name')
def name_to_str(cls, v, values, **kwargs):
return v.name if not isinstance(v, str) else v
#validator('software_version')
def version_to_str(cls, v, values, **kwargs):
return v.version if not isinstance(v, str) else v
...
And the answer was this:
[
{
"id": 1,
"hostname": "hostname2",
"softwares": [
{
"id": 1,
"software_name": "nginx",
"software_version": "2.9"
},
{
"id": 2,
"software_name": "vim",
"software_version": "0.3"
},
{
"id": 3,
"software_name": "apache",
"software_version": "1.0"
}
]
}
]
update:
As an improvement, I add an alias for each attribute for a better semantic response. So, I change software_name to name and software_version to version. Like this:
from typing import Union
from pydantic import validator
...
class SoftwareSchema(BaseSchema):
software_name: Union[str, SoftwareNameSchema] = Field(None, alias="name")
software_version: Union[str, SoftwareVersionSchema] = Field(None, alias="version")
#validator('software_name')
def name_to_str(cls, v, values, **kwargs):
return v.name if not isinstance(v, str) else v
#validator('software_version')
def version_to_str(cls, v, values, **kwargs):
return v.version if not isinstance(v, str) else v
...

Related

how to get one nested data in Fast Api from Concatenate values with same keys in a list of dictionaries?

i am using fast Api SQLAlchemy. i have a 'Writing_Test_Name' models
class Writing_Test_Name(Base):
__tablename__ = properties.Writing_Test_Name
id = Column(Integer, primary_key=True, index=True)
book_id=Column(Integer,ForeignKey('tbl_stu_adm-pricing_plan-book.id'))
book_name=Column(String)
writing_test=Column(String)
writing_test_description=Column(String)
writing_pattern = relationship('Book', back_populates='writing_items')
form this model I want to get the data in nested models like this:
[
{
"book_id": 1
"book_name": "Bangla 1st paper",
"writing_items": [
{
"id": 1,
"writing_test": "string-1",
"writing_test_description": "string-1"
},
{
"id": 2,
"writing_test": "string-2",
"writing_test_description": "string-2"
}
]
},
{
"book_id": 2
"book_name": "Bangla 2nd paper",
"writing_items": [
{
"id": 3,
"writing_test": "string-3",
"writing_test_description": "string-3"
},
{
"id": 4,
"writing_test": "string-4",
"writing_test_description": "string-4"
}
]
}
]
i alreday tried many ways but i am not properly getting the items like this, I tried this
def get_one_group(id: int, db: Session):
data = db.execute('SELECT id, book_id, book_name, writing_test,
writing_test_description FROM
public."tbl_study_content-writing_test_name";')
role_has_permission = data.mappings().all()
x=role_has_permission
from collections import defaultdict
groups = defaultdict(list)
for test in x:
book_id = test['book_id']
book_name=test['book_name']
print(book_id)
#book_name = test.pop('book_name')
#z=str(subject_id) + book_name
groups[book_name].append(test)
result = [ { 'book_name' :k, 'writing_items' : v } for k, v
in groups.items()]
while groups[book_id] == id :
return result
But I am getting null response when i tried to get the specific book id with while groups[book_id] == id . what was my mistake.

Failing to parse complex JSON using Python and Marshmallow

I am using Marshmallow to create a mapper for a JSON file. Following are the details:
My JSON File:
{
"version": "3.0",
"name": "A1",
"request": {
"startdate": "26022022",
"enddate": "26022022",
"records": 1000
},
"ranking": {
"90": {
"name": "N1",
"class1": "C1"
},
"98": {
"name": "N2",
"class1": "C2"
},
"86": {
"name": "N3",
"class1": "C3"
}
}
}
My mapper class:
class RequestMapper(Schema):
startdate = fields.String()
enddate = fields.String()
records = fields.Int()
class Ranking(Schema):
name = fields.String()
class1 = fields.String()
class RankingMapper(Schema):
rank = fields.Nested(Ranking(), dataKey = fields.Int)
class SampleSchema(Schema):
name = fields.Str()
request = fields.Nested(RequestMapper())
ranking = fields.Nested(RankingMapper())
Code to call Mapper:
print("\n\nOutput using mapper")
pprint(mapper.SampleSchema().dump(data), indent=3)
print("\n\n")
Following is the output:
Output using mapper
{ 'name': 'A1',
'ranking': {},
'request': {'enddate': '26022022', 'records': 1000, 'startdate': '26022022'}}
I am not getting any data for ranking as datakey [90, 98, 86...] are dynamic and am not sure how to create mapper for dynamic keys please.
Any inputs will be helpful.
Thank you
When nesting schemas, pass the class NAME, not a class instance:
class RankingMapper(Schema):
rank = fields.Nested(Ranking, dataKey = fields.Int)
class SampleSchema(Schema):
name = fields.Str()
request = fields.Nested(RequestMapper)
ranking = fields.Nested(RankingMapper)

graphene.Field() with filter() and graphene.List() with get() return errors (Graphene Django) [duplicate]

I used both "DjangoListField()" and "graphene.List()" with the Resolver to list all objects.
"DjangoListField()" in schema.py:
import graphene
from graphene_django import DjangoObjectType
from graphene_django import DjangoListField
from .models import Category
class CategoryType(DjangoObjectType):
class Meta:
model = Category
fields = ("id","name")
class Query(graphene.ObjectType):
all_categories = DjangoListField(CategoryType) # DjangoListField()
def resolve_all_categories(root, info): # Resolver to list all objects
return Category.objects.all()
schema = graphene.Schema(query=Query)
"graphene.List()" in schema.py:
import graphene
from graphene_django import DjangoObjectType
from .models import Category
class CategoryType(DjangoObjectType):
class Meta:
model = Category
fields = ("id","name")
class Query(graphene.ObjectType):
all_categories = graphene.List(CategoryType) # graphene.List()
def resolve_all_categories(root, info): # Resolver to list all objects
return Category.objects.all()
schema = graphene.Schema(query=Query)
Then, I queried "allCategories" for both code in schema.py above one by one:
query {
allCategories {
id
name
}
}
But the result is the same to list all objects:
{
"data": {
"allCategories": [
{
"id": "1",
"name": "category1"
},
{
"id": "2",
"name": "category2"
}
]
}
}
What is the difference between "DjangoListField()" and "graphene.List()"?
"DjangoListField()" has the Default Resolver to list all objects but "graphene.List()" doesn't have it so for "graphene.List()", you need to explicitly define the Resolver to list all objects otherwise you get "null".
So, if you remove Resolver from your code with "DjangoListField()":
import graphene
from graphene_django import DjangoObjectType
from graphene_django import DjangoListField
from .models import Category
class CategoryType(DjangoObjectType):
class Meta:
model = Category
fields = ("id","name")
class Query(graphene.ObjectType):
all_categories = DjangoListField(CategoryType) # DjangoListField()
schema = graphene.Schema(query=Query)
Then, you query "allCategories":
query {
allCategories {
id
name
}
}
Finally, you can still list all objects:
{
"data": {
"allCategories": [
{
"id": "1",
"name": "category1"
},
{
"id": "2",
"name": "category2"
}
]
}
}
But, if you remove Resolver from your code with "graphene.List()":
import graphene
from graphene_django import DjangoObjectType
from .models import Category
class CategoryType(DjangoObjectType):
class Meta:
model = Category
fields = ("id","name")
class Query(graphene.ObjectType):
all_categories = graphene.List(CategoryType) # graphene.List()
schema = graphene.Schema(query=Query)
Then, you query "allCategories":
query {
allCategories {
id
name
}
}
Finally, you cannot list all objects instead you get "null":
{
"data": {
"allCategories": null
}
}
You can get more detail about "DjangoListField()" in this page DjangoListField.

Resolve secret created with CDK in a Cfn L1 Construct

How can I use an L2 Secret created with Secrets Manager to resolve as an L1 Cfn Property value?
from aws_cdk import (
core,
aws_secretsmanager as secretsmanager,
aws_elasticache as elasticache
)
class MyStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
redis_password = secretsmanager.Secret(
self, "RedisPassword",
description="Redis auth",
generate_secret_string=secretsmanager.SecretStringGenerator(
exclude_characters='/"#'
)
)
self.redis = elasticache.CfnReplicationGroup(self, 'RedisCluster',
auth_token=redis_password.secret_value,
# other properties
)
This gives the error
jsii.errors.JSIIError: Object of type #aws-cdk/aws-secretsmanager.Secret is not convertible to #aws-cdk/core.CfnElement
In Cloudformation to resolve a secret I'd use something like
AuthToken: !Sub '{{resolve:secretsmanager:${MySecret}::password}}'
But a L2 Secret doesn't output the Cfn Ref like L1 constructs do (that I know of)
What am I missing?
I was only missing the to_string() method
from aws_cdk import (
core,
aws_secretsmanager as secretsmanager,
aws_elasticache as elasticache
)
class MyStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
redis_password = secretsmanager.Secret(
self, "RedisPassword",
description="Redis auth",
generate_secret_string=secretsmanager.SecretStringGenerator(
exclude_characters='/"#'
)
)
self.redis = elasticache.CfnReplicationGroup(self, 'RedisCluster',
auth_token=redis_password.secret_value.to_string(),
# other properties
)
This synthesizes to
{
"RedisPasswordED621C10": {
"Type": "AWS::SecretsManager::Secret",
"Properties": {
"Description": "Redis auth",
"GenerateSecretString": {
"ExcludeCharacters": "/\"#"
}
},
"Metadata": {
"aws:cdk:path": "my-cdk-stack/RedisPassword/Resource"
}
},
"RedisCluster": {
"Type": "AWS::ElastiCache::ReplicationGroup",
"Properties": {
"ReplicationGroupDescription": "RedisGroup",
"AtRestEncryptionEnabled": true,
"AuthToken": {
"Fn::Join": [
"",
[
"{{resolve:secretsmanager:",
{
"Ref": "RedisPasswordED621C10"
},
":SecretString:::}}"
]
]
},
"OtherProps": "..."
}
}
}

Flask & SqlAlchemy - alembic session.add_all() results in AttributeError: 'bool' object has no attribute 'clear' error

I'm trying to insert some data as part of deployment. I've created following alembic revision script
revision = '59891ffc8502'
down_revision = '349540cf9cef'
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
Session = sessionmaker()
Base = declarative_base()
class Colours(Base):
__tablename__ = 'colours'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column('name', sa.Unicode(length=25), nullable=False)
code = sa.Column('code', sa.Unicode(length=2), nullable=False)
def upgrade():
bind = op.get_bind()
session = Session(bind=bind)
session._model_changes = False
op.alter_column('colours', 'code', new_column_name='code', existing_type=mysql.VARCHAR(length=25))
col = [
{ "name": "Black", "code": "ff00cc" },
{ "name": "Blue", "code": "ff00cc" },
{ "name": "Brown", "code": "ff00cc" },
{ "name": "Gold", "code": "ff00cc" },
{ "name": "Green", "code": "ff00cc" }
]
# populate colours table
colours = { c['name']: Colours(name=unicode(c['name']), code=c['code']) for c in col}
session.add_all(colours.values())
session.commit()
# ### end Alembic commands ###
and upgrade head results in AttributeError: 'bool' object has no attribute 'clear' error.
Any clue?
Turns out, setting session._model_changes = False doesn't take care of Flask-SQLAlchemy enough. You need to completely unregister the event handlers it registers.
from flask_sqlalchemy import _SessionSignalEvents
from sqlalchemy import event
from sqlalchemy.orm import Session as BaseSession
Session = sessionmaker()
event.remove(BaseSession, 'before_commit', _SessionSignalEvents.session_signal_before_commit)
event.remove(BaseSession, 'after_commit', _SessionSignalEvents.session_signal_after_commit)
event.remove(BaseSession, 'after_rollback', _SessionSignalEvents.session_signal_after_rollback)

Categories

Resources