From 71a11966ac4a002ae7263e9d7471b04e50eb173b Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Mon, 25 Dec 2023 17:51:07 +0100 Subject: [PATCH 01/14] Fixed #86 - Logs are now dumped to STDOUt if setting active --- pyttman/tools/pyttmancli/terraforming.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyttman/tools/pyttmancli/terraforming.py b/pyttman/tools/pyttmancli/terraforming.py index 3e0f956..be5d36f 100644 --- a/pyttman/tools/pyttmancli/terraforming.py +++ b/pyttman/tools/pyttmancli/terraforming.py @@ -145,6 +145,11 @@ def bootstrap_app(module: str = None, devmode: bool = False, logger.setLevel(logging.DEBUG) logger.addHandler(logging_handle) + if settings.LOG_TO_STDOUT: + stdout_handle = logging.StreamHandler(sys.stdout) + stdout_handle.setFormatter(logging.Formatter(logging_format)) + logger.addHandler(stdout_handle) + # Set the configured instance of logger to the pyttman.PyttmanLogger object pyttman.logger.LOG_INSTANCE = logger From d739aed15b4c13eee9e678d4bc59aa903a7c6988 Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Mon, 25 Dec 2023 17:51:41 +0100 Subject: [PATCH 02/14] Removed unnecessary clutter from log entries --- pyttman/tools/pyttmancli/terraforming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyttman/tools/pyttmancli/terraforming.py b/pyttman/tools/pyttmancli/terraforming.py index be5d36f..c2b9a75 100644 --- a/pyttman/tools/pyttmancli/terraforming.py +++ b/pyttman/tools/pyttmancli/terraforming.py @@ -141,7 +141,7 @@ def bootstrap_app(module: str = None, devmode: bool = False, logging_format = logging.BASIC_FORMAT logging_handle.setFormatter(logging.Formatter(logging_format)) - logger = logging.getLogger(f"Pyttman logger on app {app_name}") + logger = logging.getLogger(app_name) logger.setLevel(logging.DEBUG) logger.addHandler(logging_handle) From f57999eb2e97bdb03f514790f62a86837569fbb1 Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Mon, 25 Dec 2023 18:34:14 +0100 Subject: [PATCH 03/14] Offer an interface to ignore chars in entity values. Dots, commas, questionmark, semicolon and exclamation and dash are not included in the entity value by default --- pyttman/core/entity_parsing/parsers.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/pyttman/core/entity_parsing/parsers.py b/pyttman/core/entity_parsing/parsers.py index bc8209c..0952c8e 100644 --- a/pyttman/core/entity_parsing/parsers.py +++ b/pyttman/core/entity_parsing/parsers.py @@ -1,3 +1,5 @@ +import re +import string import typing from itertools import zip_longest from typing import Type, Dict, Union @@ -19,6 +21,8 @@ class EntityFieldValueParser(PrettyReprMixin): EntityParser Api component: 'EntityField'. """ __repr_fields__ = ("identifier", "exclude", "prefixes", "suffixes") + ignore_chars = True + chars_to_ignore = ".,;:!?-" def __init__(self, prefixes: tuple | typing.Callable = None, @@ -122,14 +126,21 @@ def parse_message(self, output = [] word_index = 0 - casefolded_msg = message.lowered_content() + message_lowered = message.lowered_content() + if self.ignore_chars: + # Strip away special chars + message_lowered = [ + re.sub(rf"[{self.chars_to_ignore}]", "", word) + for word in message_lowered + ] + common_occurrences = tuple( - OrderedSet(casefolded_msg).intersection(self.valid_strings)) + OrderedSet(message_lowered).intersection(self.valid_strings)) for i, word in enumerate(common_occurrences): if i > self.span and not self.as_list: break - word_index = casefolded_msg.index(word) + word_index = message_lowered.index(word) output.append(message.content[word_index]) if len(output) > 1: @@ -142,6 +153,13 @@ def parse_message(self, self._validate_prefixes_suffixes(message) if self.value: + if self.ignore_chars: + if isinstance(self.value.value, list): + for i, elem in enumerate(self.value.value): + elem = re.sub(rf"[{self.chars_to_ignore}]", "", elem) + self.value.value[i] = elem + elif isinstance(self.value.value, str): + self.value.value = re.sub(rf"[{self.chars_to_ignore}]", "", self.value.value) entity = self.value if entity.value == self.default: return From f2ad8147e7128f8286bc6239782609554fe289b7 Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Mon, 25 Dec 2023 18:34:47 +0100 Subject: [PATCH 04/14] Test for 'pre_processor' optional lambda in EntityField classes --- .../test_entity_fields/test_entity_parsing.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/core/entity_parsing/test_entity_fields/test_entity_parsing.py b/tests/core/entity_parsing/test_entity_fields/test_entity_parsing.py index 3758c07..0f32ea4 100644 --- a/tests/core/entity_parsing/test_entity_fields/test_entity_parsing.py +++ b/tests/core/entity_parsing/test_entity_fields/test_entity_parsing.py @@ -409,3 +409,22 @@ class IntentClass(ImplementedTestIntent): cheese_type = StringEntityField(valid_strings=("blue", "yellow"), default="blue", as_list=True) + + +class PyttmanInternalTestEntityPreProcessor( + PyttmanInternalTestBaseCase +): + mock_message = Message("I would like some tea, please.") + process_message = True + expected_entities = { + "beverage": "Tea", + } + + class IntentClass(ImplementedTestIntent): + """ + Tests that the 'pre_processor' callable is executed and + can process the return value before it's spat out. + """ + beverage = StringEntityField(valid_strings=("tea", "coffee"), + pre_processor=lambda x: x.capitalize()) + From a9f0ce0d6b63ad1a7fd39b1feac10c286e38fcac Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Mon, 25 Dec 2023 18:42:27 +0100 Subject: [PATCH 05/14] When pyttman runfile / runclient / dev is executed, allow users to tab in their console (disregard ./\ symbols) --- pyttman/tools/pyttmancli/intents.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pyttman/tools/pyttmancli/intents.py b/pyttman/tools/pyttmancli/intents.py index 57789a7..b6b0f29 100644 --- a/pyttman/tools/pyttmancli/intents.py +++ b/pyttman/tools/pyttmancli/intents.py @@ -1,5 +1,6 @@ import code import os +import re import traceback from pathlib import Path @@ -28,7 +29,8 @@ class ShellMode(Intent, PyttmanCliComplainerMixin): example = "pyttman shell " help_string = "Opens a Python interactive shell with access to modules, " \ "app settings and the Pyttman 'app' object." - app_name = TextEntityField() + app_name = TextEntityField(default="", + pre_processor=lambda x: re.sub("[^a-zA-Z0-9_]", "", x)) def respond(self, message: Message) -> Reply | ReplyStream: app_name = message.entities["app_name"] @@ -48,7 +50,8 @@ class CreateNewApp(Intent, PyttmanCliComplainerMixin): Create a new Pyttman app. The directory is terraformed and prepared with a template project. """ - app_name = TextEntityField() + app_name = TextEntityField(default="", + pre_processor=lambda x: re.sub("[^a-zA-Z0-9_]", "", x)) lead = ("new",) trail = ("app",) ordered = True @@ -104,7 +107,8 @@ class RunAppInDevMode(Intent, PyttmanCliComplainerMixin): Run a Pyttman app in dev mode. This sets "DEV_MODE" to True and opens a chat interface in the terminal. """ - app_name = TextEntityField() + app_name = TextEntityField(default="", + pre_processor=lambda x: re.sub("[^a-zA-Z0-9_]", "", x)) fail_gracefully = True lead = ("dev",) example = "pyttman dev " @@ -147,7 +151,8 @@ class RunAppInClientMode(Intent, PyttmanCliComplainerMixin): "settings.py under 'CLIENT'.\n" \ f"Example: {example}" - app_name = TextEntityField() + app_name = TextEntityField(default="", + pre_processor=lambda x: re.sub("[^a-zA-Z0-9_]", "", x)) def respond(self, message: Message) -> Reply | ReplyStream: app_name = message.entities["app_name"] @@ -177,7 +182,8 @@ class RunFile(Intent, PyttmanCliComplainerMixin): help_string = "Run a singe file within a Pyttman app context. " \ f"Example: {example}" - app_name = TextEntityField() + app_name = TextEntityField(default="", + pre_processor=lambda x: re.sub("[^a-zA-Z0-9_]", "", x)) script_file_name = TextEntityField(prefixes=(app_name,)) def respond(self, message: Message) -> Reply | ReplyStream: From d14feeb69532d9fe69befcf6fdbb533fc604a9d9 Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Mon, 25 Dec 2023 18:44:55 +0100 Subject: [PATCH 06/14] # 87 - New field in EntityField classes: pre_processor. This offers users to write a lambda or leave a function ref, to a function which can mutate the value and return it processed, leaving the 'respond' method thinner. --- pyttman/core/entity_parsing/fields.py | 31 ++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/pyttman/core/entity_parsing/fields.py b/pyttman/core/entity_parsing/fields.py index d0ac249..4304187 100644 --- a/pyttman/core/entity_parsing/fields.py +++ b/pyttman/core/entity_parsing/fields.py @@ -23,15 +23,34 @@ class EntityFieldBase(EntityFieldValueParser, ABC): Not only to find the word(s) but also type-convert to a given datatype if a match is True. - """ default = None + """ + Specify a default return value, if no match is found. + """ type_cls = None + """ + The type_cls is the class which the value is converted to. + For example: int, str, float, etc. + """ identifier_cls = None + """ + Identifier class is a class which specializes in finding + patterns in text. You can provide a custom Identifier class + to further increase the granularity of the value you're looking for. + """ + pre_processor = None + """ + Pre-processor is a callable which is called before the + value is converted to the type_cls of the EntityField. + You can specify any callable here, and it will be called + with the value as its only argument. + """ def __init__(self, identifier: Type[Identifier] | None = None, default: Any = None, + pre_processor: callable = None, **kwargs): """ :param as_list: If set to True combined with providing 'valid_strings', @@ -48,6 +67,7 @@ def __init__(self, You can read more about Identifier classes in the Pyttman documentation. """ + self.pre_processor = pre_processor if self.type_cls is None or inspect.isclass(self.type_cls) is False: raise InvalidPyttmanObjectException("All EntityField classes " "must define a 'type_cls', " @@ -91,6 +111,15 @@ def convert_value(self, value: Any) -> Any: except Exception as e: raise TypeConversionFailed(from_type=type(value), to_type=self.type_cls) from e + + try: + if self.pre_processor is not None and callable(self.pre_processor): + converted_value = self.pre_processor(converted_value) + except Exception as e: + value_err = ValueError("The pre_processor callable '" + f"'{self.pre_processor}' failed: {e}") + raise TypeConversionFailed(from_type=type(value), + to_type=self.type_cls) from value_err return converted_value def before_conversion(self, value: Any) -> str: From 0636bf44c3c1b640f113253fead62d565516c841 Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Tue, 26 Dec 2023 00:08:51 +0100 Subject: [PATCH 07/14] Improve user feedback when starting an app with no abilities loaded --- pyttman/tools/pyttmancli/ability.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyttman/tools/pyttmancli/ability.py b/pyttman/tools/pyttmancli/ability.py index ecdb179..031e80a 100644 --- a/pyttman/tools/pyttmancli/ability.py +++ b/pyttman/tools/pyttmancli/ability.py @@ -40,6 +40,13 @@ def run_application(self) -> None: # #(used for attribute access in completion) app: PyttmanApp = None if (app := self.storage.get("app")) is not None: + if not app.client.message_router.abilities: + print("There are no abilities loaded, the app will not " + "respond to any messages. Create abilities by " + "running 'pyttman new ability' in the terminal.\n" + "Next up, add them to your app's settings.py file " + "under the ABILITIES key.") + exit(0) print(f"- Ability classes loaded: " f"{app.client.message_router.abilities}") app.start() From 0284ea208b32780d5b9c612845a21e58be99f0c5 Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Tue, 26 Dec 2023 00:43:43 +0100 Subject: [PATCH 08/14] Assign abilities to the `app` namespace for accessing individual ability classes by name --- pyttman/core/internals.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pyttman/core/internals.py b/pyttman/core/internals.py index 519c364..30a5f0a 100644 --- a/pyttman/core/internals.py +++ b/pyttman/core/internals.py @@ -139,9 +139,9 @@ class PyttmanApp(PrettyReprMixin): client: Any name: str | None = field(default=None) settings: Settings | None = field(default=None) - abilities: set = field(default_factory=set) hooks: LifecycleHookRepository = field( default_factory=lambda: LifecycleHookRepository()) + _abilities: set = field(default_factory=set) def start(self): """ @@ -152,3 +152,13 @@ def start(self): self.client.run_client() except Exception: warnings.warn(traceback.format_exc()) + + @property + def abilities(self): + return self._abilities + + @abilities.setter + def abilities(self, abilities): + for ability in abilities: + setattr(self, ability.__class__.__name__, ability) + self._abilities.add(ability) From 6e2ddf9a7f969239b35af6b6c30e6cdfe0337804 Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Tue, 26 Dec 2023 01:56:59 +0100 Subject: [PATCH 09/14] New script for developers, aiding in uploading builds to pypi. --- devtools/upload_pypi.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 devtools/upload_pypi.py diff --git a/devtools/upload_pypi.py b/devtools/upload_pypi.py new file mode 100644 index 0000000..9c92eb0 --- /dev/null +++ b/devtools/upload_pypi.py @@ -0,0 +1,27 @@ +import shutil +import subprocess +from pathlib import Path + +from setuptools import setup +from twine.commands.upload import upload + +# Replace these with your package information +package_name = "Pyttman" + +# Get the package version dynamically +# the file location is in a sibling directory, add it to the path + +# Upload to PyPI using twine +confirm = input("Deploying to PyPi.\n\n1: For pypi production, type 'production'" + "\n2: For test.pypi.org, type 'test'\n\n") + +if not Path("dist").exists(): + print("You need to build the package first. Run devtools/build.py.") + exit(0) + +print("Enter '__token__' for username, and the token for password") +if confirm == "production": + subprocess.run(["twine", "upload", "dist/*"]) +elif confirm == "test": + subprocess.run(["twine", "upload", "--repository-url", "https://test.pypi.org/legacy/", "dist/*"]) + From 4a66a8ebed8c99231dc3a781cbd11695271f74df Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Tue, 26 Dec 2023 01:57:24 +0100 Subject: [PATCH 10/14] removed pre_processor for pyttman cli --- pyttman/tools/pyttmancli/intents.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pyttman/tools/pyttmancli/intents.py b/pyttman/tools/pyttmancli/intents.py index b6b0f29..35ba770 100644 --- a/pyttman/tools/pyttmancli/intents.py +++ b/pyttman/tools/pyttmancli/intents.py @@ -29,8 +29,7 @@ class ShellMode(Intent, PyttmanCliComplainerMixin): example = "pyttman shell " help_string = "Opens a Python interactive shell with access to modules, " \ "app settings and the Pyttman 'app' object." - app_name = TextEntityField(default="", - pre_processor=lambda x: re.sub("[^a-zA-Z0-9_]", "", x)) + app_name = TextEntityField(default="") def respond(self, message: Message) -> Reply | ReplyStream: app_name = message.entities["app_name"] @@ -50,8 +49,7 @@ class CreateNewApp(Intent, PyttmanCliComplainerMixin): Create a new Pyttman app. The directory is terraformed and prepared with a template project. """ - app_name = TextEntityField(default="", - pre_processor=lambda x: re.sub("[^a-zA-Z0-9_]", "", x)) + app_name = TextEntityField(default="") lead = ("new",) trail = ("app",) ordered = True @@ -107,8 +105,7 @@ class RunAppInDevMode(Intent, PyttmanCliComplainerMixin): Run a Pyttman app in dev mode. This sets "DEV_MODE" to True and opens a chat interface in the terminal. """ - app_name = TextEntityField(default="", - pre_processor=lambda x: re.sub("[^a-zA-Z0-9_]", "", x)) + app_name = TextEntityField(default="") fail_gracefully = True lead = ("dev",) example = "pyttman dev " @@ -151,8 +148,7 @@ class RunAppInClientMode(Intent, PyttmanCliComplainerMixin): "settings.py under 'CLIENT'.\n" \ f"Example: {example}" - app_name = TextEntityField(default="", - pre_processor=lambda x: re.sub("[^a-zA-Z0-9_]", "", x)) + app_name = TextEntityField(default="") def respond(self, message: Message) -> Reply | ReplyStream: app_name = message.entities["app_name"] @@ -182,8 +178,7 @@ class RunFile(Intent, PyttmanCliComplainerMixin): help_string = "Run a singe file within a Pyttman app context. " \ f"Example: {example}" - app_name = TextEntityField(default="", - pre_processor=lambda x: re.sub("[^a-zA-Z0-9_]", "", x)) + app_name = TextEntityField(default="") script_file_name = TextEntityField(prefixes=(app_name,)) def respond(self, message: Message) -> Reply | ReplyStream: From d826bc6ea5beef0d166a9be0771acc0781e8c4be Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Tue, 26 Dec 2023 16:18:23 +0100 Subject: [PATCH 11/14] renamed 'pre_processor' to 'post_processor' since it offersa a way to process an entity value after initial processing --- pyttman/core/entity_parsing/fields.py | 14 +++++++------- .../test_entity_fields/test_entity_parsing.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pyttman/core/entity_parsing/fields.py b/pyttman/core/entity_parsing/fields.py index 4304187..a8ef5d7 100644 --- a/pyttman/core/entity_parsing/fields.py +++ b/pyttman/core/entity_parsing/fields.py @@ -39,7 +39,7 @@ class EntityFieldBase(EntityFieldValueParser, ABC): patterns in text. You can provide a custom Identifier class to further increase the granularity of the value you're looking for. """ - pre_processor = None + post_processor = None """ Pre-processor is a callable which is called before the value is converted to the type_cls of the EntityField. @@ -50,7 +50,7 @@ class EntityFieldBase(EntityFieldValueParser, ABC): def __init__(self, identifier: Type[Identifier] | None = None, default: Any = None, - pre_processor: callable = None, + post_processor: callable = None, **kwargs): """ :param as_list: If set to True combined with providing 'valid_strings', @@ -67,7 +67,7 @@ def __init__(self, You can read more about Identifier classes in the Pyttman documentation. """ - self.pre_processor = pre_processor + self.post_processor = post_processor if self.type_cls is None or inspect.isclass(self.type_cls) is False: raise InvalidPyttmanObjectException("All EntityField classes " "must define a 'type_cls', " @@ -113,11 +113,11 @@ def convert_value(self, value: Any) -> Any: to_type=self.type_cls) from e try: - if self.pre_processor is not None and callable(self.pre_processor): - converted_value = self.pre_processor(converted_value) + if self.post_processor is not None and callable(self.post_processor): + converted_value = self.post_processor(converted_value) except Exception as e: - value_err = ValueError("The pre_processor callable '" - f"'{self.pre_processor}' failed: {e}") + value_err = ValueError("The post_processor callable '" + f"'{self.post_processor}' failed: {e}") raise TypeConversionFailed(from_type=type(value), to_type=self.type_cls) from value_err return converted_value diff --git a/tests/core/entity_parsing/test_entity_fields/test_entity_parsing.py b/tests/core/entity_parsing/test_entity_fields/test_entity_parsing.py index 0f32ea4..acede94 100644 --- a/tests/core/entity_parsing/test_entity_fields/test_entity_parsing.py +++ b/tests/core/entity_parsing/test_entity_fields/test_entity_parsing.py @@ -422,9 +422,9 @@ class PyttmanInternalTestEntityPreProcessor( class IntentClass(ImplementedTestIntent): """ - Tests that the 'pre_processor' callable is executed and + Tests that the 'post_processor' callable is executed and can process the return value before it's spat out. """ beverage = StringEntityField(valid_strings=("tea", "coffee"), - pre_processor=lambda x: x.capitalize()) + post_processor=lambda x: x.capitalize()) From ed3be4811246b593dee96c276509cee57985aa66 Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Tue, 26 Dec 2023 16:18:40 +0100 Subject: [PATCH 12/14] version increment --- pyttman/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyttman/version.py b/pyttman/version.py index da76b1b..4b742f5 100644 --- a/pyttman/version.py +++ b/pyttman/version.py @@ -1,2 +1,2 @@ -__version__ = "1.3.1" +__version__ = "1.3.2" From 66d6f7a021b8afd66c7ff1f6e6d8104d63ecaabf Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Tue, 26 Dec 2023 19:59:38 +0100 Subject: [PATCH 13/14] changelog updated --- CHANGELOG.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f42825..fb74ab6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,66 @@ # Pyttman Changelog + V 1.3.2 + +### :star2: News* ** +* **Removed clutter from log entries** + + The log entries from Pyttman are now cleaner, without as much clutter for each log entry. +* **New argment to `EntityField` classes available: `post_processor`** + + The `post_processor` argument allows you to define a function which will be called on the value of the entity after it has been parsed. This is useful for scenarios where you want to clean up the value of the entity, or perform other operations on it before it is stored in `message.entities` in the `respond` method. + + ```python + class SomeIntent(Intent): + class EntityParser: + name = StringEntityField(post_processor=lambda x: x.strip()) + ``` +* **All `ability` classes are now available by exact name on the `app` instance** + + The `app` instance in Pyttman apps now has all `Ability` classes available by their exact name, as defined in the `settings.py` file. This is useful for scenarios where you want to access the `storage` object of an ability, or other properties of the ability. + + ```python + # ability.py + class SomeAbility(Ability): + pass + + # settings.py + ABILITIES = [ + "some_ability.SomeAbility" + ] + + # any file in the project + from pyttman import app + + ``` + + +### **🐛 Splatted bugs and corrected issues** + +* **Fixed a bug where `LOG_TO_STDOUT` didn't work, and logs were not written to STDOUT:** [#86](https://github.com/dotchetter/Pyttman/issues/86) + +# V 1.3.1 + +### :star2: News +* **New setting variable: `STATIC_FILES_DIR`** + + This new setting is set by default in all new apps, and offers a standard way to keep static files in a project. + All new apps, even ones created with older versions of Pyttman, will have the `static_files` directory + as part of the app catalog. + +* **Simplified the use of the logger in Pyttman** + The logger in pyttman offers a simple, ready-to-use logger for your app. + It offers a decorator previously as `@pyttman.logger.logged_method` which is now simplified to `@pyttman.logger`. + + +### **🐛 Splatted bugs and corrected issues** + +* **Corrected an issue when using `pyttman runfile` to execute scripts** + An issue with the relative path to the script file being exeucted has been + corrected; now, an absolute path can be provided to the script file, and + the script will be executed as expected. +** + # V 1.3.0.1 Hotfix release, addressing an issue with PyttmanCLI executing scripts, where the directory of the app is included in the path for a script From 30fd605c5550c8a24f7e9218ba3541ac76b5bba8 Mon Sep 17 00:00:00 2001 From: Simon Olofsson Date: Tue, 26 Dec 2023 20:01:43 +0100 Subject: [PATCH 14/14] synax fix in changelog --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb74ab6..0716558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,10 @@ ```python class SomeIntent(Intent): - class EntityParser: - name = StringEntityField(post_processor=lambda x: x.strip()) + """ + In this example, the name will be stripped of any leading or trailing whitespace. + """ + name = StringEntityField(default="", post_processor=lambda x: x.strip()) ``` * **All `ability` classes are now available by exact name on the `app` instance**