Get matched view in pyramid - python

I have pyramid service and code like this:
#view_defaults(route_name="result", context=Result, renderer="json")
class ResultView(RESTView):
"""
Rest view for exercise results
"""
#view_config(request_method='GET', permission='view', role="owner")
#view_config(request_method='GET', permission='view', role="admin")
#view_config(request_method='GET', permission='view', role="gadmin")
#schema(Result)
#log("get result")
def get(self):
"""
Gets result
"""
return self.get.__schema__.dictirialize(self.context, prepare_json=True)
......
How can i get matched view inside the ResultView.get?

I solved this by creating a custom view decorator that attaches view_id on request. This decorator gets called based on which view config gets activated. Here is an example:
#view_config(request_method='GET', role="owner", permission="view", decorator=view_id("owner"))
#view_config(request_method='GET', role="admin", permission="view", decorator=view_id("admin"))
#view_config(request_method='GET', permission="view")
#schema(User, excludes=('password', 'deleted'), key="owner")
#schema(User, key="admin")
#log("get user")
def get(self):
return self.get.__schema__[self.request.id].dictify(self.context)
And the decorator:
def view_id(id):
"""
Pyramid view decorator that remembers the view id and assigns it to request
"""
def _view_id(view):
def view_callable(context, request):
request.id = id
return view(context, request)
return view_callable
return _view_id
It gets the job done for me.

Related

Says 'AnimalShelter' object has no attritbute 'create' but everything is there?

I am trying to test my AnimalShelter.py code in Jupyter Notebook but I keep getting the error that the 'create' attribute is not there. Did I miss something? I have it defined in the code but no matter what I change it still says it is not there.
AnimalShelter.py code:
import pymongo
from pymongo import MongoClient
from bson.objectid import ObjectId
class AnimalShelter(object):
""" CRUD operations for Animal collection in MongoDB """
def __init__(self, username, password):
#Initializing the MongoClient. This helps to access the MongoDB databases and collections.
self.client = MongoClient('mongodb://%s:%s#localhost:45344' % (username, password))
#where xxxx is your unique port number
self.database = self.client['AAC']
#Complete this create method to implement the C in CRUD.
def create(self, data):
if data is not None:
insert = self.database.animals.insert(data) #data should be dictionary
else:
raise Exception("Nothing to save, because data parameter is empty")
#Create method to implement the R in CRUD.
def read(self, searchData):
if searchData:
data = self.database.animals.find(searchData, {"_id": False})
else:
data = self.database.animals.find({}, {"_id": False})
return data
#Create method to implement U in CRUD.
def update(self, searchData, updateData):
if searchData is not None:
result = self.database.animals.update_many(searchData, {"$set": updateData})
else:
return "{}"
return result.raw_result
#Create method to implement D in CRUD.
def delete(self, deleteData):
if deleteData is not None:
result = self.database.animals.delete_many(deleteData)
else:
return "{}"
return result.raw_result
The test code I am trying to use to see that CRUD is functioning properly:
from AnimalShelter import AnimalShelter
data = {}
query = {}
test_class = AnimalShelter()
#test each function in the AnimalShelter class
response = test_class.create(data)
assert response
response = test_class.read(data)
assert response
response = test_class.update(data)
assert response
response = test_class.delete(data)
assert response
I am at a loss. I am new to Jupyter Notebook with the whole testing thing and I figured this was the simplest way to test the attributes before making a different test for specific data from the python code but either way I still get that the create attribute doesn't exist!
I created a slightly altered version of your code for easy testing and got the error that no username and password were being passed to the AnimalShelter constructor.
class AnimalShelter(object):
""" CRUD operations for Animal collection in MongoDB """
def __init__(self, username, password):
print("constructor")
#Complete this create method to implement the C in CRUD.
def create(self, data):
print("create")
#Create method to implement the R in CRUD.
def read(self, searchData):
print("read")
#Create method to implement U in CRUD.
def update(self, searchData, updateData):
print("update")
#Create method to implement D in CRUD.
def delete(self, deleteData):
print("delete")
test_class = AnimalShelter("user", "pass")
data = {}
test_class.create(data)
test_class.read(data)
test_class.update(data, {})
test_class.delete(data)
Could your error be because you didn't pass these variables when constructing the test_class object? Upon passing "user" and "pass", I got the expected output:
constructor
create
read
update
delete

Does transaction.atomic cover the called function too?

