V 1.3.2
-
Removed clutter from log entries
The log entries from Pyttman are now cleaner, without as much clutter for each log entry.
-
New argment to
EntityFieldclasses available:post_processorThe
post_processorargument 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 inmessage.entitiesin therespondmethod.class SomeIntent(Intent): """ In this example, the name will be stripped of any leading or trailing whitespace. """ name = StringEntityField(default="", post_processor=lambda x: x.strip())
-
All
abilityclasses are now available by exact name on theappinstanceThe
appinstance in Pyttman apps now has allAbilityclasses available by their exact name, as defined in thesettings.pyfile. This is useful for scenarios where you want to access thestorageobject of an ability, or other properties of the ability.# ability.py class SomeAbility(Ability): pass # settings.py ABILITIES = [ "some_ability.SomeAbility" ] # any file in the project from pyttman import app
- Fixed a bug where
LOG_TO_STDOUTdidn't work, and logs were not written to STDOUT: #86
-
New setting variable:
STATIC_FILES_DIRThis 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_filesdirectory 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_methodwhich is now simplified to@pyttman.logger.
- Corrected an issue when using
pyttman runfileto 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. **
Hotfix release, addressing an issue with PyttmanCLI executing scripts, where the directory of the app is included in the path for a script twice.
In this release, we're introducing some cool new features along with some bug fixes. The star of the show this time is the test suite class for improved testability of Pyttman applications.
-
Test suite class for developing tests
A new Test suite class has been developed for use in Pyttman apps, for making it easier to write unit test for Pyttman applications. The test automatically loads the app context for the app in which the tests are created in.
from pyttman.testing import PyttmanTestCase class TestUserSynchronizer(PyttmanTestCase): def setUp(self) -> None: # This setup hook works as with any TestCase class. # You have access to your pyttman app just as if the app was running. app = self.app def test_some_func(self): self.fail()
-
New EntityField class for Decimal type
For finance and other domains, the floating point precision
floatisn't high enough. An industry standard is theDecimaltype and it's now supported in theEntityFieldecosystem in Pyttman.class EnterIncome(Intent): income = DecimalEntityField()
-
New mode in Pyttman CLI:
runfile- Run single scripts with PyttmanSome times a single script does the job, or the app you've developed isn't designed to be conversational. For these situations, the new PyttmanCLI command
runfileis perfect. It will invoke a Python file with the app loaded, providing you all the benefits of using Pyttman without having to develop your app with a conversational approach.# someapp/script.py from pyttman import app if __name__ == "__main__": print(f"Hello! I'm using {app.name} as context.")
Running the file above with PyttmanCLI:
$ pyttman runfile pyttman_app_name script.py > Hello! I'm using pyttman_app_name as context.
-
New mode in Pyttman CLI:
-V- See the version you're atThis mode is quite simple. It returns the version of the installed the version of the installation of Pyttman.
pyttman -V 1.3.0
- Fixes problem where words with special characters where ignored in entities
This is a hotfix release, addressing an issue with the integration with discord.py 2.0 API were Pyttman incorrectly parsed the 'channel' property for the message.
- Fixes the discord integration
This release includes an important update for the discord client. Any application using Pyttman will need to upgrade to this version for the app to still work after new year 2023 due to Discord API changes.
- Fixes #71
- Reduced dependencies to 1/3 of previous versions, improving security and reducing dependency exposure.
- Fixes various spelling errors in the code and comments
This is a hotfix release, fixing an issue with BoolEntityField instances not parsing the values correctly if the sought word is also part of the 'lead' and/or 'trail' tuples.
- Fixes #69
This is a hotfix release, fixing an issue with default values in
TextEntityFields, causing a crash if it was combined with as_list=True, and
valid_strings.
- Fixes #68
This is a hotfix release, fixing an issue with EntityFields with valid_strings
configured combined with 'suffixes' and/or 'prefixes',
where the 'prefixes' and/or 'suffixes' were ignored, if an entity matched
a string mentioned in 'valid_strings'.
This is a hotfix release, fixing an issue with EntityFields with as_list
configured, where it would append an infinite amount of matching strings,
when in fact, it should only add as many as defined in span.
- Fixes #66
This is a minor release, containing new improved features, some changes and bug fixes, where the first point on the News list is the reason for this release being a minor release.
-
Accessing the
Abilityinstance in anIntentclassin Pyttman, the relationship between an Ability and Intent classes have been parent->child, with no way to access the Ability instance from the Intent. This is now possible, which allows for accessing methods which Intents may share. To access the Ability from an Intent, you can do so with
self.abilityin all Intent methods. This is, however, not a breaking change: backwards compatibility is still supported, meaning apps withEntityParserinner classes will continue to work normally. -
BoolEntityFielddefaults to FalseInstead of defaulting to
None, theBoolEntityFieldclass now more appropriately defaults toFalseif the sought pattern isn't found in the message. -
New mode in Pyttman CLI:
shellThe
shellmode allows you to open your Pyttman app bootstrapped with dependencies and environment loaded up, in an interactive shell. This is useful where you want to try your classes using an interactive shell. This feature is invoked usingpyttman shell <app name>.
-
Removed pytz dependency from the library
-
Internal refactoring and code cleanup
-
The inner class "EntityParser" is no longer used and is unsupported.
The inner class
EntityParserinsideIntentclasses was optional, and added EntityParser functionality to an intent class. This class proved to be redundant however, as the EntityParser API continues to evolve. The need for an inner class is thus removed, andEntityFieldclasses are instead directly declared inside theIntentclass as class fields, more resembling other declarative API:s.# Old class SomeIntent(Intent): class EntityParser: name = StringEntityField() # New class SomeIntent(Intent): name = StringEntityField()
- Fixes #64
2022-04-10
This mini-release contains small changes and bug fixes.
-
Improved behavior control when using the EntityParser Api
Added the option for developers to state explicitly whether they want to ignore or keep any strings in 'lead' and/or 'trail' from the Intent, as possible strings in the EntityParser.
-
Internal cleaning and refactoring of our Parser code.
Removed redundant class
AbstractParserBase, making theParserclass the base parser class in Pyttman. -
BoolEntityField now defaults to
FalseThe
BoolEntityFieldclass defaults toFalseif it can't find the sought value in a message, in comparison to the previous defaultNoneas default value.
- Fixes #63
2022-04-7
This release improves the EntityParser API, packs the new create ability command for the pyttman cli tool, and introduces a
few new other features and bugfixes.
-
New EntityField classes
-
The
TextEntityFieldcan now also be used asStringEntityFieldandStrEntityField -
The
IntegerEntityFieldcan now also be used asIntEntityField
-
-
New command for
pyttmancli:create abilityYou can now create new ability modules with the Pyttman cli tool
pyttmanfrom your terminal shell. A new module is created withability.py,intents.pyand__init__.pyfile inside. Our ambition is that this further improves the simplicity of developing apps with Pyttman, by streamlining the way applications grow. -
pyttman.appIn Pyttman, you now have access to your application represented as the
appobject, which you can import from pyttman as:from pyttman import appin any file inside your project. On this object you have access tosettings,abilitiesandhooks(mentioned further down) - which empowers you to inspect and modify the state of your app down the road. -
Project templates
The project template, used when creating new projects, is no longer shipped with the project through PyPi, but rather downloaded from an official GitHub repository, lowering the payload when installing the package and ensures distributions always use the latest templates.
-
Lifecycle Hooks
Lifecycle hooks allows you as a developer to have code executed in certain timepoints in the lifecycle of your application.
-
Ability lifecycle hook: 'before_create' allows you to execute code before a certain Ability is loaded. This is useful to pre-populate the
storageobject asself.storage['foo'] = 'bar'before the ability is loaded. -
applifecycle hooks allows you to import the app you've developed as:from pyttman import app, and then decorating any function in the application as@app.hooks.run('before_start')allows you to run a function before the entire app starts. This is useful for connecting to databases or performing other tasks necessary to the application.You can only decorate functions as app-lifecycle hooks from a special module in your app:
app.py. This module is automatically imported at the start of the runtime, by Pyttman, if present in the app root directory.
-
-
Introducing support for params in
EntityFieldclasses to be callableA select set of arguments provided to EntityField classes such as
StringEntityFieldand others, can now be callables. This allows you as a developer to have rules for EntityField classes evaluated at runtime, and not when the app starts. This is useful for scenarios where you'd want data from a dynamic source to control the behavior of an EntityField, say the available users in your app.Example:
username = StringEntityField(valid_strings=get_enrolled_usernames)
Note! This is a breaking change.
-
Further expansion of the Pyttman Middleware epic
In applications, the
settings.pysetting calledMESSAGE_ROUTERchanges name toMIDDLEWARE. This is a part of the movement toward supporting more flexible and powerful plugins in Pyttman, where the MessageRouter belongs as a part of this Midde-ware ecosystem. In new apps, this setting has the updated name automatically. In apps from previous versions of Pyttman, you must change this name insettings.py. -
Refactored the
Message,ReplyandReplyStreamclasses import pathThis update moves the above mentioned classes. Change imports
from:
from pyttman.core.communication.models.containers import Reply, ReplyStream, Messageto:
from pyttman.core.containers import Reply, ReplyStream, Message
2022-02-28
This release further improves the new Plug-And-Play EntityField classes used in the EntityParser API in Pyttman; further empowering developers to quickly find words of interest in Messages from users, using the declarative EntityParser API.
-
Major refactoring of internal API algorithms to improve speed and functionality of the EntityParser API.
-
New EntityField:
BoolEntityFieldThe
BoolEntityFieldprovides a boolean entity on whether a pattern was matched in an incoming message, or not. This is useful for scenarios when the word itself in the message is not of interest, but only the fact if it occurred in the message at all.# message.entities["answer_in_spanish"] will be True or False # depending on whether the message contains the word 'spanish' class EntityParser: answer_in_spanish = BoolEntityField(message_contains=("spanish",))
-
Added 2 new Identifier classes
FloatIdentifierandNumberIdentifierare both Identifier classes to find numbers, and are direct references toIntegerIdentifierbut offers more naming options.
-
New settings.py option The option to log to STDOUT in addition to the default log file has been added. To use it, define
LOG_TO_STDOUT = Truein the Pyttman appsettings.pyfile. -
Introducing Pyttman Middleware
The Pyttman framework is growing. To make it easier to extend functionality for developers using Pyttman, we have abstracted the layer for message routing to a new layer; the Middleware layer.
This allows for future extension of the Middleware layer, adding options for message filtering and reply relaying through event schedulers and much more. As of this release, nothing has really changed in the usagae experience, except for the deprecation warning on importing the
FirstMatchingRouterthrough the legacy import path, since it was moved. No direct action is required for apps upgrading from older versions.
-
Fixes #57 - Logs don't show in Heroku
This issue traces back to the way certain platform log the STDOUT and STDERR streams by default. We added a new optional setting which allows apps to also log to STDOUT:
LOG_TO_STDOUT = Trueinsettings.pyin a Pyttman app will resolve this issue.
-
Pyttman 1.1.10 requires Python 3.10 at minimum.
Changes to the type hinting in several places in the source code means that Python versions below 3.10 are not supported.
Note! This is a breaking change.
-
The usage of
Parserclasses inEntityParserclasses inIntentclasses have been deprecated and are no longer supported. Refer to theEntityFieldtypes for direct replacements.EntityFieldclasses implement the same interface as theParserclasses.Note! This is a breaking change.
-
Identifierclasses are moved frompyttman.core.parsing.identifierstopyttman.core.entity_parsing.identifiersmodule.Note! This is a breaking change.
-
The
ChoiceParserclass is deleted, in favor of usingTextEntityFieldwithvalid_stringsas the subset of available options.Note! This is a breaking change.
-
Reverts the change made in
1.1.9with Entities:message.entitiesis now back to being a dict of direct entity values, notEntityclasses. The reason for this revert was the repetitive pattern of using the.valueproperty in apps on properties, and not doing so would cause exceptions.Note! This is a breaking change.
-
The Middleware API in Pyttman results in a move of the
FirstMatchingRouterclass, commonly referenced in Pyttman apps insettings.pyunderROUTER_CLASS. The class is now available underpyttman.core.middleware.routing.FirstMatchingRouterbut is still available for import at the older import path,pyttman.core.parsing.routing.FirstMatchingRouterfor the time being, which results in a deprecation warning to STDOUT.
2021-12-24
This release is a hotfix release of a critical bug in the Pyttman cli, rendering apps unable to start in client mode, rendering them unusable.
- Fixes #55 - pyttmancli runclient not working
2021-12-11
This release includes bug fixes but also some new cool features.
- Entities are now accessed on the
Messageobject in Intents instead of being accessed on the Intent itself. Accessing Entities on the Intent is supported until 1.2.0 and will raise a deprecation warning. - EntityField classes provide an easier and more efficient way to find values of interest in messages from users.
- Fixes #47 - Strings with different case from otherwise identical values in lead/trail/exclude are not truncated
- Fixes #48 - ChoiceParser can't parse choices when capitalized
- Fixes an issue with type hinting referring to the
MessageMixininIntent.respond()implementation -> Corrected to now hintingMessage.
- Entities are no longer
str, butEntityinstances. To fetch the value of the entity itself as previously done byname = self.entities.get ("name")is now instead done asname = self.entities.get("name").value
This release includes bug fixes and internal improvements mainly.
Although the points listed below may seem minor, we've rewired and tested this release probably better than any other up to this point :happy:
-
New setting in
settings.pysettings.pyin Pyttman apps now have theDEV_MODEflag for users to toggle.Note! When you run an app in dev mode using
pyttman dev <app_name>, it is automatically set toTrueregardless ofsettings.py.Example:
# In settings.py DEV_MODE = True # Somewhere in the app logic if pyttman.settings.DEV_MODE is True: print("Some debug statement")
-
The
BaseClientclass is moved in Pyttman, which changes the import path for the class:pyttman.clients.builtin.base.BaseClientbecomespyttman.clients.base.BaseClient. -
Vast improvements to the Pyttman CLI tool
The administrative CLI tool
pyttmanfor creating, bootstrapping and debugging Pyttman apps has been rewritten using the Pyttman framework itself to build Intents, read from the terminal shell.
- Fixes #35 with internal improvements to the EntityParser algorithm in how it considers the resolution order of how entity strings are parsed, identified and later stored in
self.entitiesinIntentclasses. - Fixes #40 -
pyttman dev <app name>now works without providing a Client class.
This release is a hotfix release, adressing issues using the runclients command with pyttman cli tool on linux and unix based systems.
-
Clients are no longer started in parallel using Threading due to issues with security and runtime on unix and linux based systems. Observe that in your
settings.pyfile, theCLIENTSfield is replaced by aCLIENTfield, which is a single dictionary containing the client configuration for your app. This was necessary for multiple reasons, one being the complexity of pickling application logic to run them in parallel using a process pool instead of threading, to solve bug #33. We're sorry about the inconvenience this may cause for your development and the experience with Pyttman so far. We're still learning. It seems that this approach works well with deploying apps using Docker, as you can create containers using different settings for various platforms and support multiple platforms in this manner.Note! This is a breaking change.
-
The Pyttman CLI "
pyttman" argument for running client has changed fromrunclientsto justrunclient, indicating asingleclient configuration in settings.pyNote! This is a breaking change.
-
Fixes an issue, causing the
runclientsargument not to start apps as intended on linux and unix based operating systems. -
Improves how settings are loaded, using the new Setting class. You still access your settings defined in
settings.pyusingpyttman.settingsin your app.
-
The
Featureclass is renamed toAbilityfor better semantic similarity to the general standard of terminology.Note! This is a breaking change.
-
The
Commandclass is renamed toIntentfor better semantic similarity to the general standard of terminology.Note! This is a breaking change.
-
pyttman-cliis renamed to justpyttmanfor increased simplicity.Note! This is a breaking change.
-
The reference to
FeatureinIntentclasses (previouslyCommandclasses) - is removed. this means that theStorageobject previously accessed throughself.feature.storagecan no longer be accessed this way. Instead, theAbilityis no longer referenced insideIntentclasses for cleaner OOP relations. However, theStorageobject is still available inIntentclasses, of course. It is accessed usingself.storageboth in theAbilityand inIntentclasses.Note! This is a breaking change.
-
The NLU component
EntityParserclass ofIntentclasses has been improved, and no longer identifies one entity more than once. It is also a lot smarter in how it traveres the message in order to find the data of interest. -
The
EntityParserclass must no longer inherit fromEntityParserBaseorIntent.EntityParser, metaclassing is internally handeled. -
The
CommandProcessorclass which was deprecated in version 1.1.4, is removed. -
The
Callbackclass which was deprecated in 1.1.4, is removed. -
The
Interpretationclass which was deprecated in 1.1.4, is removed. -
Methods associated with legacy classes from the
IntentandAbilityclasses internally, have been removed -
The new
ReplyStreamQueue-like object offers you the ability to return multiple response messages in a single object from Intents. TheReplyStreamwill wrap your strings or other objects asReplyobjects if compatible, and the client will post each of these elements as separate messages in the client. -
The
pyttman.schedule.methodapi method no longer requires the use of theasync_loopargument if the function to be scheduled is asynchronous, but rather acquires the running loop throughasyncio.get_running_loop(). If no running loop is identified, it will automatically run the asynchronous function usingasyncio.run. -
Identifier class
DateTimeStringIdentifierhas added regex patterns to also identify strings with a date stamp, without a specific time. For example, in a message like:On that fateful night of 1986/04/26 (...)- theDateTimeStringIdentifierwould now find1986/04/26as a valid entity.
-
Fixes an issue where line separations in
Replyobjects were not present when the data was displayed in applications such as DIscord or the Cli client terminal shell. These are now present. -
Fixes an issue where clients could not communicate any errors upon startup. These are now showed through user warnings.
-
Fixes an issue where one element in a message would end up multiple times in
self.entitiesincorrectly -
Fixes an issue where strings defined in
leadandtrailinIntentclasses were case-sensitive - they are not anymore. -
Fixes an issue where an entity parsed using a
ChoiceParserwould be stored as the casefolded variant. With this correction, identification is done case-insensitively, and the defined value in theChoiceParser.choicesis the one present inself.entites, when a match occurs. -
Fixes an issue with the
CapitalizedIdentifieridentifier class, as it would not grant all-caps words as valid.
This feature is one of the flagship-features of this release.
A new interface class, BaseClient dictates how Pyttman expects a minimally developed client to behave.
This allows us to subclass platform clients from SDK's and libraries from plattforms, and using the BaseClient as a mixin, creating the powerful combination of a native client to be used with Pyttman.
The native and community Client support in Pyttman enables you to launch your app to the Discord plattform without a single line of code.
By simply providing which clients you want to use in the settings.py file ( using Pyttman-included Clients, or a client you wrote yourself ) - your app will be running on all clients in parallel by starting your app with pyttman-cli runclients.
The Pyttman MessageRouter will keep your clients separate, so there's no risk of a Reply ending up in the wrong plattform.
Many more clients are on the way, so stay tuned for more plattform clients to be supported natively.
Note
Some platforms offer different methods than others; if you mix plattform clients in your app, it's a good idea to check which client is associated with your message, if you're accessing members of that client.
Example
# The following CLIENTS list will start your app using the Discord client
# making your bot go online, with your Pyttman app powering it's backend.
# To use more clients, simply append more config dict's like this one, and
# have your app hosted on these platforms in parallel.
CLIENTS = [
{
"module": "pyttman.clients.community.discord.DiscordClient",
"token": "foo-token-from-discord-developer-portal",
"guild": "bar-guild-id-from-your-discord-server"
}
]-
Parallel runtime for all clients
Develop your app once - and have it online on multiple platforms in an instant. Add the clients you want to use to the
CLIENTSlist insettings.py. That's it!The next time you run
pyttman-cli runclients, the clients start up in parallell and inside your app, you will see which client is sending the message by the Message propertyclient.Example
# inside a Command.respond method: if isinstance(message.client, DiscordClient): print(message.client.users) elif isinstance(message.client.CliClient): message.client.publish("I can publish this directly to my CliClient for testing!")
Hint!
If you're a power user and want to use the hooks defined in
discord.Client, simply subclassDiscordClientto have the Pyttman-defined 'on_message' hook method already taken care of (this is where the integration takes place between your Pyttman app and Discord) and define any behavior in the other hooks as you please. Use your custom class instead of the included one, in the example above.
This feature is one of the flagship-features of this release.
The EntityParser is a powerful tool for developers looking to extract information from natural language.
Odds are you're developing a chatbot using Pyttman. Chatbots usually have a job to do, and more than often, we as devs, are looking for data in the message from a user.
A message from a user may look something like this:
Can you play Rocket Man by Elton John on Spotify please?
In this example, you may have a Command which job it is to play songs for users on their Spotify accounts.
Now, your app may host support for more platforms than just one. Say you also support SoundCloud, or YouTube Music. You'd want to identify which platform the user wanted to use.
You're also looking for artist and/or song in this message.
The EntityParser class defined inside your Command will take care of this for you. It enables you to quickly and without a single iteration or if-statement, find the values you're looking for by defining a set of rules to wich degree is entirely up to you - loosely or constricted.
Example
class PlayMusic(Command):
lead = ("play",)
"""
Define the EntityParser inside the Command class and
create fields, named as you want them to be named when
accessing them through self.entities.get().
"""
class EntityParser(Command.EntityParser):
song = ValueParser(identifier=CapitalizedIdentifier, prefixes=("play",), span=10)
artist = ValueParser(identifier=CapitalizedIdentifier, prefixes=("by",), span=2)
platform = ChoiceParser(choices=("Spotify", "SoundCloud", "YouTubeMusic"))
def respond(self, message: MessageMixin) -> Reply:
print("My entities:", self.entities)
return Reply(self.entities)
class MusicFeature(Ability):
commands = (PlayMusic,)
Writing the message to the following example command returns:
{'artist': 'Elton John', 'platform': 'spotify', 'song': 'Rocket Man'}This is a short example of how powerful the EntityApi is, and what you can do with it.
In short - it enables you to develop Commands and Features which extract information from messages, without looping manually, looking for data.
-
Ability-level implicit encapsulation, dict-like storage in all
Commandclasses.The Storage API offers a
Storageobject accessible in allCommandsubclasses by accessing theself.feature.storageproperty. Your other Commands which are defined in the sameAbilitywill access the same storage object which allows for an easy and safe way to store and share data between commands.Example
# Put data class FooCommand(Command): def respond(self, Message): # self.feature.storage["foo"] = "bar" works as well self.feature.storage.put("foo", "bar") # Get data class BarCommand(Command): def respond(self, Message): foo = self.feature.storage.get("foo") print(foo) class FooBarFeature(Ability): commands = (FooCommand, BarCommand)
Outputs:
>>> "bar"The Storage object is encapsulated by the scope of a
Abilityin which the command is listed in. This means thatCommandclasses operate on the sameStorageobject as the otherCommandclasses in the sameAbility, but commands outside of the Ability cannot interfere with the data in that storage object.Note
If you don't use the Storage API but try to use instance variables in your commands, you will eventually learn that they don't stick. This is because of how Pyttman preserves memory in deleting Command instances once the Reply is generated.
-
Improved versatility with configuration for default replies vastly improved and other similar settings
-
AutoHelp, creating automatically generated help snippets for your Commands by their configuration
-
Improved error handling
-
If exceptions occur in the application, the user will in 99% of the times receive a mesage letting them know something went wrong instead of the app going silent. The app is much less likely to crash, and a UUID is stored in the log file along with a stderr print out of the error. The user also gets to see thsi UUID for relaying to a developer if needed.
MessageRouters improved, now instantiating Command classes each time to prevent memory leaks in local preferences in Command objects outside of the Storage API.
This release includes discord.py, and it's license is mentioned in the README.MD and LICENSE of the Pyttman project, here