I have an use case where multi-inheritance seems the right way to go. But it implies sharing attributes between "sibling" classes, attributes that are initialized on other classes (so somehow unknown for them).
I'm asking if this below is a "right" and "pythonic" model, or should I better go with a dertivated-classes model.
Let's say we want to develop different deliverers, which will take some source data, apply some format to it, and send it through some channel. And this three parts (data - format - send) can be customizable for each case.
First, come code to make the examples below working:
import sys
PY3 = not sys.version_info < (3,)
from string import Template
import csv, io, smtplib, requests, os
def read_test_movies(year_from, year_to, genre= None):
TEST_MOVIES= [
{'year': 1971, 'release': '01/01/1971', 'genre': 'thriller', 'title': 'Play Misty for Me'},
{'year': 1973, 'release': '02/02/1973', 'genre': 'romantic', 'title': 'Breezy'},
{'year': 1976, 'release': '03/03/1976', 'genre': 'western', 'title': 'The Outlaw'},
{'year': 1986, 'release': '04/04/1986', 'genre': 'war', 'title': 'Heartbreak'},
{'year': 1988, 'release': '05/05/1988', 'genre': 'music', 'title': 'Bird'},
{'year': 1992, 'release': '06/06/1992', 'genre': 'western', 'title': 'Unforgiven'},
{'year': 1995, 'release': '07/07/1995', 'genre': 'romantic', 'title': 'The Bridges of Madison County'},
{'year': 2000, 'release': '08/08/2000', 'genre': 'space', 'title': 'Space Cowboys'},
{'year': 2003, 'release': '09/09/2003', 'genre': 'trhiller', 'title': 'Mystic River'},
{'year': 2004, 'release': '10/10/2004', 'genre': 'sports', 'title': 'Million Dollar Baby'},
{'year': 2006, 'release': '11/11/2006', 'genre': 'war', 'title': 'Flags of Our Fathers'},
{'year': 2006, 'release': '12/12/2006', 'genre': 'war', 'title': 'Letters from Iwo Jima'},
{'year': 2008, 'release': '13/11/2008', 'genre': 'drama', 'title': 'Changeling'},
{'year': 2008, 'release': '14/10/2008', 'genre': 'drama', 'title': 'Gran Torino'},
{'year': 2009, 'release': '15/09/2009', 'genre': 'sports', 'title': 'Invictus'},
{'year': 2010, 'release': '16/08/2010', 'genre': 'drama', 'title': 'Hereafter'},
{'year': 2011, 'release': '17/07/2011', 'genre': 'drama', 'title': 'J. Edgar'},
{'year': 2014, 'release': '18/06/2014', 'genre': 'war', 'title': 'American Sniper'},
{'year': 2016, 'release': '19/05/2016', 'genre': 'drama', 'title': 'Sully'}
]
out= []
for m in TEST_MOVIES:
if year_from <= m['year'] and m['year'] <= year_to:
if genre is None or (genre is not None and genre == m['genre']):
out.append(m)
return out
Being this three parts (data - format - send) so distinguishable, we would start with these interface-like classes (I guess abc could be used too):
class ITheData(object):
def __init__(self, year_from, year_to, genre= None):
self.year_from= year_from
self.year_to = year_to
self.genre = genre
def readMovies(self):
raise NotImplementedError('%s.readMovies() must be implemented' % self.__class__.__name__)
class ITheFormat(object):
def filename(self):
raise NotImplementedError('%s.filename() must be implemented' % self.__class__.__name__)
def make(self):
raise NotImplementedError('%s.make() must be implemented' % self.__class__.__name__)
class ITheSend(object):
def send(self):
raise NotImplementedError('%s.send() must be implemented' % self.__class__.__name__)
For each custom deliver, we will subclass the three of them, and put them together in a class like:
class ITheDeliverer(ITheData, ITheFormat, ITheSend):
def deliver(self):
raise NotImplementedError('%s.deliver() must be implemented' % self.__class__.__name__)
So, we could have two different data sources. Apart from source, they may differ on post-processing actions. Although for simplicity I'm just doing a self.readMovies() all over the place, it could be some other custom method on the subclass.
class TheIMDBData(ITheData):
def readMovies(self):
# movies = some_read_from_IMDB(self.genre, self.year_from, self.year_to)
movies= read_test_movies(self.year_from, self.year_to, self.genre)
return movies
class TheTMDbData(ITheData):
def readMovies(self):
# movies = some_read_from_TMDb(self.genre, self.year_from, self.year_to)
movies= read_test_movies(self.year_from, self.year_to, self.genre)
return movies
We could use also two different formats:
class TheTXTFormat(ITheFormat):
def filename(self):
# Here `genre`, `year_from` and `year_to` are unknown
params= {'genre': self.genre, 'year_from': self.year_from, 'year_to': self.year_to}
return Template('movies_of_${genre}_from_${year_from}_to_${year_to}.txt').substitute(**params)
def make(self):
# Here `readMovies()` is unknown
strio = PY3 and io.StringIO() or io.BytesIO()
for movie in self.readMovies():
line= Template('$title, released on $release').substitute(**movie)
line+= '\n'
strio.write(line)
strio.seek(0)
return strio.read()
class TheCSVFormat(ITheFormat):
def filename(self):
# Here `genre`, `year_from` and `year_to` are unknown
params= {'genre': self.genre, 'year_from': self.year_from, 'year_to': self.year_to}
return Template('movies_of_${genre}_from_${year_from}_to_${year_to}.csv').substitute(**params)
def make(self):
# Here `readMovies()` is unknown
strio = PY3 and io.StringIO() or io.BytesIO()
writer = csv.writer(strio, delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL)
header = ('Title', 'Release')
writer.writerow(header)
for movie in self.readMovies():
writer.writerow((movie['title'], movie['release']))
strio.seek(0)
return strio.read()
And two different sending channels:
class TheMailSend(ITheSend):
host = 'localhost'
sender = 'movie#spammer.com'
receivers = ['movie#spammed.com']
def send(self):
# Here `make()` is unknown
print('TheMailSend.send() Sending to %s' % str(self.receivers))
try:
message = self.make() # Format agnostic
smtpObj = smtplib.SMTP(self.host)
smtpObj.sendmail(self.sender, self.receivers, message)
return True, 'ok'
except Exception as ss:
return False, str(ss)
class TheWSSend(ITheSend):
url = 'spammed.com/movies/send'
def send(self):
# Here `make()` is unknown
print('TheWSSend.send() Sending to %s' % str(self.url))
try:
content = self.make() # Format agnostic
s= requests.Session()
response= s.post(url= self.url, data= {'content': content})
s.close()
if response.status_code == 200:
return True, 'ok'
else:
return False, response.status_code
except Exception as ss:
return False, str(ss)
So, we could end with some deliverers like these:
class TheIMDBToTXTFile(ITheDeliverer, TheIMDBData, TheTXTFormat):
def __init__(self, year_from, year_to, genre= None):
TheIMDBData.__init__(self, year_from, year_to, genre)
def deliver(self):
filepath= os.path.join('/tmp', self.filename())
f= open(filepath, 'w')
f.write(self.make())
f.close()
print('TheIMDBToTXTFile.deliver() => Successfully delivered to %s' % str(filepath))
class TheIMDBToWS(ITheDeliverer, TheIMDBData, TheTXTFormat, TheWSSend):
def __init__(self, year_from, year_to, genre=None):
TheIMDBData.__init__(self, year_from, year_to, genre)
def deliver(self):
ok, msg = self.send()
if ok:
print('TheIMDBToWS.deliver() => Successfully delivered!')
else:
print('TheIMDBToWS.deliver() => Error delivering: %s' % str(msg))
class TheTMDbToMail(ITheDeliverer, TheTMDbData, TheCSVFormat, TheMailSend):
def __init__(self, year_from, year_to, genre=None):
TheTMDbData.__init__(self, year_from, year_to, genre)
def deliver(self):
ok, msg= self.send()
if ok:
print('TheTMDbToMail.deliver() => Successfully delivered!')
else:
print('TheTMDbToMail.deliver() => Error delivering: %s' % str(msg))
And they work fine -with obvious connection errors-:
>>> imdbToTxt = TheIMDBToTXTFile(year_from= 2000, year_to= 2010)
>>> imdbToTxt.deliver()
TheIMDBToTXTFile.deliver() => Successfully delivered to /tmp/movies_of_None_from_200_to_2010.txt
>>>
>>> imdbToWs = TheIMDBToWS(year_from= 2000, year_to= 2010)
>>> imdbToWs.deliver()
TheWSSend.send() Sending to http://spammed.com/movies/send?
TheIMDBToWS.deliver() => Error delivering: 405
>>>
>>> tmdbToMail = TheTMDbToMail(year_from= 1980, year_to= 2019, genre= 'war')
>>> tmdbToMail.deliver()
TheMailSend.send() Sending to ['movie#spammed.com']
TheTMDbToMail.deliver() => Error delivering: [Errno 111] Connection refused
But, as commented, some attributes are unknown for some classes, and the linter is -obviously- complaining about it:
Instance of 'TheTXTFormat' has no 'genre' member
Instance of 'TheTXTFormat' has no 'year_from' member
Instance of 'TheTXTFormat' has no 'year_to' member
Instance of 'TheTXTFormat' has no 'readMovies' member
Instance of 'TheCSVFormat' has no 'genre' member
Instance of 'TheCSVFormat' has no 'year_from' member
Instance of 'TheCSVFormat' has no 'year_to' member
Instance of 'TheCSVFormat' has no 'readMovies' member
Instance of 'TheMailSend' has no 'make' member
Instance of 'TheWSSend' has no 'make' member
So, the question remains: is here multi-inheritance a good model?
The alternatives could be: a derivated-classes model, or just independent classes and passing around parameters like data or formatter. But none of them seem so simple as multi-inheritance (although they'd fix linter -and probably conceptual- problems).
I don't think inheritance is a good model here. You have lots of classes and it gets messy.
I think It's ok to implement inheritance for different "flavors" of the same step, of using the "Template pattern" described here.
from abc import ABC, abstractmethod
class ITheSend(ABC):
def run(self) -> None:
"""
The template method defines the skeleton of an algorithm.
"""
self.pre_send_hook()
self.send()
self.post_send_hook()
# These operations have to be implemented in subclasses.
#abstractmethod
def send(self) -> None:
pass
# These are "hooks." Subclasses may override them, but it's not mandatory
# since the hooks already have default (but empty) implementation. Hooks
# provide additional extension points in some crucial places of the
# algorithm.
def pre_send_hook(self) -> None:
pass
def post_send_hook(self) -> None:
pass
class TheMailSend(ITheSend):
host = 'localhost'
sender = 'movie#spammer.com'
receivers = ['movie#spammed.com']
def send(self, message):
print('TheMailSend.send() Sending to %s' % str(self.receivers))
try:
smtpObj = smtplib.SMTP(self.host)
smtpObj.sendmail(self.sender, self.receivers, message)
return True, 'ok'
except Exception as ss:
return False, str(ss)
class TheWSSend(ITheSend):
url = 'spammed.com/movies/send'
def send(self, content):
print('TheWSSend.send() Sending to %s' % str(self.url))
try:
s= requests.Session()
response= s.post(url= self.url, data= {'content': content})
s.close()
if response.status_code == 200:
return True, 'ok'
else:
return False, response.status_code
except Exception as ss:
return False, str(ss)
However, for the full chain I'd explore composition instead of inheritance.
class Chain:
def __init__(self, data, format, send):
self._data = data
self._format = format
self._send = send
def deliver(self):
data = self._data.execute()
format = self._format.execute(data)
send = self._send.execute(format)
the_IMDB_to_TXT_file = Chain(send=ITheDeliverer, data=TheIMDBData, format=TheTXTFormat)
Related
Given the following API response from pinecone (https://www.pinecone.io/docs/api/operation/query/)
results = {'matches': [{'id': 'yral5m',
'metadata': {'subreddit': '2qkq6',
'text': 'Black Friday SaaS Deals - 2022'},
'score': 0.772717535,
'sparseValues': {},
'values': []},
{'id': 'yqypa5',
'metadata': {'subreddit': '2qkq6',
'text': 'B2B sales interface.'},
'score': 0.74192214,
'sparseValues': {},
'values': []}],
'namespace': ''}
i am simply trying to return the JSON results from a function. (from a service to a controller) and getting a range of errors:
doing so with:
return results yields RecursionError: maximum recursion depth exceeded in comparison
return json.dumps(results) yeilds TypeError: Object of type QueryResponse is not JSON serializable
*QueryResponse is the type returned from pinecone
return jsonpickle.encode(query_results) yeilds "null"
Very lost any advice would be appreciated!!
full code example:
Controller:
#router.post("/query/")
async def semantic_search(query: str, ):
result = await TopicQueryService.query(query)
return result
Service method:
#staticmethod
async def query(query) -> str:
index = PineConeService.get_or_create_index("openai")
embed = GPT3Service.get_embedding(query)
query_results = index.query(
vector=embed,
top_k=2,
include_metadata=True
)
return json.dumps(query_results)
Replacing Service Method query results with the logged response from index. query works fine eg below. Leading me to believe it is due to the QueryResponse object pinecone returns.
#staticmethod
async def query(query) -> str:
index = PineConeService.get_or_create_index("openai")
embed = GPT3Service.get_embedding(query)
logger.info(embed)
query_results = {'matches': [{'id': 'yral5m',
'metadata': {'subreddit': '2qkq6',
'text': 'Black Friday SaaS Deals - 2022'},
'score': 0.772717535,
'sparseValues': {},
'values': []},
{'id': 'yqypa5',
'metadata': {'subreddit': '2qkq6',
'text': 'B2B sales interface.'},
'score': 0.74192214,
'sparseValues': {},
'values': []}],
'namespace': ''}
return json.dumps(query_results)
Somewhat of a solution -> just iterate over and build an object to return, not ideal though
``blah = []
for x in query_results.matches:
blah.append({
"id": x.id,
"metadata": x.metadata,
"score": x.score
})
json.dumps(blah)``
You can use the method .to_dict() from the QueryResponse object:
#staticmethod
async def query(query) -> str:
index = PineConeService.get_or_create_index("openai")
embed = GPT3Service.get_embedding(query)
query_results = index.query(
vector=embed,
top_k=2,
include_metadata=True
)
return json.dumps(query_results.to_dict())
I have a function that verifies if a given input string is a proper GCP zone:
def validate_zone(compute, project_id, zone):
try:
zone_response = compute.zones().get(project=project_id, zone=zone).execute()
print(zone_response)
print(zone_response.return_value)
if ['status'] in zone_response:
zone_details = {
'status': zone_response['status'],
'region': zone_response['region'],
'name': zone_response['name']
}
return zone_details
else:
return "Zone {} not found for project {}".format(zone, project_id)
except HttpError as error:
print("Error calling zone {}: \n {}".format(zone, error))
I am trying to write a test to verify that but I can't mock the output of the compute method correctly.
#patch('googleapiclient.discovery')
def test_validate_zone(self, mock_response):
compute = mock_response.build(serviceName='compute', version='v1')
compute.zones().get(project_id=self.project_id, zone=self.zone).execute().return_value = {
'status': 'status',
'region': 'region',
'name': 'name'
}
zone_response = inventory.validate_zone(compute, self.project_id, self.zone)
print(zone_response)
This results in the zone_response output being a MagicMock object with its return_value being correct as developed in the test.
zone_response = MagicMock name='discovery.build().zones().get().execute()' id='139870134525456'
zone_response.return_value = {'status': 'status', 'region': 'region', 'name': 'name'}
Any ideas on what I'm doing wrong? I've been trying to write tests for this for quite a while so maybe my approach is just off.
Turns out the issue was the () on the execute method in the test. So the correct test should be:
#patch('inventory.discovery.build', serviceName='compute', version='v1')
def test_validate_zone(self, compute):
print(compute)
compute.zones().get(project_id=self.project_id, zone=self.zone).execute.return_value = {
'status': 'status',
'region': 'region',
'name': 'name'
}
zone_response = inventory.validate_zone(compute, self.project_id, self.zone)
print(zone_response)
Source can be found at: https://realpython.com/python-mock-library/#managing-a-mocks-return-value
I have a enum OsTypeEnum:
class OsTypeEnum(Enum):
WINDOWS = 100
LINUX = 200
MAC = 300
ANDROID = 400
IOS = 500
#classmethod
def get_list(cls):
ret = []
for e in cls:
ret.append({'name': e.name, 'value': e.value})
return ret
I need to hide ANDROID and IOS from calling the get_list function, but don't want to remove them from the OsTypeEnum.
Rather than hard-code the list of members to exclude, I would make that information part of each member instead. I'll show code using the aenum library1, but it can be done using the stdlib version, just more verbosely.
from aenum import Enum
class OsTypeEnum(Enum):
#
_init_ = 'value type'
#
WINDOWS = 100, 'pc'
LINUX = 200, 'pc'
MAC = 300, 'pc'
ANDROID = 400, 'mobile'
IOS = 500, 'mobile'
#
#classmethod
def get_pc_list(cls):
ret = []
for e in cls:
if e.type == 'pc':
ret.append({'name': e.name, 'value': e.value})
return ret
#
#classmethod
def get_mobile_list(cls):
ret = []
for e in cls:
if e.type == 'mobile':
ret.append({'name': e.name, 'value': e.value})
return ret
By storing that extra piece of information on the member, you are more easily able to get your original list, plus other lists.
In use, it looks like:
>>> OsTypeEnum.get_pc_list()
[{'name': 'WINDOWS', 'value': 100}, {'name': 'LINUX', 'value': 200}, {'name': 'MAC', 'value': 300}]
>>> OsTypeEnum.get_mobile_list()
[{'name': 'ANDROID', 'value': 400}, {'name': 'IOS', 'value': 500}]
1 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.
You can create excluded enums list
class OsTypeEnum(Enum):
WINDOWS = 100
LINUX = 200
MAC = 300
ANDROID = 400
IOS = 500
#classmethod
def get_list(cls):
ret = []
for e in cls:
if e not in cls.__get_excluded():
ret.append({'name': e.name, 'value': e.value})
return ret
#classmethod
def __get_excluded(cls):
return [cls.ANDROID, cls.IOS]
This seems like a good candidate for an if statement. if the enum is not ANDROID or IOS, then add it to the return value.
I want to create develop a chatbot using Lex's interface, as a first step i found a ScheduleAppointment default bot so i decided to do adjustments to it. The default prompts in this bot are date, time and appointmenttype. As a first step i went to using blueprint lex-make-appointment-python https://console.aws.amazon.com/lambda/home?region=us-east-1#/create/new?bp=lex-make-appointment-python and had to change lots of the default stuff there, for example it has an exact appointment time and days etc while the version i want to work on before developing it any further in lambda is one that would take ANY TIME and ANY DAY for example i can't get an error if i ask to schedule an appointment tomorrow but if the bot asks for date and i put something like hfujdhfu or banana i should be asked what is the date? again.
All that being said and done this is my version of that code after adjusting it :
import json
import dateutil.parser
import datetime
import time
import os
import math
import random
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
""" --- Helpers to build responses which match the structure of the necessary dialog actions --- """
def elicit_slot(session_attributes, intent_name, slots, slot_to_elicit, message, response_card):
return {
'sessionAttributes': session_attributes,
'dialogAction': {
'type': 'ElicitSlot',
'intentName': intent_name,
'slots': slots,
'slotToElicit': slot_to_elicit,
'message': message,
'responseCard': response_card
}
}
def confirm_intent(session_attributes, intent_name, slots, message, response_card):
return {
'sessionAttributes': session_attributes,
'dialogAction': {
'type': 'ConfirmIntent',
'intentName': intent_name,
'slots': slots,
'message': message,
'responseCard': response_card
}
}
def close(session_attributes, fulfillment_state, message):
response = {
'sessionAttributes': session_attributes,
'dialogAction': {
'type': 'Close',
'fulfillmentState': fulfillment_state,
'message': message
}
}
return response
def delegate(session_attributes, slots):
return {
'sessionAttributes': session_attributes,
'dialogAction': {
'type': 'Delegate',
'slots': slots
}
}
def build_response_card(title, subtitle, options):
"""
Build a responseCard with a title, subtitle, and an optional set of options which should be displayed as buttons.
"""
buttons = None
if options is not None:
buttons = []
for i in range(min(5, len(options))):
buttons.append(options[i])
return {
'contentType': 'application/vnd.amazonaws.card.generic',
'version': 1,
'genericAttachments': [{
'title': title,
'subTitle': subtitle,
'buttons': buttons
}]
}
""" --- Helper Functions --- """
def parse_int(n):
try:
return int(n)
except ValueError:
return float('nan')
def try_ex(func):
"""
Call passed in function in try block. If KeyError is encountered return None.
This function is intended to be used to safely access dictionary.
Note that this function would have negative impact on performance.
"""
try:
return func()
except KeyError:
return None
def isvalid_date(date):
try:
dateutil.parser.parse(date)
return True
except ValueError:
return False
def build_time_output_string(appointment_time):
hour, minute = appointment_time.split(':') # no conversion to int in order to have original string form. for eg) 10:00 instead of 10:0
if int(hour) > 12:
return '{}:{} p.m.'.format((int(hour) - 12), minute)
elif int(hour) == 12:
return '12:{} p.m.'.format(minute)
elif int(hour) == 0:
return '12:{} a.m.'.format(minute)
return '{}:{} a.m.'.format(hour, minute)
def make_appointment(intent_request):
"""
Performs dialog management and fulfillment for booking a dentists appointment.
Beyond fulfillment, the implementation for this intent demonstrates the following:
1) Use of elicitSlot in slot validation and re-prompting
2) Use of confirmIntent to support the confirmation of inferred slot values, when confirmation is required
on the bot model and the inferred slot values fully specify the intent.
"""
appointment_type = intent_request['currentIntent']['slots']['AppointmentType']
date = intent_request['currentIntent']['slots']['Date']
appointment_time = intent_request['currentIntent']['slots']['Time']
source = intent_request['invocationSource']
output_session_attributes = intent_request['sessionAttributes'] if intent_request['sessionAttributes'] is not None else {}
booking_map = json.loads(try_ex(lambda: output_session_attributes['bookingMap']) or '{}')
if source == 'DialogCodeHook':
# Perform basic validation on the supplied input slots.
slots = intent_request['currentIntent']['slots']
if not appointment_type:
return elicit_slot(
output_session_attributes,
intent_request['currentIntent']['name'],
intent_request['currentIntent']['slots'],
'AppointmentType',
{'contentType': 'PlainText', 'content': 'What type of appointment would you like to schedule?'},
build_response_card(
'Specify Appointment Type', 'What type of appointment would you like to schedule?',
build_options('AppointmentType', appointment_type, date, None)
)
)
if appointment_type and not date:
return elicit_slot(
output_session_attributes,
intent_request['currentIntent']['name'],
intent_request['currentIntent']['slots'],
'Date',
{'contentType': 'PlainText', 'content': 'When would you like to schedule your {}?'.format(appointment_type)},
build_response_card(
'Specify Date',
'When would you like to schedule your {}?'.format(appointment_type),
build_options('Date', appointment_type, date, None)
)
)
)
message_content = 'What time on {} works for you? '.format(date)
if appointment_time:
output_session_attributes['formattedTime'] = build_time_output_string(appointment_time)
)
available_time_string = build_available_time_string(appointment_type_availabilities)
return elicit_slot(
output_session_attributes,
intent_request['currentIntent']['name'],
slots,
'Time',
{'contentType': 'PlainText', 'content': '{}{}'.format(message_content, available_time_string)},
build_response_card(
'Specify Time',
'What time works best for you?',
build_options('Time', appointment_type, date, booking_map)
)
)
return delegate(output_session_attributes, slots)
duration = get_duration(appointment_type)
""" --- Intents --- """
def dispatch(intent_request):
"""
Called when the user specifies an intent for this bot.
"""
logger.debug('dispatch userId={}, intentName={}'.format(intent_request['userId'], intent_request['currentIntent']['name']))
intent_name = intent_request['currentIntent']['name']
# Dispatch to your bot's intent handlers
if intent_name == 'MakeAppointment':
return make_appointment(intent_request)
raise Exception('Intent with name ' + intent_name + ' not supported')
""" --- Main handler --- """
def lambda_handler(event, context):
"""
Route the incoming request based on intent.
The JSON body of the request is provided in the event slot.
"""
# By default, treat the user request as coming from the America/New_York time zone.
os.environ['TZ'] = 'America/New_York'
time.tzset()
logger.debug('event.bot.name={}'.format(event['bot']['name']))
return dispatch(event)
There is ) at line 171 and line 175 without any ( which must be causing syntax error. many code lines are unreachable because you are coding return before them but those won't cause syntax error.
You can watch the logs on Cloudwatch.
The blueprint which you are using is very complex and not beginner friendly. You really should be using this blueprint for starting. That is my suggestion.
Also, since you are using response_card so please be aware that the response cards won't be shown in the Lex Console window. It will work in Facebook and Slack though.
I am working on creating a python module for getting stocks data.
I have a dictionary:
{'StockSymbol': 'AMD', 'LastTradeTime': '4:00PM EST', 'ChangePercent': '+0.58', 'ID': '327', 'LastTradeDateTimeLong': 'Mar 10, 4:00PM EST', 'Index': 'NASDAQ', 'LastTradeWithCurrency': '13.91', 'LastTradeDateTime': '2017-03-10T16:00:02Z', 'LastTradePrice': '13.91', 'LastTradeSize': '0', 'PreviousClosePrice': '13.33'}
Currently I have 11 methods such as:
class Stock(object):
def getSymbol():
return self.data['StockSymbol']
def getLastTradeTime():
return self.data['LastTradeTime']
........
I use it as:
google = Stock('GOOG')
print(google.getLastTradeTime()) //4:00PM EST
My question is, Is it possible to generate these methods dynamically?
So I could do google.getLastTradeSize() etc without defining them.
Here is a Python fiddle: https://repl.it/GSG1
In Python there's a design pattern called bunch, it works like this, I believe it can solve your problem:
class Bunch(dict):
def __init__(self, *args, **kwargs):
super(Bunch, self).__init__(*args, **kwargs)
self.__dict__ = self
def __getattribute__(self, item):
try:
return object.__getattribute__(self, item)
except:
return None
my_dict = {'StockSymbol': 'AMD', 'LastTradeTime': '4:00PM EST', 'ChangePercent': '+0.58', 'ID': '327',
'LastTradeDateTimeLong': 'Mar 10, 4:00PM EST', 'Index': 'NASDAQ', 'LastTradeWithCurrency': '13.91',
'LastTradeDateTime': '2017-03-10T16:00:02Z', 'LastTradePrice': '13.91', 'LastTradeSize': '0',
'PreviousClosePrice': '13.33'}
obj = Bunch(**my_dict)
print obj.StockSymbol
print obj.LastTradeTime
print obj.key_not_exist
And we get:
AMD
4:00PM EST
None
So you don't have to define your so-called gettter method, like what you do in Java/C++;
PS: in a real project, you can also inherit from this Bunch class.
===== Another optional ======
you can use pythonic-toolbox, a 3rd party lib maintained by me, that contains many useful tools, demos.
For your case, I think DictObj is a good choicehere in this lib.
my_dict = {'StockSymbol': 'AMD', 'LastTradeTime': '4:00PM EST', 'ChangePercent': '+0.58', 'ID': '327',
'LastTradeDateTimeLong': 'Mar 10, 4:00PM EST', 'Index': 'NASDAQ', 'LastTradeWithCurrency': '13.91',
'LastTradeDateTime': '2017-03-10T16:00:02Z', 'LastTradePrice': '13.91', 'LastTradeSize': '0',
'PreviousClosePrice': '13.33'}
from pythonic_toolbox.utils.dict_utils import DictObj
obj = DictObj(my_dict)
assert hasattr(obj, 'StockSymbol')
assert obj.StockSymbol == 'AMD'
assert 'StockSymbol' in obj
assert obj.pop('LastTradePrice') == '13.91'
assert 'LastTradePrice' not in obj # 'LastTradePrice' is popped up, so obj don't have attribute LastTradePrice anymore
del obj.LastTradeSize
assert not hasattr(obj, 'LastTradeSize') # besides pop key as a dict, you can also delete it like an attribute
obj.greetings = 'hello world' # assign new key/attribute
assert obj['greetings'] == 'hello world'
If you don't want others to change your DictObj attributes(modify, del, add), you can also use FinalDictObj, by from pythonic_toolbox.utils.dict_utils import FinalDictObj