How to test a Pyramid application's configuration? - python

I have a Pyramid application which uses a number of customization on the request object, in particular, and I would like to be sure that my settings are correctly configured and that they are actually configured.
For example, I have the following (simplified for brevity):
config = Configurator()
config.add_request_method(lambda self: portal_object, name="portal", property=True)
config.set_default_permission('view')
config.add_request_method(auth.get_user, 'user', reify=True)
If these things are not set on the configuration, the application is not going to work, or going to be completely open.
The things I would be interested to test would be:
the portal property that I want to set on the request is the one I passed when configuring the application
by default, my views have a permission set (so unauthenticated users have a forbidden access)
my requests always have a user property, and this property is cached.
So far, I tried to produce a "real" Pyramid request, which involves copy/paste-ing code from pyramid.router (not cool :( ) and, although I haven't tried, I guess it would work if I was setting up something like WebTest but I would then test the whole stack, which I'm not so interested at the moment (the views, especially, are already tested separately.)
What are my possibilities to test my application's configuration, and (hopefully) only this?

How about moving the configuration setting part to a separate function and create a unit test against this functinon?

Related

Release sessions not showing up in Sentry

I have added Sentry to my Python program like this:
sentry_sdk.init(
"https://2de30dc7030a4a78a41fad327ba0acff#o1107570.ingest.sentry.io/6134822",
traces_sample_rate=1.0,
release=__version__,
auto_session_tracking=True,
)
sentry_sdk.set_user(dict(id=get_user_id()))
This is supposed to also track user sessions, the auto_session_tracking would default to True anyway. When I take a look at the web interface, however, I see that my messages (send via sentry_sdk.capture_message(event)) show up, but there are no users tracked for the releases:
It refers to the documentation, but there is a gap there. This has already been reported. In that issue is stated that the feature is available, just the documentation is missing still.
Do I have to do anything special to get this tracked properly?
The auto_session_tracking config currently only applies to our WSGI middleware so it'll only be effective with a framework/integration that uses that WSGI middleware (Django being the obvious example).
If you wish to track a session yourself, we have a context manager that you can use as below.
import sentry_sdk
from sentry_sdk.sessions import auto_session_tracking
with auto_session_tracking(session_mode="request"):
with sentry_sdk.push_scope():
sentry_sdk.capture_message("foobar")
The session_mode argument can be either "request" or "application" with semantics documented here. Adding automatic session tracking more generally to other frameworks is on our roadmap. We will also update the docs to clarify this.

In Google App Engine (Python), how do you boost the resources of your application server?

This used to be done in the adminstrator console. Now, according to the docs, it's controlled by a setting in the application's configuration files.
I updated my app.yaml file to include these lines and redeployed it:
#
# Module Settings
# https://cloud.google.com/appengine/docs/python/modules/#Python_Configuration
#
module: default
instance_class: F2
However, I haven't noticed any improvement in my application's performance. Specifically, I have a (cpu-bound) script that was taking 4-5 secs to run and there has been no difference since the change.
So my question is: am I doing this correctly? And is there a way to confirm (for example, in the logs or elsewhere in the admin console) the level at which my application's servers are running?
I should note that I am testing this on an unbilled application. Although I couldn't find any information in the docs that indicated this feature was limited to billed applications, I know that some features are unavailable on unbilled apps.
The settings you have there look correct.
If you are using modules, and it looks like you are, you can confirm the frontend instance class is what you set it to by viewing the "Versions" page on the old app engine console at http://appengine.google.com/
If you aren't using modules you can view the instance type on the "Application Settings" page.
Unfortunately, there doesn't seem to be a way to check the frontend instance class using the new cloud console.
If you look under Instances in the application dashboard you can see which ones you currently have running.

How to change settings in a middleware?

I'd like to change the environment variable DJANGO_SETTINGS_MODULE (along with a few others) and then have ALL relevant modules like django.conf, django.db etc reloaded to reflect the information from the new settings module. The new settings module will have different database. I will be doing this in a middleware.
I was able to achieve this by reloading a few modules along with django.conf and django.db. All new SQL statements were fired against the new DB.
But this appears to be so hackish.
The main reason for me wanting to do this is to have the same apache child process serve requests for different django applications (different settings and not different apps) without having to recreate a new apache child process which reloads the whole thing.
Is there a clean way of achieving what I want to do?
Thanks,
UPDATE (19-Sept-2014): I have accepted Daniel Roseman's answer as that seems to be the reality in the context of the question asked. The Router approach suggested by him was something that I explored but couldn't use because django's transaction classes don't use the router. The router I presume exists for a different reason. The application code base I'm working on, which is pretty large, has tons of transaction.commit_manually for the default or a specific db alias. I was trying to get this to support multiple client databases without changing the application code.
However, I did manage to solve the main problem which was to support multiple client DBs and other settings. I don't try to change the settings on the fly nor do I use the router. I instead have a single settings.py with all client DB information. I monkey patched the connection handler to return a different database connection for 'default' alias (or other specific alias used by the code) based on certain env variables set in the middleware. So far this has worked fine. I will post an update if I run into any issues or if someone else can point out a potential issue with the approach.
No, there is no way to do this, and that's a very good thing as it is a bad idea. There is no reason to use the same Apache process for different sites: instead you should have different virtual hosts for each of your sites, and let Apache manage them.

Starting app engine modules in Google App Engine

App engine "modules" are a new (and experimental, and confusingly-named) feature in App Engine: https://developers.google.com/appengine/docs/python/modules. Developers are being urged to convert use of the "backends" feature to use of this new feature.
There seem to be two ways to start an instance of a module: to send a HTTP request to it (i.e. at http://modulename.appname.appspot.com for the appname application and modulename module), or to call google.appengine.api.modules.start_module().
The Simple Way
The simple way to start an instance of a module would seem to be to create an HTTP request. However, in my case this results in only two outcomes, neither of which is what I want:
If I use the name of the backend that my application defines, i.e. http://backend.appname.appspot.com, the request is properly routed to the backend and properly denied (because backend access is defined by default to be private).
Anything else results in the request being routed to the sole frontend instance of the default module, even using random character strings as module names, such as http://sdlsdjfsldfsdf.appname.appspot.com. This even holds for made-up instance IDs such as in the case of http://99.sdlsdjfsldfsdf.appname.appspot.com, etc. And of course (this is the problem) for the actual name of my module as well.
Starting via the API
The documentation says that calling start_module() with the name of a module and version should cause the specified version of the specified module to start up. However, I'm getting an UnexpectedStateError whenever I call this function with valid arguments.
The Unfortunate State of Affairs
Because I can't get this to work, I'm wondering if there is some subtlety that the documentation might not have mentioned. My setup is pretty straightforward, so I'm wondering if this is a widespread problem to which someone has found a solution.
It turns out that versions cannot be numeric. This problem seems to have been happening because our module's version was "1" and not (for example) "v1".
With modules, they changed the terminology around a little bit. What used to be "backends" are now "basic scaling" or "manual scaling" instances.
"Automatic scaling" and "basic scaling" instances start when they process a request, while "manual scaling" instances run constantly.
Generally to start an instance you would send an HTTP request to your module's URL.
start_module() seems to have limited use for modules with "manual scaling" instances, or restarting modules that have been stopped with stop_module().
You can add:
login: admin
To the handler for your backend. This way an admin user can call your backend and trigger it to run. With login: admin, you can also have issue URLFetch requests froom elsewhwere in your app (ie from a frontend) trigger your backend.

Accessing Request from __init__.py in Pyramid

When setting up a Pyramid app and adding settings to the Configurator, I'm having issues understanding how to access information from request, like request.session and such. I'm completely new at using Pyramid and I've searched all over the place for information on this but found nothing.
What I want to do is access information in the request object when sending out exception emails on production. I can't access the request object, since it's not global in the __init__.py file when creating the app. This is what I've got now:
import logging
import logging.handlers
from logging import Formatter
config.include('pyramid_exclog')
logger = logging.getLogger()
gm = logging.handlers.SMTPHandler(('localhost', 25), 'email#email.com', ['email#email.com'], 'Error')
gm.setLevel(logging.ERROR)
logger.addHandler(gm)
This works fine, but I want to include information about the logged in user when sending out the exception emails, stored in session. How can I access that information from __init__.py?
Attempting to make request a global variable, or somehow store a pointer to "current" request globally (if that's what you're going to try with subscribing to NewRequest event) is not a terribly good idea - a Pyramid application can have more than one thread of execution, so more than one request can be active within a single process at the same time. So the approach may appear to work during development, when the application runs in a single thread mode and just one user accesses it, but produce really funny results when deployed to a production server.
Pyramid has pyramid.threadlocal.get_current_request() function which returns thread-local request variable, however, the docs state that:
This function should be used extremely sparingly, usually only in unit
testing code. it’s almost always usually a mistake to use
get_current_request outside a testing context because its usage makes
it possible to write code that can be neither easily tested nor
scripted.
which suggests that the whole approach is not "pyramidic" (same as pythonic, but for Pyramid :)
Possible other solutions include:
look at exlog.extra_info parameter which should include environ and params attributes of the request into the log message
registering exception views would allow completely custom processing of exceptions
Using WSGI middleware, such as WebError#error_catcher or Paste#error_catcher to send emails when an exception occurs
if you want to log not only exceptions but possibly other non-fatal information, maybe just writing a wrapper function would be enough:
if int(request.POST['donation_amount']) >= 1000000:
send_email("Wake up, we're rich!", authenticated_userid(request))

Categories