I'm working with Django / Celery and I would like to know if transaction.atomic on my create function cover also the called function (createUserTenant) too
this is an example (as you can see i'm calling createUserTenant which contains some queries) :
#transaction.atomic
def create(self, request):
formSerializer = CustomUserSerializer(data = request.data)
if formSerializer.is_valid():
NewUserRecord = formSerializer.save()
if createUserTenant.delay(NewUserRecord.id, connection.schema_name):
return Response(TeamSerializer(Team.objects.all(), many=True).data, status = status.HTTP_201_CREATED)
return Response(formSerializer.errors, status.HTTP_400_BAD_REQUEST)
As you can see I have some transactions here
#shared_task
def createUserTenant(userid, current_schema):
state = True
try:
with schema_context(current_schema):
addUserInTeam = Team.objects.create(user = CustomUser.objects.get(pk=userid))
with schema_context('public'):
userInQuestion = CustomUser.objects.get(id=userid)
# create your first real tenant
tenant = Client(
schema_name=str(userInQuestion.username),
name=userInQuestion.first_name + '_' + userInQuestion.last_name,
paid_until='2014-12-05',
on_trial=True)
tenant.save()
# migrate_schemas automatically called, your tenant is ready to be used!
# Add one or more domains for the tenant
domain = Domain()
domain.domain = str(userInQuestion.username) + settings.BASE_URL # tx.domain.com
domain.tenant = tenant
domain.is_primary = False
domain.save()
except:
state = False
return state
No, the decorator only wraps the function create in a transaction block and transactions can also be nested. A transaction block does not automatically wraps the parent block in a transaction. You need to create a transaction in your task to ensure that the code outside the create is being run in a transaction.
Here are more details: https://docs.djangoproject.com/en/3.2/topics/db/transactions/#controlling-transactions-explicitly
One way to do it:
from django.db import transaction
#shared_task
def createUserTenant(userid, current_schema):
with transaction.atomic():
# your code
# ....

Pyramid: resource tree in URL Dispatch(hybrid) application

I am trying to implement dynamic ACLs(including row-level security) in URL Dispatch app.
Defining Root factory only doesn't seem to be sufficient as I need to perform individual authz checks for each secured endpoint. My setup looks as follows(I was using pyramid docs and mmerickel's tutorials as a guide):
config.py
...
settings = config.registry.settings
config = Configurator(settings=settings, root_factory=RootPermissionFactory)
config.set_authentication_policy(CustomAuthenticationPolicy(settings))
config.set_authorization_policy(ACLAuthorizationPolicy())
...
views.py
#import ...
#view_defaults(renderer='json', permission='secured')
class RecordsView(object):
...
#view_config(request_method='GET', route_name='records_by_id')
def get(self):
record = self.request.context.data
if not record:
return HTTPNotFound()
return record
#view_config(request_method='GET', route_name='records')
def get_by_owners(self):
owner_uids = self.request.params.mixed()['owner_uids']
return records_service.get_records(owner_uids=owner_uids)
def includeme(config):
config.add_route('records', '/records', factory=RecordsResource)
config.add_route('records_by_id', 'records/{record_id}', factory=RecordFactory, traverse='{record_id}')
authorization.py
class RootPermissionFactory(object):
__name__ = None
__parent__ = None
def __acl__(self):
return [
(Allow, 'authenticated_principal', 'secured'),
]
def __init__(self, request):
self.request = request
class RecordFactory(object):
def __init__(self, request):
self.request = request
def __getitem__(self, key):
record_data = records_service.get_record(key)
owner = record_data.get('owner_uid')
return RecordContext(self.request, owner, record_data)
class RecordContext(object):
def __acl__(self):
owner_principal = 'u:{owner}'.format(owner=self.owner)
return [
(Allow, owner_principal, 'secured'),
]
def __init__(self, request, owner, record_data):
self.request = request
self.owner = owner
self.data = record_data
class RecordsResource(object):
def __acl__(self):
request_params = self.request.params.mixed()
request_owner_uids = request_params['owner_uids']
authorized_owner_uids = owners_api_service.get_authorized_owners(self.request.user['auth_data'])
return [(Allow, 'authenticated_principal', 'secured')]\
if set(authorized_owner_uids) == set(request_owner_uids) else []
def __init__(self, request):
self.request = request
My questions are following:
is it acceptable to utilize row-level security checks without having Model layer? i.e. there is no ORM set up for Records data and there is also no plain Model to put persisted data into, so I have to use 'fake' RecordContext class to attach __acl__ rules and pass data to the view
is it acceptable to treat /records endpoint as a Resource despite the fact it is not a resource from Traversal perspective and relies on query parameters rather than path sections?
I think the answer to both of your question is: if it works for you then it's totally acceptable. I've found a lot of success treating URLs as resources as a general pattern to the extent that I have a some tooling to avoid using route_name. For example:
config.add_route('records', '/records', factory=RecordsResource, use_global_views=True)
config.add_route('records_by_id', 'records/{record_id}', factory=RecordFactory, traverse='{record_id}', use_global_views=True)
#view_config(context=RecordsResource, renderer='json')
def records_view(request):
return {}
#view_config(context=RecordContext, renderer='json')
def record_view(request):
return {}

