Flask REST API record sender's IP address - rest

I have created a minimal REST API using Flask, SQLAlchemy, and Marshmallow. here is app.py file:
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
import os
# Initialize App
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
# Database Setup
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'db.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Init db
db = SQLAlchemy(app)
# Init marshmallow
ma = Marshmallow(app)
# Product Class/Model
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True)
description = db.Column(db.String(200))
price = db.Column(db.Float)
qty = db.Column(db.Integer)
def __init__(self, name, description, price, qty):
self.name = name
self.description = description
self.price = price
self.qty = qty
# Product Schema
class ProductSchema(ma.Schema):
class Meta:
fields = ('id', 'name', 'description', 'price', 'qty')
# Init Schema
product_schema = ProductSchema()
products_schema = ProductSchema(many=True)
# Create Product
#app.route('/product', methods=['POST'])
def add_product():
name = request.json['name']
description = request.json['description']
price = request.json['price']
qty = request.json['qty']
new_product = Product(name, description, price, qty)
db.session.add(new_product)
db.session.commit()
return product_schema.jsonify(new_product)
# Get All Products
#app.route('/receive', methods=['GET'])
def get_products():
all_products = Product.query.all()
result = products_schema.dump(all_products)
return jsonify(result)
# Run the Server
if __name__ == '__main__':
app.run(debug=True)
I want to extract the sender's IP address through the GET method. However, the sender's IP doesn't need to be part of the JSON payload.
Example: POST
{
"name": "Product 1",
"description": "This is product 1",
"price": 120.00,
"qty": 100
}
GET
{
"ip": "<whatever-the-ip>"
"name": "Product 1",
"description": "This is product 1",
"price": 120.00,
"qty": 100
}
How do I implement this functionality in my code? I tried using request.remote_addr, but I am not getting what I expected.

You can modify the response in the GET route any way you want before returning it, I added the request's IP to the response after serializing the db result like this:
# Get All Products
#app.route('/receive', methods=['GET'])
def get_products():
all_products = Product.query.all()
results = products_schema.dump(all_products)
for product in results
product.update({"ip": str(request.remote_addr)})
return jsonify(results)

Related

Python Flask, sqlalchemy and marshmallow serialization Issue in JSON representation and DB mirroring structure

