Sorry for the noob question about classes. I'm trying to assign a soap client to a variable inside a class function and then access that variable in other class functions. I don't have any arguments to pass to the setup_client() function.
In the following example code, how do I make self.client accessible outside setup_client() so that I can use it in use_client(), and ditto making self.response available outside use_client()
class soap_call(self):
def __init__(self):
# What goes here?
self.client = # what?
self.response = # what?
def setup_client(self):
credentials = {'username': 'stuff', 'password': 'stuff'}
url = 'stuff'
t = HttpAuthenticated(**credentials)
self.client = suds.client.Client(url, transport=t)
def use_client(self):
self.response = self.client.service.whatever
print self.response
I quickly realized that if I add an optional client argument (self, client=None) to the class definition and include self.client = client, then I get a None type error when trying to use it in my functions.
I realize I just have a lack of understanding of classes. I've done some general reading up on classes but haven't come across any specific examples that describe what I'm dealing with.
I'd go with None in both cases because logically speaking, none of them exists at the time of object instantiation. It also allows you to do some sanity checking of your logic, e.g.
class SoapCall(object):
def __init__(self):
self.client = None
self.response = None
def setup_client(self):
credentials = {'username': 'stuff', 'password': 'stuff'}
url = 'stuff'
t = HttpAuthenticated(**credentials)
if self.client is None:
self.client = suds.client.Client(url, transport=t)
def use_client(self):
if self.client is None:
self.client = self.setup_client()
self.response = self.client.service.whatever
print self.response
It's fine to leave the client unspecified when you first create the instance, but then you need to be sure to call setup_client before you call use_client.
Related
I was wondering if I could get some software design advice. Hypothetically, let's say that I'm writing an unofficial API for Reddit that lets you log into a Reddit account and manage it, manage messages, view account information, etc., with a wide array of features.
This is the typical pattern I use. All of the code is simplified and dumbed down.
class HTTPClient:
def __init__(self, session: requests.Session = None):
if not self.session:
self.session = requests.Session()
else:
self.session = session
def _request(self, method, url, **kwargs):
"""Internal request handler"""
resp = self.session.request(method, url, **kwargs)
if resp.ok:
return resp
raise Exception(resp, resp.text)
class RedditSession(HTTPClient):
"""Establishes a Reddit session and does the authentication stuff"""
def __init__(self, username:str, password: str, session: requests.Session = None):
super().__init__(session)
self.username = username
self.password = password
def _authentication_stuff_here(self):
self._request("get", "https://example.com")
pass
def _login(self):
pass
class RedditClient(RedditSession)
def __init__(self, username:str, password: str, session: requests.Session = None):
super().__init__(username, password, session)
self._login(self):
# Profile intance contains methods to manage profile info
self.profile = Profile(self.session)
# Manage account info (not profile info)
def get_account_information(self):
pass
def change_account_password(self, password: str):
pass
def change_account_email(self, email: str):
pass
I was wondering if anyone knew if this structure was considered bad design, and if anyone had any ideas on how I could improve it.
What particularly bothers me is the multiple layers of indirection; i.e., it feels repetitive to pass an optional session instance (or some other variable) through multiple parents. Also, things begin to feel even more messy when I pass the session to multiple other classes when using composition.
For example, what if I want to separate methods in a way that a user can type user.profile.message.delete()? Would it be normal practice to pass session to the Profile instance, which then passes it to the Message instance? Or is there a better way of achieving this without so many layers of abstraction?
I have tried various design patterns, but everything always feels "messy" in the end. Maybe it's due to my lack of OOP knowledge. Thank you in advance for any help/advice.
I am trying to write unit test for some of Python classes I have created. I have created a class for wrapping s3 functionalities, and in that class I'm initializing boto3 s3 client.
class S3_Client:
def __init__(self, bucket_name):
self.s3 = boto3.client("s3", aws_access_key_id=e_config["aws_access_key"], aws_secret_access_key=e_config["aws_secret_key"])
self.bucket_name = bucket_name
def fetch(self, key):
response = self.s3.get_object(Bucket=self.bucket_name, Key=key)
return self.__prepare_file_info(response, key) # return formatted response
I would like to test method fetch with mocked response from self.s3.get_object. This is my test class:
import unittest
from .aws_s3_service import S3_Client # class I want to test
import boto3
from botocore.stub import Stubber
class TestS3_Client(unittest.TestCase):
def setUp(self):
self.client = boto3.client('s3')
self.stubber = Stubber(self.client)
def test_fetch(self):
get_object_response = {...} # hardcoded response
self.stubber.add_response('get_object', get_object_response, {"Bucket": "test_bucket", "Key": "path/to/file/test_file.txt"})
with self.stubber:
client = S3_Client("test_bucket")
result = client.fetch("path/to/file/test_file.txt")
The stubber is not actually injected into S3_Client, a real call to S3 is made. How do I inject the stubber? Any help is appreciated, thanks.
You need to make S3_Client accept a client object in a constructor argument.
In this way in your tests you can create a client, stub it, then inject it to S3_Client as a parameter.
If you don't like having to always create that client outside of the class, you can make it an optional argument, and create an instance in __init__ if none was passed:
class S3_Client:
def __init__(self, bucket_name, s3=None):
if s3 is None:
self.s3 = boto3.client("s3", aws_access_key_id=e_config["aws_access_key"], aws_secret_access_key=e_config["aws_secret_key"])
else:
self.s3 = s3
self.bucket_name = bucket_name
...
In the code of your test you would then say: client = S3_Client("test_bucket", self.client).
So I'm trying to port a code to work with Asyncio.
My problem are the #propertys, which - if not yet cached - need a network request to complete.
While I can await them without a problem in most contexts using that property in the __str__ method fails which is kinda expected.
What is the most pythonic way to solve this, other than caching the value on instance creation or just not using Asyncio.
Here is a example code illustrating the issue:
import requests
from typing import Union
class Example(object):
_username: Union[str, None]
#property
def username(self):
if not _username:
r = request.get('https://api.example.com/getMe')
self._username = r.json()['username']
return self._username
def __str__(self):
return f"Example(username={self.username!r})"
e = Example()
str(e)
So far I could come up with the following.
I could substitute #property with #async_property (
PyPi, Github, Docs),
and requests with httpx (PyPi, Github, Docs).
However it is still not working.
My attempt at porting it to Asyncio:
import httpx
from typing import Union
from async_property import async_property
class Example(object):
_username: Union[str, None]
#async_property
async def username(self):
if not _username:
async with httpx.AsyncClient() as client:
r = await client.get('https://www.example.org/')
self._username = r.json()['username']
return self._username
async def __str__(self):
return f"Example(username={await self.username!r})"
e = Example()
str(e)
Fails with TypeError: __str__ returned non-string (type coroutine).
First, I store an email in my session:
#cherrypy.expose
def setter(self):
email = "email#email.com"
cherrypy.session["email"] = email
return "Variable passed to session" // This works fine!
Second, I return the session data:
#cherrypy.expose
def getter(self):
return cherrypy.session("email") // This works fine too!
But now, I would like to store this data in a variable and return it:
#cherrypy.expose
def getter(self):
variable = cherrypy.session("email")
return variable
When doing this, I get a 500 Internal: KeyError 'variable'
Don't forget to turn sessions on in the config. It's disabled by default. Also, you use cherrypy.session as a dictionary, it's not a function you call.
Take this example code:
# rimoldi.py
import cherrypy
class TestApp:
#cherrypy.expose
def setter(self):
email = "email#email.com"
cherrypy.session["email"] = email
return 'Variable stored in session object. Now check out the getter function'
#cherrypy.expose
def getter(self):
return "The email you set earlier, was " + cherrypy.session.get("email")
if __name__ == '__main__':
cherrypy.quickstart(TestApp(), "/", {
"/": {
"tools.sessions.on": True,
}
})
You run the above example with:
python rimoldi.py
CherryPy says:
[09/Jan/2017:16:34:32] ENGINE Serving on http://127.0.0.1:8080
[09/Jan/2017:16:34:32] ENGINE Bus STARTED
Now point your browser at http://127.0.0.1:8080/setter, and you'll see:
Variable stored in session object. Now check out the getter function
Click the 'getter' link. The browser shows:
The email you set earlier, was email#email.com
Voila! This is how you use sessions in CherryPy. I hope this example helps you.
How do you delete messages using imap4.IMAP4Client? I cannot get the "deleted" tag correctly applied for using the "expunge" method.
I keep getting the following error:
Failure: twisted.mail.imap4.IMAP4Exception: Invalid system flag \
Sample code would be appreciated. This is what I have so far:
from twisted.internet import protocol, reactor
from twisted.mail import imap4
#Variables for connection
username = 'user#host.com'
password = 'mypassword'
host = 'imap.host.com'
port = 143
class IMAP4LocalClient(imap4.IMAP4Client):
def connectionMade(self):
self.login(username,password).addCallbacks(self._getMessages, self._ebLogin)
#reports any connection errors
def connectionLost(self,reason):
reactor.stop()
#drops the connection
def _ebLogin(self,result):
print result
self.transport.loseConnection()
def _programUtility(self,result):
print result
return self.logout()
def _cbExpungeMessage(self,result):
return self.expunge().addCallback(self._programUtility)
def _cbDeleteMessage(self,result):
return self.setFlags("1:5",flags=r"\\Deleted",uid=False).addCallback(self._cbExpungeMessage)
#gets the mailbox list
def _getMessages(self,result):
return self.list("","*").addCallback(self._cbPickMailbox)
#selects the inbox desired
def _cbPickMailbox(self,result):
mbox='INBOX.Trash'
return self.select(mbox).addCallback(self._cbExamineMbox)
def _cbExamineMbox(self,result):
return self.fetchMessage("1:*",uid=False).addCallback(self._cbDeleteMessage)
class IMAP4ClientFactory(protocol.ClientFactory):
def buildProtocol(self,addr):
return IMAP4LocalClient()
def clientConnectionFailed(self,connector,reason):
print reason
reactor.stop()
reactor.connectTCP(host,port,IMAP4ClientFactory())
reactor.run()
Changed to:
def _cbDeleteMessage(self,result):
return self.setFlags("1:5",flags=['\\Deleted'],uid=False).addCallback(self._cbExpungeMessage)
thanks to Jean-Paul Calderone and it worked, setFlags requires a list, not just a string.
I think there are two problems here.
First, you're passing a string as the flags parameter to setFlags. Notice the documentation for that parameter: The flags to set (type: Any iterable of str). Try a list containing one string, instead.
Second, \\Deleted is probably not a flag the server you're interacting with supports. The standard deleted flag in IMAP4 is \Deleted.