What is the proper way to delineate modules and classes in Python?

I am new to Python, and I'm starting to learn the basics of the code structure. I've got a basic app that I'm working on up on my Github.
For my simple app, I'm create a basic "Evernote-like" service which allows the user to create and edit a list of notes. In the early design, I have a Note object and a Notepad object, which is effectively a list of notes. Presently, I have the following file structure:
Notes.py
|
|------ Notepad (class)
|------ Note (class)
From my current understanding and implementation, this translates into the "Notes" module having a Notepad class and Note class, so when I do an import, I'm saying "from Notes import Notepad / from Notes import Note".
Is this the right approach? I feel, out of Java habit, that I should have a folder for Notes and the two classes as individual files.
My goal here is to understand what the best practice is.
As long as the classes are rather small put them into one file.
You can still move them later, if necessary.
Actually, it is rather common for larger projects to have a rather deep hierarchy but expose a more flat one to the user. So if you move things later but would like still have notes.Note even though the class Note moved deeper, it would be simple to just import note.path.to.module.Note into notes and the user can get it from there. You don't have to do that but you can. So even if you change your mind later but would like to keep the API, no problem.
I've been working in a similar application myself. I can't say this is the best possible approach, but it served me well. The classes are meant to interact with the database (context) when the user makes a request (http request, this is a webapp).
# -*- coding: utf-8 -*-
import json
import datetime
class Note ():
"""A note. This class is part of the data model and is instantiated every
time there access to the database"""
def __init__(self, noteid = 0, note = "", date = datetime.datetime.now(), context = None):
self.id = noteid
self.note = note
self.date = date
self.ctx = context #context holds the db connection and some globals
def get(self):
"""Get the current object from the database. This function needs the
instance to have an id"""
if id == 0:
raise self.ctx.ApplicationError(404, ("No note with id 0 exists"))
cursor = self.ctx.db.conn.cursor()
cursor.execute("select note, date from %s.notes where id=%s" %
(self.ctx.db.DB_NAME, str(self.id)))
data = cursor.fetchone()
if not data:
raise self.ctx.ApplicationError(404, ("No note with id "
+ self.id + " was found"))
self.note = data[0]
self.date = data[1]
return self
def insert(self, user):
"""This function inserts the object to the database. It can be an empty
note. User must be authenticated to add notes (authentication handled
elsewhere)"""
cursor = self.ctx.db.conn.cursor()
query = ("insert into %s.notes (note, owner) values ('%s', '%s')" %
(self.ctx.db.DB_NAME, str(self.note), str(user['id'])))
cursor.execute(query)
return self
def put(self):
"""Modify the current note in the database"""
cursor = self.ctx.db.conn.cursor()
query = ("update %s.notes set note = '%s' where id = %s" %
(self.ctx.db.DB_NAME, str(self.note), str(self.id)))
cursor.execute(query)
return self
def delete(self):
"""Delete the current note, by id"""
if self.id == 0:
raise self.ctx.ApplicationError(404, "No note with id 0 exists")
cursor = self.ctx.db.conn.cursor()
query = ("delete from %s.notes where id = %s" %
(self.ctx.db.DB_NAME, str(self.id)))
cursor.execute(query)
def toJson(self):
"""Returns a json string of the note object's data attributes"""
return json.dumps(self.toDict())
def toDict(self):
"""Returns a dict of the note object's data attributes"""
return {
"id" : self.id,
"note" : self.note,
"date" : self.date.strftime("%Y-%m-%d %H:%M:%S")
}
class NotesCollection():
"""This class handles the notes as a collection"""
collection = []
def get(self, user, context):
"""Populate the collection object and return it"""
cursor = context.db.conn.cursor()
cursor.execute("select id, note, date from %s.notes where owner=%s" %
(context.db.DB_NAME, str(user["id"])))
note = cursor.fetchone()
while note:
self.collection.append(Note(note[0], note[1],note[2]))
note = cursor.fetchone()
return self
def toJson(self):
"""Return a json string of the current collection"""
return json.dumps([note.toDict() for note in self.collection])
I personally use python as a "get it done" language, and don't bother myself with details. This shows in the code above. However one piece of advice: There are no private variables nor methods in python, so don't bother trying to create them. Make your life easier, code fast, get it done
Usage example:
class NotesCollection(BaseHandler):
#tornado.web.authenticated
def get(self):
"""Retrieve all notes from the current user and return a json object"""
allNotes = Note.NotesCollection().get(self.get_current_user(), settings["context"])
json = allNotes.toJson()
self.write(json)
#protected
#tornado.web.authenticated
def post(self):
"""Handles all post requests to /notes"""
requestType = self.get_argument("type", "POST")
ctx = settings["context"]
if requestType == "POST":
Note.Note(note = self.get_argument("note", ""),
context = ctx).insert(self.get_current_user())
elif requestType == "DELETE":
Note.Note(id = self.get_argument("id"), context = ctx).delete()
elif requestType == "PUT":
Note.Note(id = self.get_argument("id"),
note = self.get_argument("note"),
context = ctx).put()
else:
raise ApplicationError(405, "Method not allowed")
By using decorators I'm getting user authentication and error handling out of the main code. This makes it clearer and easier to mantain.