Writing a REST API with Flask - I have declared 2 simple Models (Company and Address) using SQLAlchemy and i have also created the underlying DB and Tables in PostgreSQL (running Locally on PGAdmin).
Here the Models with (repr andjson methods included):
import os
import json
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
app = Flask(__name__)
app.config.from_object(os.environ['APP_SETTINGS'])
db = SQLAlchemy(app)
ma = Marshmallow(app)
#MODELS
class Company(db.Model):
__tablename__ = 'company'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
description = db.Column(db.String, nullable=False)
website = db.Column(db.Text, nullable=False)
addresses = db.relationship('Address', backref='company')
def json(self):
return {'id': self.id,'description': self.description, 'website': self.website}
def __repr__(self):
company_object = {
'id': self.id,
'description': self.description,
'website': self.website,
'addresses': self.addresses
}
return json.dumps(company_object)
def get_company(_id):
return Company.json(Company.query.filter_by(id=_id).first())
class Address(db.Model):
__tablename__ = 'address'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
street = db.Column(db.String, nullable=False)
postcode = db.Column(db.String, nullable=False)
country = db.Column(db.String, nullable=False)
company_Id = db.Column(db.Integer,db.ForeignKey('company.id'))
def json(self):
return {'id': self.id, 'street': self.street, 'postcode':self.postcode, 'country':self.country}
def __repr__(self):
Address_object = {
'id': self.id,
'street': self.street,
'postcode':self.postcode,
'country':self.country
}
return json.dumps(Address_object)
Here is the Controller for the Compnay API ( a simple get company via id that should return a company with an array of object addresses nested inside ) a company can have one or more addresses) notice i have tried to implement the marshmallow schema Serialization ( i think i did something worng there- not really sure):
from models.company_model import Company,Address
import json
from marshmallow import Schema, fields
#This below import ma from database.py --> from flask_marshmallow import Marshmallow
from database import ma
producstAPI = Blueprint('companyAPI', __name__)
class AddressSchema(ma.Schema):
id = fields.Int(dump_only=True)
street = fields.String()
postcode = fields.String()
country = fields.String()
class CompanySchema(ma.Schema):
id = fields.Int(dump_only=True)
title = fields.String()
description = fields.String()
imageURL = fields.String()
addresses= fields.Nested(AddressSchema,many=True)
# GET A Compaany via id - this company ALREADY exists in the Postgresql db with id 1 in the table Company and so the 2 addresses on the table Address
#companyAPI.route('/api/v1/companies/<int:company_id>')
def get_company_via_company_id(company_id):
company = Company.get_company(company_id)
company_schema = CompanySchema()
output = company_schema.dump(company)
return jsonify(output)
I start my Flask app and hit this URL 'http://127.0.0.1:5000/api/v1/companies/1' with POSTMAN to test my code. here Below is the result:
{
"description": "My Company",
id": 1,
"price": 23.45,
"addresses": [],
"website": "A link"
}
But I am trying to acheive this format Below:
{
"id": 1,
"description": "My Company",
"website": "A link",
"addresses": [
{
"id": 1,
"street": "1 dark street",
"postcode": "00001",
"country": "UK"
},
{
"id": 1,
"street": "1 light street",
"postcode": "00002",
"country": "Australia"
}
]
}
I have some inexperience with more nested/complicated models declarations - i have worked with flat json structures and i never had issues like this before - or maybe the fact that creating 2 tables is the actuall isssue here? i have made 2 tables to maintain data integrity between the 2 object making sure every address is linked to a company - i tought that marshmallow would simplify things but it's becoming a bit of a nightmare. i believe this could be a simple Serialization issue. can someone help? also if i had to write in the DB via the api would i encoutner same issue?
It could be a typo in the question, but the fields should be named addresses, shouldn't it?
class CompanySchema(ma.Schema):
id = fields.Int(dump_only=True)
title = fields.String()
description = fields.String()
price = fields.Float()
imageURL = fields.String()
# Here:
# sizeOptions = fields.Nested(AddressSchema,many=True)
addresse = fields.Nested(AddressSchema,many=True)
There may be other issues because I don't see where price comes from.

How to use Arrow type in FastAPI response schema?

