Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ask-sdk-core/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ target/

# IntelliJ configs
*.iml
.idea/
4 changes: 2 additions & 2 deletions ask-sdk-core/CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ This release contains the following :
1.10.2
^^^^^^^

This release contains the following changes :
This release contains the following changes :

- `Bug fix <https://github.com/alexa/alexa-skills-kit-sdk-for-python/pull/99>`__ on delete persistence attributes, to delete attributes without checking if they are set.
- `Bug fix <https://github.com/alexa/alexa-skills-kit-sdk-for-python/pull/99>`__ on delete persistence attributes, to delete attributes without checking if they are set.
- Fix `type hints <https://github.com/alexa/alexa-skills-kit-sdk-for-python/pull/95>`__ on lambda_handler.
4 changes: 2 additions & 2 deletions ask-sdk-core/README.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
====================================================
====================================================
ASK SDK Core - Base components of Python ASK SDK
====================================================

Expand Down Expand Up @@ -40,7 +40,7 @@ Usage and Getting Started
-------------------------

Getting started guides, SDK Features, API references, samples etc. can
be found in the `technical documentation <https://developer.amazon.com/docs/alexa-skills-kit-sdk-for-python/overview.html>`_
be found at `Read The Docs <https://alexa-skills-kit-python-sdk.readthedocs.io/en/latest/>`_


Got Feedback?
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def process(self, handler_input, response):
:type handler_input: HandlerInput
:param response: Execution result of the Handler on
handler input.
:type response: Union[None, :py:class:`ask_sdk_model.Response`]
:type response: Union[None, :py:class:`ask_sdk_model.response.Response`]
:rtype: None
"""
raise NotImplementedError
10 changes: 10 additions & 0 deletions ask-sdk-core/ask_sdk_core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,13 @@ class PersistenceException(AskSdkException):
class ApiClientException(AskSdkException):
"""Exception class for ApiClient Adapter processing."""
pass


class TemplateLoaderException(AskSdkException):
"""Exception class for Template Loaders"""
pass


class TemplateRendererException(AskSdkException):
"""Exception class for Template Renderer"""
pass
29 changes: 26 additions & 3 deletions ask-sdk-core/ask_sdk_core/handler_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
#
import typing
from .response_helper import ResponseFactory
from .view_resolvers import TemplateFactory

if typing.TYPE_CHECKING:
from typing import Any
from typing import Any, Dict
from ask_sdk_model import RequestEnvelope
from ask_sdk_model.response import Response
from ask_sdk_model.services import ServiceClientFactory
from .attributes_manager import AttributesManager

Expand Down Expand Up @@ -48,11 +50,13 @@ class HandlerInput(object):
for calling Alexa services
:type service_client_factory:
ask_sdk_model.services.service_client_factory.ServiceClientFactory
:param template_factory: Template Factory to chain loaders and renderer
:type template_factory: :py:class:`ask_sdk_core.view_resolver.TemplateFactory`
"""
def __init__(
self, request_envelope, attributes_manager=None,
context=None, service_client_factory=None):
# type: (RequestEnvelope, AttributesManager, Any, ServiceClientFactory) -> None
context=None, service_client_factory=None, template_factory=None):
# type: (RequestEnvelope, AttributesManager, Any, ServiceClientFactory, TemplateFactory) -> None
"""Input to Request Handler, Exception Handler and Interceptors.

:param request_envelope: Request Envelope passed from Alexa
Expand All @@ -68,12 +72,15 @@ def __init__(
for calling Alexa services
:type service_client_factory:
ask_sdk_model.services.service_client_factory.ServiceClientFactory
:param template_factory: Template Factory to chain loaders and renderer
:type template_factory: :py:class:`ask_sdk_core.view_resolver.TemplateFactory`
"""
self.request_envelope = request_envelope
self.context = context
self.service_client_factory = service_client_factory
self.attributes_manager = attributes_manager
self.response_builder = ResponseFactory()
self.template_factory = template_factory