Pyramid traversal is doing my head in

I am trying to get my head around Pyramid traversal with this very simple example. What I haven't quite grasped yet is where to "inject" an Article object from the db.
As it is, /Article correctly finds and renders the article_view but that's fairly useless. How/when/where do I use the next part of the URL to query for a particular Article from the db? eg. /Article/5048230b2485d614ecec341d.
Any clues would be great!
init.py
from pyramid.config import Configurator
from pyramid.events import subscriber
from pyramid.events import NewRequest
import pymongo
from otk.resources import Root
def main(global_config, **settings):
""" This function returns a WSGI application.
"""
config = Configurator(settings=settings, root_factory=Root)
config.add_static_view('static', 'otk:static')
# MongoDB
def add_mongo_db(event):
settings = event.request.registry.settings
url = settings['mongodb.url']
db_name = settings['mongodb.db_name']
db = settings['mongodb_conn'][db_name]
event.request.db = db
db_uri = settings['mongodb.url']
MongoDB = pymongo.Connection
if 'pyramid_debugtoolbar' in set(settings.values()):
class MongoDB(pymongo.Connection):
def __html__(self):
return 'MongoDB: <b>{}></b>'.format(self)
conn = MongoDB(db_uri)
config.registry.settings['mongodb_conn'] = conn
config.add_subscriber(add_mongo_db, NewRequest)
config.include('pyramid_jinja2')
config.include('pyramid_debugtoolbar')
config.scan('otk')
return config.make_wsgi_app()
resources.py
class Root(object):
__name__ = None
__parent__ = None
def __init__(self, request):
self.request = request
def __getitem__(self, key):
if key == 'Article':
return Article(self.request)
else:
raise KeyError
class Article:
__name__ = ''
__parent__ = Root
def __init__(self, request):
self.reqeust = request
# so I guess in here I need to update the Article with
# with the document I get from the db. How?
def __getitem__(self, key):
raise KeyError
views.py
from pyramid.view import view_config
from otk.resources import *
from pyramid.response import Response
#view_config(context=Root, renderer='templates/index.jinja2')
def index(request):
return {'project':'OTK'}
#view_config(context=Article, renderer='templates/view/article.jinja2')
def article_view(context, request):
# I end up with an instance of Article here as the context.. but
# at the moment, the Article is empty
return {}
You'd generally return a Article object from the id part of the URL traversal.
What happens with traversal is that for each element in the URL path, an object is looked up and made the new current object for the next path element lookup.
So for Article, the root object is asked for something matching that name, and the result of that lookup is made the new "current" object, and 5048230b2485d614ecec341d is then looked up on that new object.
So, what you are looking for is a dispatcher object, something that looks up articles based on the longer id you are passed, and that returns your Article instances:
class Root(object):
__name__ = None
__parent__ = None
def __init__(self, request):
self.request = request
def __getitem__(self, key):
if key == 'articles':
dispatch = ArticleDispatcher(self.request)
dispatch.__name__ = key
dispatch.__parent__ = self
return dispatch
raise KeyError(key)
class ArticleDispatcher(object):
__name__ = None
__parent__ = None
def __init__(self, request):
self.request = request
def __getitem__(self, key):
# Get a hold of the database here:
db = findDatabase(self.request)
if db.exists(key):
data = db.load(key)
art = Article(data)
art.__name__ = key
art.__parent__ = self
return art
raise KeyError(key)
class Article:
__name__ = None
__parent__ = None
def __init__(self, data):
self.data = data
Note how I returned a ArticleDispatcher when you use the /articles URL path, and also how I set the __name__ and __parent__ variables; you'll need those to be able to generate URLs for those instances.
The Article object returned now contains the actual article data, and the view can access that information when rendering.
You really want to go and study the Pyramid Traversal tutorial which explains this all in more detail.

Categories