I want to use Arrow type in FastAPI response because I am using it already in SQLAlchemy model (thanks to sqlalchemy_utils).
I prepared a small self-contained example with a minimal FastAPI app. I expect that this app return product1 data from database.
Unfortunately the code below gives exception:
Exception has occurred: FastAPIError
Invalid args for response field! Hint: check that <class 'arrow.arrow.Arrow'> is a valid pydantic field type
import sqlalchemy
import uvicorn
from arrow import Arrow
from fastapi import FastAPI
from pydantic import BaseModel
from sqlalchemy import Column, Integer, Text, func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy_utils import ArrowType
app = FastAPI()
engine = sqlalchemy.create_engine('sqlite:///db.db')
Base = declarative_base()
class Product(Base):
__tablename__ = "product"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Text, nullable=True)
created_at = Column(ArrowType(timezone=True), nullable=False, server_default=func.now())
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
product1 = Product(name="ice cream")
product2 = Product(name="donut")
product3 = Product(name="apple pie")
session.add_all([product1, product2, product3])
session.commit()
class ProductResponse(BaseModel):
id: int
name: str
created_at: Arrow
class Config:
orm_mode = True
arbitrary_types_allowed = True
#app.get('/', response_model=ProductResponse)
async def return_product():
product = session.query(Product).filter(Product.id == 1).first()
return product
if __name__ == "__main__":
uvicorn.run(app, host="localhost", port=8000)
requirements.txt:
sqlalchemy==1.4.23
sqlalchemy_utils==0.37.8
arrow==1.1.1
fastapi==0.68.1
uvicorn==0.15.0
This error is already discussed in those FastAPI issues:
https://github.com/tiangolo/fastapi/issues/1186
https://github.com/tiangolo/fastapi/issues/2382
One possible workaround is to add this code (source):
from pydantic import BaseConfig
BaseConfig.arbitrary_types_allowed = True
It is enough to put it just above #app.get('/'..., but it can be put even before app = FastAPI()
The problem with this solution is that output of GET endpoint will be:
// 20210826001330
// http://localhost:8000/
{
"id": 1,
"name": "ice cream",
"created_at": {
"_datetime": "2021-08-25T21:38:01+00:00"
}
}
instead of desired:
// 20210826001330
// http://localhost:8000/
{
"id": 1,
"name": "ice cream",
"created_at": "2021-08-25T21:38:01+00:00"
}
Add a custom function with the #validator decorator that returns the desired _datetime of the object:
class ProductResponse(BaseModel):
id: int
name: str
created_at: Arrow
class Config:
orm_mode = True
arbitrary_types_allowed = True
#validator("created_at")
def format_datetime(cls, value):
return value._datetime
Tested on local, seems to be working:
$ curl -s localhost:8000 | jq
{
"id": 1,
"name": "ice cream",
"created_at": "2021-12-02T08:25:10+00:00"
}
The solution is to monkeypatch pydantic's ENCODERS_BY_TYPE so it knows how to convert Arrow object so it can be accepted by json format:
from arrow import Arrow
from pydantic.json import ENCODERS_BY_TYPE
ENCODERS_BY_TYPE |= {Arrow: str}
Setting BaseConfig.arbitrary_types_allowed = True is also necessary.
Result:
// 20220514022717
// http://localhost:8000/
{
"id": 1,
"name": "ice cream",
"created_at": "2022-05-14T00:20:11+00:00"
}
Full code:
import sqlalchemy
import uvicorn
from arrow import Arrow
from fastapi import FastAPI
from pydantic import BaseModel
from sqlalchemy import Column, Integer, Text, func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy_utils import ArrowType
from pydantic.json import ENCODERS_BY_TYPE
ENCODERS_BY_TYPE |= {Arrow: str}
from pydantic import BaseConfig
BaseConfig.arbitrary_types_allowed = True
app = FastAPI()
engine = sqlalchemy.create_engine('sqlite:///db.db')
Base = declarative_base()
class Product(Base):
__tablename__ = "product"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Text, nullable=True)
created_at = Column(ArrowType(timezone=True), nullable=False, server_default=func.now())
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
product1 = Product(name="ice cream")
product2 = Product(name="donut")
product3 = Product(name="apple pie")
session.add_all([product1, product2, product3])
session.commit()
class ProductResponse(BaseModel):
id: int
name: str
created_at: Arrow
class Config:
orm_mode = True
arbitrary_types_allowed = True
#app.get('/', response_model=ProductResponse)
async def return_product():
product = session.query(Product).filter(Product.id == 1).first()
return product
if __name__ == "__main__":
uvicorn.run(app, host="localhost", port=8000)
Here is a code example where you do not need class Config and can work for any type by creating your own subclass with validators:
from psycopg2.extras import DateTimeTZRange as DateTimeTZRangeBase
from sqlalchemy.dialects.postgresql import TSTZRANGE
from sqlmodel import (
Column,
Field,
Identity,
SQLModel,
)
from pydantic.json import ENCODERS_BY_TYPE
ENCODERS_BY_TYPE |= {DateTimeTZRangeBase: str}
class DateTimeTZRange(DateTimeTZRangeBase):
#classmethod
def __get_validators__(cls):
yield cls.validate
#classmethod
def validate(cls, v):
if isinstance(v, str):
lower = v.split(", ")[0][1:].strip().strip()
upper = v.split(", ")[1][:-1].strip().strip()
bounds = v[:1] + v[-1:]
return DateTimeTZRange(lower, upper, bounds)
elif isinstance(v, DateTimeTZRangeBase):
return v
raise TypeError("Type must be string or DateTimeTZRange")
#classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(type="string", example="[2022,01,01, 2022,02,02)")
class EventBase(SQLModel):
__tablename__ = "event"
timestamp_range: DateTimeTZRange = Field(
sa_column=Column(
TSTZRANGE(),
nullable=False,
),
)
class Event(EventBase, table=True):
id: int | None = Field(
default=None,
sa_column_args=(Identity(always=True),),
primary_key=True,
nullable=False,
)
link to Github issue:
https://github.com/tiangolo/sqlmodel/issues/235#issuecomment-1162063590