@property
def service_client_factory(self):
Expand All @@ -98,3 +105,19 @@ def service_client_factory(self, service_client_factory):
ServiceClientFactory
"""
self._service_client_factory = service_client_factory

def generate_template_response(self, template_name, data_map, **kwargs):
# type: (str, Dict, Any) -> Response
"""Generate response using skill response template and injecting data.

:param template_name: name of response template
:type template_name: str
:param data_map: map contains injecting data
:type data_map: Dict[str, object]
:param kwargs: Additional keyword arguments for loader and renderer.
:return: Skill Response output
:rtype: :py:class:`ask_sdk_model.response.Response`
"""
return self.template_factory.process_template(
template_name=template_name, data_map=data_map, handler_input=self,
**kwargs)
Empty file.
10 changes: 9 additions & 1 deletion ask-sdk-core/ask_sdk_core/skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .serialize import DefaultSerializer
from .handler_input import HandlerInput
from .attributes_manager import AttributesManager
from .view_resolvers import TemplateFactory
from .utils import RESPONSE_FORMAT_VERSION, user_agent_info
from .__version__ import __version__

Expand Down Expand Up @@ -137,6 +138,8 @@ def __init__(self, skill_configuration):
self.serializer = DefaultSerializer()
self.skill_id = skill_configuration.skill_id
self.custom_user_agent = skill_configuration.custom_user_agent
self.loaders = skill_configuration.loaders
self.renderer = skill_configuration.renderer

self.request_dispatcher = GenericRequestDispatcher(
options=skill_configuration
Expand Down Expand Up @@ -185,6 +188,10 @@ def invoke(self, request_envelope, context):
else:
factory = None

template_factory = TemplateFactory(
template_loaders=self.loaders,
template_renderer=self.renderer)

attributes_manager = AttributesManager(
request_envelope=request_envelope,
persistence_adapter=self.persistence_adapter)
Expand All @@ -193,7 +200,8 @@ def invoke(self, request_envelope, context):
request_envelope=request_envelope,
attributes_manager=attributes_manager,
context=context,
service_client_factory=factory)
service_client_factory=factory,
template_factory=template_factory)

response = self.request_dispatcher.dispatch(
handler_input=handler_input)
Expand Down
32 changes: 30 additions & 2 deletions ask-sdk-core/ask_sdk_core/skill_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
from .skill import CustomSkill, SkillConfiguration

if typing.TYPE_CHECKING:
from typing import Callable, TypeVar, Dict
from typing import Callable, TypeVar, Dict, List
from ask_sdk_model.services import ApiClient
from .attributes_manager import AbstractPersistenceAdapter
from ask_sdk_runtime.view_resolvers import (
AbstractTemplateLoader, AbstractTemplateRenderer)
T = TypeVar('T')


Expand All @@ -39,7 +41,7 @@ class SkillBuilder(AbstractSkillBuilder):
def __init__(self):
# type: () -> None
super(SkillBuilder, self).__init__()
self.custom_user_agent = None
self.custom_user_agent = None # type: str
self.skill_id = None

@property
Expand Down Expand Up @@ -110,6 +112,32 @@ def wrapper(event, context):
return skill.serializer.serialize(response_envelope) # type:ignore
return wrapper

def add_custom_user_agent(self, user_agent):
# type: (str) -> None
"""Adds the user agent to the skill instance.

This method adds the passed in user_agent to the skill, which is
reflected in the skill's response envelope.

:param user_agent: Custom User Agent string provided by the developer.
:type user_agent: str
:rtype: None
"""
if self.custom_user_agent is None:
self.custom_user_agent = user_agent
else:
self.custom_user_agent += " {}".format(user_agent)

def add_renderer(self, renderer):
# type: (AbstractTemplateRenderer) -> None
"""Register renderer to generate template responses.