How to use database models in Python Flask?

I'm trying to learn Flask and use postgresql with it. I'm following this tutorial https://realpython.com/flask-by-example-part-2-postgres-sqlalchemy-and-alembic/, but I keep getting error.
Cannot import name 'AorticStenosis' from partially initialized module 'models' (most likely due to a circular import)
I understand the problem, but I can't figure out how to fix it. So, I started playing around and try to use the steps by step tutorial on something that I'm working on, but I still get the same problem.
Here's my attempt:
Models.py
from app import db
class AorticStenosis(db.Model):
__tablename__ = 'wvu-model'
id = db.Column(db.Integer, primary_key=True)
ip_address = db.Column(db.String())
date_created = db.Column(db.DateTime, default=datetime.utcnow)
e_prime = db.Column(db.Float())
LVMi = db.Column(db.Float())
A = db.Column(db.Float())
LAVi = db.Column(db.Float())
E_e_prime = db.Column(db.Float())
EF = db.Column(db.Float())
E = db.Column(db.Float())
E_A = db.Column(db.Float())
TRV = db.Column(db.Float())
prediction = db.Column(db.String())
def __init__(self, ip_address, e_prime, LVMi, A, E_e_prime, EF, E, E_A, TRV, prediction):
print('initialized')
self.ip_address = ip_address
self.e_prime = e_prime
self.LVMi = LVMi
self.A = A
self.LAVi = LAVi
self.E_e_prime = E_e_prime
self.EF = EF
self.E = E
self.E_A = E_A
self.TRV = TRV
self.prediction = prediction
def __repr__(self):
return '<id {}>'.format(self.id)
app.py
import os
import ast
import bcrypt
from flask import Flask, redirect, render_template, request, session, url_for
import flask_login
from flask_sqlalchemy import SQLAlchemy
from bigml.deepnet import Deepnet
from bigml.api import BigML
import pickle as pkl
import sklearn
app = Flask(__name__)
if app.config['ENV'] == 'production':
app.config.from_object('as_config.ProductionConfig')
else:
app.config.from_object("config.DevelopmentConfig")
# app.config.from_pyfile('as_config.py')
# app.config.from_object(os.environ['APP_SETTINGS'])
# app.config.from_object('as_config.DevelopmentConfig')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
from models import AorticStenosis
login_key = app.config['LOGIN_KEY']
api_key = app.config['API_KEY']
model = app.config['MODEL']
api = BigML(login_key, api_key)
deepnet = Deepnet(model, api=api)
scaler = pkl.load(open("scaler.pkl", "rb"))
#app.route("/", methods=["GET", "POST"])
def home():
prediction = None
if request.method == "POST":
form_data = [
float(request.form.get("e_prime")),
float(request.form.get("LVMi")),
float(request.form.get("A")),
float(request.form.get("LAVi")),
float(request.form.get("E_e_prime")),
float(request.form.get("EF")),
float(request.form.get("E")),
float(request.form.get("E_A")),
float(request.form.get("TRV"))
]
form_data = scaler.transform([form_data])[0]
print(form_data)
prediction = str(deepnet.predict({
"e_prime": form_data[0],
"LVMi": form_data[1],
"A": form_data[2],
"LAVi": form_data[3],
"E_e_prime": form_data[4],
"EF": form_data[5],
"E": form_data[6],
"E_A": form_data[7],
"TRV": form_data[8]
}, full=True))
prediction = ast.literal_eval(prediction)
print(prediction)
## get ip address from the user
ip_address = request.environ['REMOTE_ADDR']
print("ip_address: ", ip_address)
try:
aorticStenosis = AorticStenosis(
ip_address = ip_address,
e_prime = form_data[0],
LVMi = form_data[1],
A = form_data[2],
LAVi = form_data[3],
E_e_prime = form_data[4],
EF = form_data[5],
E = form_data[6],
E_A = form_data[7],
TRV = form_data[8]
)
db.session.add(aorticStenosis)
db.session.commit()
except Exception as e:
print(str(e))
if prediction["prediction"] == "1":
prediction["prediction"] = "Low Risk"
prediction["probability"] = round(
1 - prediction["probability"], 6)
elif prediction["prediction"] == "2":
prediction["prediction"] = "High Risk"
else:
prediction["prediction"] = "No prediction was made!"
return render_template("home.html",
prediction=prediction["prediction"],
probability=prediction["probability"])
else:
return render_template("home.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", port="8000")
I made a new file database.py and defined db there.
database.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def init_app(app):
db.init_app(app)
app.py
import database
...
database.init_app(app)
models.py
from database import db
...

Generate SQLite database in Flask REST API code

I am new to REST API and starting building first REST API app using Flask, SQLAlchemy & Marshmallow. This is my app.py file:
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
import os
# Initialize App
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
# Database Setup
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'db.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Init db
db = SQLAlchemy(app)
# Init marshmallow
ma = Marshmallow(app)
# Product Class/Model
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True)
description = db.Column(db.String(200))
price = db.Column(db.Float)
qty = db.Column(db.Integer)
def __init__(self, name, description, price, qty):
self.name = name
self.description = description
self.price = price
self.qty = qty
# Product Schema
class ProductSchema(ma.Schema):
class Meta:
fields = ('id', 'name', 'description', 'price', 'qty')
# Init Schema
product_schema = ProductSchema()
products_schema = ProductSchema(many=True)
# Create Product
#app.route('/product', methods=['POST'])
def add_product():
name = request.json['name']
description = request.json['description']
price = request.json['price']
qty = request.json['qty']
new_product = Product(name, description, price, qty)
db.session.add(new_product)
db.session.commit()
return product_schema.jsonify(new_product)
# Get All Products
#app.route('/receive', methods=['GET'])
def get_products():
all_products = Product.query.all()
result = products_schema.dump(all_products)
return jsonify(result)
# Run the Server
if __name__ == '__main__':
app.run(debug=True)
For generating SQLite database, I have to open python interactive shell and then there I have to do this:
from app import db
db.create_all()
But I have to genreate database from app.py itself so I am inserting the same commands inside app.py, but it's giving me error:
OperationalError: (sqlite3.OperationalError) no such table: product
How do I generate a database from app.py?
Where are you placing your db.create_all()? The error may simply be a result of placement. When I copy and paste your code into PyCharm (running Python 3.7) it creates the DB fine when I place
db.create_all()
immediately before
# Run the Server
if __name__ == '__main__':
app.run(debug=True)
If you try to run db.create_all() before you instantiate the db object it will throw an error because db does not exist yet.
You should not need to use "from app import db" at all because the db object is declared up top.

Facebook and linkedin not filling email field properly