:param renderer: Renderer to render the template
:type renderer: :py:class:`ask_sdk_runtime.view_resolvers.AbstractTemplateRenderer`
"""
super(SkillBuilder, self).add_renderer(renderer)
self.add_custom_user_agent("templateResolver")


class CustomSkillBuilder(SkillBuilder):
"""Skill Builder with api client and persistence adapter setter
Expand Down
Empty file.
77 changes: 77 additions & 0 deletions ask-sdk-core/ask_sdk_core/utils/view_resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
#
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights
# Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the
# License.
#

import typing

import os
import re

if typing.TYPE_CHECKING:
from typing import Any, Sequence


def split_locale(locale):
# type: (str) -> Sequence[str]
"""Function to extract language and country codes from the locale.

:param locale: A string indicating the user’s locale. For example: en-US.
:type locale: str
:return: Tuple of (language, country)
:rtype: (optional) Tuple(str,str)
:raises: ValueError for invalid locale values
"""
if not locale:
return None, None
match = re.match(r'^([a-z]{2})-([A-Z]{2})$', locale)
if match is None:
raise ValueError("Invalid locale: {}".format(locale))
return match.groups()


def append_extension_if_not_exists(file_path, file_extension):
# type: (str, str) -> str
"""Function to check if the file path already has file extension added to
it else append it with file extension argument if available.

:param file_path: Input file to check for extension existence
:type file_path: str
:param file_extension: File extension of the template to be loaded
:type file_extension: str
:return: File path with file extension
:rtype: str
"""
if not file_extension:
return file_path
extension = os.path.splitext(file_path)[-1]
if not extension:
return "{}.{}".format(file_path, file_extension)
return file_path


def assert_not_null(attribute, value):
# type: (Any, str) -> Any
"""Asserts that the given object is non-null and returns it.

:param attribute: Object to assert on
:param value: Field name to display in exception message if null
:return: Object if non null
:raises: ValueError if object is null
"""
if not attribute:
raise ValueError("{} is null".format(value))
return attribute
26 changes: 26 additions & 0 deletions ask-sdk-core/ask_sdk_core/view_resolvers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
#
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights
# Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the
# License.
#
# Importing the most commonly used component classes, for
# short-circuiting purposes.

from .access_ordered_template_content import AccessOrderedTemplateContent
from .file_system_template_loader import FileSystemTemplateLoader
from .locale_template_enumerator import LocaleTemplateEnumerator
from .lru_cache import LRUCache
from .template_content import TemplateContent
from .template_factory import TemplateFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
#
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights
# Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the
# License.
#
import time
from ask_sdk_core.utils.view_resolver import assert_not_null
import typing

if typing.TYPE_CHECKING:
from ask_sdk_core.view_resolvers import TemplateContent


class AccessOrderedTemplateContent(object):
"""Time based wrapper of :py:class:`ask_sdk_core.view_resolvers.TemplateContent`
for :py:class:`ask_sdk_core.view_resolvers.LRUCache` to manage.

AccessOrderedTemplateContent class is used for adding a timestamp in
milliseconds for the template_content object which is used during
caching to determine if the data is stale and needs to be evicted from
the cache after it crosses its time to live threshold value.

System time at particular instant is used for timestamp values hence
note the cache implementation depends on the time being constant.

i.e System clock can go backwards and time stamp is affected by this
updates.
https://docs.python.org/3/library/time.html#time.time

:param template_content: Template Content
:type template_content: py:class:`ask_sdk_core.view_resolvers.TemplateContent`
"""
def __init__(self, template_content):
# type: (TemplateContent) -> None
"""Wrap the TemplateContent object with a timestamp for LRU caching.

:param template_content: Template Content
:type template_content: py:class:`ask_sdk_core.view_resolvers.TemplateContent`
"""
self.template_content = assert_not_null(template_content,
"Template Content")
self.time_stamp_millis = int(round(time.time() * 1000))

Loading