Google and github logins are working properly but for some reason I cant get the facebook and linkedin accounts to properly fill the email fields.
Here are the involved files
__init__.py
db = SQLAlchemy(app)
login_manager=LoginManager()
login_manager.init_app(app)
# Import blueprints from app (example: from app.posts import posts)
from app.users import users
# Register all blueprints to the main app (example: app.register_blueprint(posts))
app.register_blueprint(users)
app.register_blueprint(social_auth)
#3rd part db interaction
init_social(app, db)
# Import main views from app
from app import views
#Set login bootback
login_manager.login_view ='/login'
app.context_processor(backends)
Settings.py configuration, keys have been removed from post
#Flask
SECRET_KEY = ''
SESSION_COOKIE_NAME = ''
### Python Social Auth ###
DEBUG_TB_INTERCEPT_REDIRECTS = False
SESSION_PROTECTION = 'strong'
#Redirects and Paths
SOCIAL_AUTH_LOGIN_URL = '/login'
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/'
SOCIAL_AUTH_BACKEND_ERROR_URL = '/login'
SOCIAL_AUTH_USER_MODEL = 'app.users.models.User'
SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL = True
#Facebook
SOCIAL_AUTH_FACEBOOK_KEY=''
SOCIAL_AUTH_FACEBOOK_SECRET=''
#Google
SOCIAL_AUTH_GOOGLE_KEY=''
SOCIAL_AUTH_GOOGLE_SECRET=''
#LinkedIn
SOCIAL_AUTH_LINKEDIN_KEY=''
SOCIAL_AUTH_LINKEDIN_SECRET=''
#Github
SOCIAL_AUTH_GITHUB_KEY=''
SOCIAL_AUTH_GITHUB_SECRET=''
SOCIAL_AUTH_AUTHENTICATION_BACKENDS = (
'social.backends.google.GoogleOpenId',
'social.backends.google.GoogleOAuth2',
'social.backends.google.GoogleOAuth',
'social.backends.facebook.FacebookOAuth2',
'social.backends.linkedin.LinkedinOAuth',
'social.backends.github.GithubOAuth2',
)
Here is the model itself, follows pretty closely the example provided by omab
models.py
from __future__ import absolute_import, print_function
from time import time
import functools
import uuid
import pbkdf2
from marshmallow import Serializer, fields
from app import db
ROLE_USER=0
ROLE_ADMIN=1
ROLE_GOD=2
default_img = '/assets/images/avatars/default.jpg'
class User(db.Model):
#meat n' potatoes
id = db.Column(db.Integer, primary_key=True)
img=db.Column(db.String(255), default=default_img)
username = db.Column(db.String(255))
email = db.Column(db.String(200), unique=True)
first_name = db.Column(db.String(30))
last_name = db.Column(db.String(40))
created_at = db.Column(db.BigInteger, default=time())
#controls
role = db.Column(db.SmallInteger, default=ROLE_USER)
is_active = db.Column(db.Boolean, default=True)
is_authenticated= db.Column(db.Boolean, default=True)
#password
salt = db.Column(db.String(50))
pass_hash = db.Column(db.String(255))
def __unicode__(self):
return self.email
def __repr__(self):
return '<User %r>' % self.email
def is_authenticated(self):
return self.is_authenticated
def is_anonymous(self):
return False
def is_active(self):
return self.is_active
def get_id(self):
return unicode(self.id)
def _check_password(self, password):
hash_check = pbkdf2.crypt(password, self.salt, 1000)
if hash_check ==self.pass_hash:
valid=True
else:
valid=False
return valid
def validate_user(self,password):
p=self.check_password(password=password)
if p:
return True;
else:
return False;
meta = {
'allow_inheritance': True,
'indexes': ['-created_at'],
'ordering': ['-created_at']
}
class UserSerializer(Serializer):
id=fields.Integer()
img=fields.String()
email=fields.String()
first_name=fields.String()
last_name=fields.String()
Found this in the docs silly of me not to realize that they would have different request parameters:
http://psa.matiasaguirre.net/docs/backends/index.html
UPDATE:
See this answer from Babken Vardanyan
https://stackoverflow.com/a/46807907/2529583