{"id":34063342,"url":"https://github.com/juanclopgar97/python_sharp","last_synced_at":"2026-04-09T00:32:33.083Z","repository":{"id":279753696,"uuid":"939836094","full_name":"juanclopgar97/python_sharp","owner":"juanclopgar97","description":"python# (python sharp) is a module created to add EOP (event oriented programing) into python in the most native feeling, easy sintax way possible. Based on C# event implementation structure","archived":false,"fork":false,"pushed_at":"2025-04-03T04:51:00.000Z","size":529,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-16T03:16:46.108Z","etag":null,"topics":["event-driven","event-oriented-programming","events","functionalities","native","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juanclopgar97.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-27T07:27:43.000Z","updated_at":"2025-04-03T04:49:13.000Z","dependencies_parsed_at":"2025-02-27T10:36:54.437Z","dependency_job_id":"11cadbf4-0b47-4497-88b2-6ab865ca4b6a","html_url":"https://github.com/juanclopgar97/python_sharp","commit_stats":null,"previous_names":["juanclopgar97/python_sharp"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/juanclopgar97/python_sharp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanclopgar97%2Fpython_sharp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanclopgar97%2Fpython_sharp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanclopgar97%2Fpython_sharp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanclopgar97%2Fpython_sharp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juanclopgar97","download_url":"https://codeload.github.com/juanclopgar97/python_sharp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanclopgar97%2Fpython_sharp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31579970,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"ssl_error","status_checked_at":"2026-04-08T14:31:17.202Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["event-driven","event-oriented-programming","events","functionalities","native","python"],"created_at":"2025-12-14T05:19:40.509Z","updated_at":"2026-04-09T00:32:33.071Z","avatar_url":"https://github.com/juanclopgar97.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Python Sharp Logo](https://raw.githubusercontent.com/juanclopgar97/python_sharp/refs/heads/master/documentation_images/python_sharp.png)\n\n```Python\n    def project_finished(sender:object,e:EventArgs)-\u003eNone:\n      print(\"Development is fun!!!\")\n\n    project = Project() \n    project.finished += project_finished\n    project.finish()\n\n```\n```\n\"Development is fun!!!\"\n```\n\n# Python# (Python sharp)\n\n## Table of Contents\n1. [Introduction](#Introduction)\n2. [Installation](#Installation)\n3. [Tools and support](#tools-and-support)\n4. [Important disclaimer](#important-disclaimer)\n5. [Use cases and examples](#Use-cases-and-examples)\n    1. [Delegates](#Delegates)\n        1. [How to add callables into a Delegate](#How-to-add-callables-into-a-Delegate)\n        2. [How to get returned values of callables out of a Delegate](#How-to-get-returned-values-of-callables-out-of-a-Delegate)\n        3. [Delegates Summary](#Delegates-Summary)\n    2. [Events](#Events)\n        1. [EventArgs, CustomEventArgs and CancellableEventArgs class](#eventargs-customeventargs-and-cancellableeventargs-class)\n        2. [Implementation](#Implementation)\n            1. [Simple events](#Simple-events)\n            2. [Events with arguments](#Events-with-arguments)\n            3. [Events with modifiable arguments](#Events-with-modifiable-arguments)\n            4. [Implementation summary](#implementation-summary)\n    3. [Static events](#Static-events)\n\n## Introduction\n\nPython# (Python sharp) module was created with the intention of adding EOP (event oriented programming) into Python in the most native feeling, easy syntax way possible.\n\nEOP is a programming paradigm that allows execute actions (code) based on \"occurrences\" or events, this is really useful when you have to execute specific actions when something happens but you do not have the certainty when or how many times is going to occur.\n\nThis module was designed to accomplish EOP with 2 objectives in mind:\n\n1. Features should look and feel like native Python features.\n2. Implementation should be based in another famous EOP language to reduce the learning curve and improve user experience.\n\nEvents are just another possible way to declare a class member like: fields/attributes, properties and methods, Python already have a way to define a property with **@property**, this helps to define objective number 1, for this reason events are implemented with **@event** syntax to be consistent with Python:\n\n```Python #5\nclass Person:\n  def __init__(self,name:str)-\u003eNone:\n    self._name = name\n\n  @property\n  def name(self)-\u003estr: \n        return self._name\n\n  @name.setter \n  def name(self,value:str)-\u003eNone:\n        self._name = value\n\n  @event\n  def name_changed(self,value)-\u003eNone:\n    #some implementation\n    pass\n```\n\nFor objective 2, the module was architected thinking in how another EOP language (in this case C#) implements its events. This implementation will be explained below, keep in mind this is a really simplified explanation of how C# events actually work, if you are interested in learn how they work exactly please go to C# documentation. With this clarified, let's move on to the explanation: \n\n1. C# implements events as a collection of callbacks that will be executed in some point of time, this collection of functions are called **Delegates**, invoking(executing) the delegate will cause the execution of all functions(callables) in its collection.\n\n2. delegates are not commonly exposed publicly, for security reasons. Just as fields/attributes in a class have to be encapsulated, so do delegates. The way to encapsulate them is with events. Fields/attributes are to properties as delegates are to events.\n\n3. Properties encapsulate fields/attributes using two methods: \"getter\" and \"setter\", which define the logic of how data should be GET and SET out of the object, in C# events encapsulate delegates with 2 methods as well called \"adder\" and \"remover\", which define the logic of how functions/subscribers should be added or removed out of the delegate.\n\n\n## Installation\n\n### Requirements\n\n- **Python**: Version 3.6 or higher\n- **pip**: Python package manager\n\nTo install `python_sharp` you can follow either of the options listed:\n\n### Disclaimer\n\nversion 1.0.0 is only available through GitHub PyPI does not contain that version.\n\n### 1. Clone the Repository \nIf you want to explore the source code, you can clone the repository:\n```bash\ngit clone https://github.com/juanclopgar97/python_sharp.git\ncd python_sharp\n```\n\n### 2. Install the package directly from GitHub using pip:\n\n```bash\npip install git+https://github.com/juanclopgar97/python_sharp.git\n```\n\nfrom a specific branch/commit/version:\n\n```bash\npip install git+https://github.com/juanclopgar97/python_sharp.git@\u003cbranch_or_commit_or_version\u003e\n```\n\nExample:\n\n```bash\npip install git+https://github.com/juanclopgar97/python_sharp.git@v1.0.0\n```\n\n### 3. Install from PyPI\n\n```bash\npip install python_sharp\n```\nor select your version\n\n```bash\npip install python_sharp==\u003cversion\u003e\n```\n\nExample:\n\n```bash\npip install python_sharp==1.0.1\n```\nUpgrade it:\n\n```bash\npip install python_sharp --upgrade\n```\n\n\n### Usage\n\n```Python\nfrom python_sharp import *\n\n#your code\n```\n\n## Tools and support\n\nCurrently there is an upcoming effort to create a VS code extension to deliver a better experience while using Python sharp, an example of this is a custom OUTLINE to visualize *@property* and *@event* with its corresponding icons as the next image shows:\n\n![outline support](https://raw.githubusercontent.com/juanclopgar97/python_sharp/refs/heads/master/documentation_images/outline_support.png)\n\nand so much more!, if you want to see it come true you can show interest using and spreading the use of Python sharp, this will help to add more support to the project.\n\nTo create an enhancement request, report a bug, raise a question etc. you can use the [issues](https://github.com/juanclopgar97/python_sharp/issues) section of this repository with the corresponding labels **enhancement**, **bug**, **question** etc. in this way collaborators can check for the request and attend it.\n\n## Important disclaimer\n\nIn some parts of this documentation you will find the words \"**HIGHLY RECOMMENDED**\", these words are used to highlight some important use aspects of Python sharp.\n\nThe omission of \"**HIGHLY RECOMMENDED**\" conventions (like naming conventions, or implementation conventions) might not break your code and it might work without them, even you might found a way to create your own way to implement them, HOWEVER, this could lead to readability, clarity, maintenance and scalability issues of the code. \n\nFor this reason, always **FOLLOW THE CONVENTIONS** since this is necessary to keep the readability, clarity, maintenance and scalability of your code, in this way other collaborators can go over your code easily and improve the work flow.\n\n\n## Use cases and examples:\n\nIn this repository there are 2 main files \"python_sharp.py\" (which is the module file) and \"test.py\". This last file contains all the features applied into one single script, this can be really useful if you want to do a quick check about how something is implemented, however, since it is a \"testing\" script and not a \"walk through\" it could be confusing if you do not know what is going on, so it is **Highly recommended** read the documentation below which explains step by step how to implement every single feature in the module.\n\n### Delegates\n\nPython sharp Delegates are a list of callables with the same signature, when a delegate is being executed (delegates are callable objects), it executes every single callable in its list.\n\n#### How to add callables into a Delegate\nIt is really important to keep the callables added into the delegate with consistent signatures because parameters passed to the delegate when is being executed are the same ones passed to every single callable in the collection, so if one callable signature is expecting only 2 parameters and the next callable 3 parameters this is going to cause a TypeError that might look like this: \n\n```Python\nfrom python_sharp import *\n\ndef function_1(parameter_1:int): # defining a function with 1 parameter (int type)\n  print(\"function 1\")\n\ndef function_2(parameter_1:int,parameter_2:str): # defining a function with 2 parameters (int,str types)\n  print(\"function 2\")\n\ndelegate = Delegate() #creating a Delegate\ndelegate += function_1 #adding function_1\ndelegate += function_2 #adding function_2\n\ndelegate(5) # executing the delegate with only 1 parameter\n\n```\n\nOUTPUT:\n```\nfunction 1\nTraceback (most recent call last):\n  File \"c:\\PATH\\test.py\", line 341, in \u003cmodule\u003e\n    delegate(5) # executing the delegate with only 1 parameter\n    ^^^^^^^^^^^\n  File \"c:\\PATH\\python_sharp.py\", line 72, in __call__\n    results.append(callable( *args, **kwds))\n                   ^^^^^^^^^^^^^^^^^^^^^^^^\nTypeError: function_2() missing 1 required positional argument: 'parameter_2'\n```\n\nHere *function_1* was executed correctly due the signature of the function match with how the delegate was executed (passing only one integer \"5\"), and *function_2* was expecting a second string parameter resulting in a TypeError. So, it is really important keep signatures in a homogeneous manner.\n\n#### How to get returned values of callables out of a Delegate\n\nOnce the delegate is executed you can get the returned values (if Any) as a tuple returned by the delegate, this tuple represents the values returned by every callable in the delegate's callable collection:\n\n```Python\nfrom python_sharp import *\n\ndef function(text:str):\n  print(\"%s, Function is being executed!\" % text)\n  return \"function result\"\n\nclass Test:\n  def method(self,text:str):\n    print(\"%s, Method is being executed!\" % text)\n    return \"method result\"\n\ntest_instance = Test()\n\nfirst_delegate = Delegate(function) #adding a function. You can pass the first callble optionally through the constructor\n\ndelegate = Delegate() # creates an empty delegate\ndelegate += first_delegate #adding a delegate. You can add a delegate to another delegate due a Delegate is callable\ndelegate += test_instance.method #adding a method\n\nresults = delegate(\"Hello!\")\n\nprint(f\"returned values: {results}\")\n```\n\nOUTPUT:\n```\nHello!, Function is being executed!\nHello!, Method is being executed!\nreturned values: (('function result'), 'method result')\n```\nIn this example we can see that *delegate* executes its first item added which is *first_delegate*, as result 'function' is executed and *first_delegate* return a tuple with the return value of 'function', this tuple is added into *delegate* results, then *delegate* executes its next item *test_instance.method* as result it returns a string that is going to be added into the *delegate* results.\n\nAt the end we finish with all callables executed and the results: \n  - ('function result'): result of *first_delegate* execution\n  - 'method result': result of *test_instance.method* execution.\n\n#### Delegates Summary\n\nAs summary, Delegates are really useful to execute a bulk of callables, and its return values (if any) are returned by the delegate in a tuple.\n\n### Events\n\nIn programming, an event refers to an action or occurrence that a program can detect and respond to. Events can be triggered by user interactions (like clicking a button, typing text, or moving a mouse), system-generated activities (like a file being updated or a timer expiring), or even messages from other parts of the program. Typically, an event is associated with subscribers (event listeners) which are functions or blocks of code designed to execute when the specific event occurs.\n\nEvents are commonly part of an event-driven programming paradigm, where the flow of the program is determined by these events.\n\nEvents can be implemented as members of an instance or a class (static events) on different ways, in this module we can group this \"ways\" into 3 main implementations:\n\n1. **Simple events** (Normally implemented as *property changed* events):\n  These events only \"notify\" that something relevant happens, they do not provide extra information about the event like: How, When, Why etc.\n  \n  Name convention for this events is: [optional subject or adjective] +  VERB + 'ed' (past simple).\n\n  Examples:\n\n  - name_changed (this is an example of property change implementation in this case for *name* property)\n  - location_changed (this is an example of property change implementation in this case for *location* property)\n  - moved\n  - executed\n\n2. **Events with arguments**:\n  This events are like *simple events* but they are capable of provide extra information about the event like: How, When, Why etc, to the subscribers through a parameter. They follow the same name convention as *simple events*.\n\n3. **Events with modifiable arguments** (Normally implemented as *pre-events*)\n\n  *Events with modifiable arguments* are most likely implemented as **pre-events** this means the event advertise something that is about to happen, and it will let the subscribers provide information to determine the future, like cancelling what was about to happen or modify how it was going to be done.\n\n  Name convention for this events is: [optional subject or adjective] + VERB + 'ing' (present continuous).\n\n  Examples:\n\n  - name_changing \n  - house_changing \n  - moving\n  - executing\n\n  - Another example to clarify this could be an event called \"window_closing\", this event will notify that a window is about to close, the subscribers will have the power to pass information through the event arguments to cancel or modify the incoming action (in this case the window closing), this is really useful if the changes in the app are not saved.\n\n\n  In some rare occasions these name conventions will not satisfy your necessities to name your events due they don't describe properly what your events are going to do, and it is fine to name them as you want, however, it is **HIGHLY RECOMMENDED** use these name conventions as much as possible, since this would lead to better readability, clarity, maintenance and scalability of the code, so, if you can name your events under the suggested name conventions **DO IT**, only use your own naming conventions if the naming conventions described in this document do not fit with what your event is going to do. \n\n\n#### EventArgs, CustomEventArgs and CancellableEventArgs class\n\n*EventArgs* class is an empty class designed to be a base class to pass the event arguments, these arguments are going to be passed from the publisher to the subscriber in order to provide more information about what happens.\n\n-  **Simple events** use *EventArgs* objects to pass the event arguments to the subscriber, due *EventArgs* is an empty class, no arguments are passed to the subscriber, this is the reason why these events are the simplest to implement and the ones used for *property changed* events, they only notify something happens and that's it, no more information. Worth mentioning *property changed* events are not the only use for these event types, it is just a use case example\n\n-  **Events with arguments** use a custom class that inherit from *EventArgs* class to describe what arguments are going to be passed to the subscriber. The arguments passed to the subscriber are passed as read_only properties (properties with only getter). If a **simple event** is not enough, you might need an **Event with arguments**, in this case, you can use a custom EventArgs that contains your arguments.\n\n    As a use case example imagine an event called *moved*, this event notifies when the object moves, but maybe only notify the movement is not enough and we want to inform how much the object moves, this is a perfect use for our custom *EventArgs* class:\n\n\n    ```Python\n    class MovedEventArgs(EventArgs): # example of Custom EventArgs to pass event information (distance moved in this case)\n\n        _delta:int\n\n        def __init__(self,delta:int)-\u003eNone: # Request the distance of the movement\n            super().__init__()\n            self._delta = delta # Save the distance\n\n        @property\n        def delta(self)-\u003eint: #encapsulate the value and placing its getter\n            return self._delta\n    ```\n\n- **Events with modifiable arguments** use a custom class that inherit from *EventArgs* class to describe what arguments are going to be passed from the subscriber to the publisher, this module already include one example of this approach *CancellableEventargs*:\n\n    ```Python\n    \n    class CancellableEventArgs(EventArgs):\n    \n        _cancel:bool\n    \n        def __init__(self)-\u003eNone:\n            super().__init__()\n            self._cancel = False \n    \n        \n        @property\n        def cancel(self)-\u003ebool: #to show the value of _cancel attribute\n            return self._cancel\n        \n        @cancel.setter\n        def cancel(self,value:bool)-\u003eNone: #to let the subscriber set a value into _cancel\n            self._cancel = value\n    ```\n\n    as you can see, this implementation is really similar to **Events with arguments**, the only difference is we are placing a setter method to let modify the cancel value, this value can be used for the publisher at the end of the execution of all the callbacks stored.\n\n    It is **REALLY IMPORTANT** to remark, CancellableEventArgs is only an example of an *EventArgs* used for **Events with modifiable arguments** and is not the only way to implement it, you don't even need to inherit necessarily from it. In order to consider an *EventArgs* used for **Events with modifiable arguments** it has to implement a setter on it, in this way this new *EventArgs* can provide and store information about the event, and this information can be used by the publisher and subscribers. Another way to see it is as a bidirectional channel to communicate the publisher and subscribers, publisher can provide information with the getters and subscribers can store information in it with the setters.\n\n\n#### Implementation\n\nBelow this text, the use cases and explanation about the events are shown, please read the examples and after READ THE EXPLANATION OF THE EXAMPLE CODE, this is really important because it specifies step by step the \"WHY\"s of the implementation.\n\n##### Simple events\n\n  ```Python\n  from python_sharp import *\n\n  class Person: \n    \n    def __init__(self, name:str)-\u003eNone: \n      self._name = name \n      self._name_changed = Delegate() #collection of future callbacks\n\n    @property \n    def name(self)-\u003estr:\n      return self._name\n\n    @name.setter \n    def name(self,value:str)-\u003eNone:\n      self._name = value\n      self._on_name_changed(EventArgs()) \n\n    def _on_name_changed(self,e:EventArgs)-\u003eNone:\n      self._name_changed(self,e) \n\n    @event \n    def name_changed(self,value)-\u003eNone: # This example doesn't contain the parameter annotations for simplicity because it is the first example, however (as the document will explain in the summary of simple events), it is really important to place the event annotations. (There is a link at the end of this code block to go to the explanation)\n      self._name_changed += value\n\n    @name_changed.remover\n    def name_changed(self,value)-\u003eNone: # Annotations are not included for simplicity because it is the first example\n      self._name_changed -= value \n\n\n  def person_name_changed(sender:object,e:EventArgs)-\u003eNone:\n    print(\"person change its name to %s\" % sender.name)\n\n  person = Person(\"Juan\")\n  person.name_changed += person_name_changed \n  person.name = \"Carlos\" \n  person.name_changed -= person_name_changed \n  person.name = \"Something\" \n  ```\n\n  OUTPUT\n  ```\n  person change its name to Carlos\n  ```\n\n[Link to event annotation convention explanation](#event-annotation-convention)\n\n*\"simple events\"* \"notify\" that something relevant happens, they do not provide extra information about the event like why, when, where, etc.\n\nOn this example an event *name_changed* is implemented to notify when the person's name change.\n\nTo implement a *simple event* the first thing you have to do is create a variable to store the subscribers, look at this variable as a \"To do list\" because it contains the callables that are going to be executed at some specific time.\n\n```Python\nself._name_changed = Delegate() # it can be viewed as a \"To do list\"\n```\n\nAs you might notice the variable that is going to store the subscribers is a Delegate and the name starts with '_' to \"protect\" the attribute. Expose the attribute \"publicly\" is not a good practice, because other parts of the code can manipulate the attribute wrongly or get/set information in a way that was not mean to. To fix this, we can define 2 methods to encapsulate the delegate (adder/remover methods), Through these 2 methods the other objects in the code can subscribe/unsubscribe (add/remove) callables to our delegate.\n\n```Python\n  @event \n  def name_changed(self,value)-\u003eNone:\n    self._name_changed += value # add the new callable to the attribute with a delegate\n\n  @name_changed.remover\n  def name_changed(self,value)-\u003eNone:\n    self._name_changed -= value # remove the callable to the attribute with a delegate\n```\n\nCode above implements add/remove logic to the delegate. Function below *@event* decorator defines the logic for the *add* or how a callable should be added to our \"To do list\". Function below *@name_changed.remover* defines the logic for the *remover* or how a callable should be removed from the delegate\n\nNotice the functions HAVE to be named exactly with the same name, and if an *@event* is defined you **must** implement *@IDENTIFIER.remover* or the code will throw a traceback, this is to protect the integrity of the code and provide instructions about how to add AND remove a callable.\n\nThe callable to be added/removed will be passed through the \"value\" parameter. Notice in this example \"value\" parameter doesn't have any type annotation, this is only to keep this first example \"simple/readable\" at first sight, however it is **HIGHLY RECOMMENDED** annotate the type as the following examples on this document (Events with arguments or Events with modifiable arguments examples contain these annnotations), due this is the way to indicate clearly what is the signature expected from the event to their subscribers (callables). [Link to event annotation convention explanation](#event-annotation-convention)\n\nOnce this is in place, we have:\n\n- A place to store the callables \n- Logic to let to other parts of the code add/remove callables\n\nNow we need to execute the callables in the right momment, in this case the event is called \"name_changed\" so the callables should be executed when the name changes, this means our extra logic needs to be added in the *name* setter due that is the part of the code that has this responsability (change the person's name).\n\n```Python\n  @name.setter \n  def name(self,value:str)-\u003eNone:\n    self._name = value\n    # execute our \"To do list\" or delegate\n```\n\nIn the snippet code above the comment defines where the \"To do list\" needs to be executed, however, sometimes the own object needs to implement its own logic when (in this case) the property *name* change, for this purpose it is **HIGHLY RECOMMENDED** as a good practice define another function/method called \"\\_on\\_[EVENT NAME]\"\n\n```Python\n  @name.setter \n  def name(self,value:str)-\u003eNone:\n    self._name = value\n    self._on_name_changed()\n\n  def _on_name_changed(self)-\u003eNone:\n    #logic when the name change (if any)\n    self._name_changed() #external logic\n```\nInside of this method the own internal and external logic when the name change must be implemented, in other words, *What as a Person I need to do when my name changes?* (own/internal logic), and after, attend external logic (To do list) in other words instructionss provided by other objects or parts of the code. *What others needs to do when my name changes?*\n\nIn this case the class Person doesn't need to do \"something\" when the name changes (internal logic), so we only need to execute the external logic (execute the delegate)\n\n\nNow we have a way to add/remove subscribers and trigger the event, however, you might notice the code above is not exactly the same as the example code, this is because despite the event is now implemented and working is not following a good practice CONVENTION. So even with a working code, it is **HIGHLY RECOMMENDED** follow next convention:\n\n```Python\n  @name.setter \n  def name(self,value:str)-\u003eNone:\n    self._name = value\n    self._on_name_changed(EventArgs())\n\n  def _on_name_changed(self,e:EventArgs)-\u003eNone:\n    #internal logic if any\n    self._name_changed(self,e)\n```\n\nYou can notice 2 things\n\n1. *\\_on\\_name_changed* now requires a parameter called 'e' which is an EventArgs, this is a safety implementation, every \"\\_on\\_[EVENT NAME]\" must require an EventArgs (or any other class that inherits from it), this is a way to say \"Are you sure the event happens? show me the evidence!\", in this case there is no arguments so the evidence is an empty *EventArgs* object. *EventArgs* object is used first for the internal logic and then passed to the external logic as a parameter.\n\n2. 'self' is passed to the external logic as first parameter, this is to allow the subscribers know 'Who is executing my piece of code\"\n\n\n\n**As summary:** \n\n- There are 2 main sections to implement when you want to define an event: \n\n    1. Part that store and define how to add/remove callables\n    2. Part that executes/trigger those callables stored\n\n\u003ca id=\"event-annotation-convention\"\u003e\u003c/a\u003e\n- There are conventions about how the logic must be implemented to facilitate readability and maintenance of the code.\n\n- Callables to be subscribed to a simple event should follow the next signature:\n\n    *Callable[[object, EventArgs], None]* (a callable with 2 parameters, first one contains the publisher and second the event arguments, the function must return None)\n\nThe next snipped code shows and example of how the *simple events* should be implemented with the **HIGHLY RECOMMENDED** event annotation convention: \n\n```Python\n  @event \n  def name_changed(self,value:Callable[[object, EventArgs], None])-\u003eNone:\n    self._name_changed += value\n\n  @name_changed.remover\n  def name_changed(self,value:Callable[[object, EventArgs], None])-\u003eNone:\n    self._name_changed -= value \n```\n\nThis is done with the intention of clarifying what is the event expecting from its subscribers signature.\n\nThe omission of this event annotation convention do not affect how the code works, however, it could lead to problems of readability, clarity, maintenance and scalability. For these reasons **ALWAYS** place the event annotations, this is the only way for the event to communicate what is he expecting from its subscribers.\n\n\nTo use the event:\n\n```Python\ndef person_name_changed(sender:object,e:EventArgs)-\u003eNone: #function to be executed when the name changes (subscriber)\n  print(\"person change its name to %s\" % sender.name)\n\nperson = Person(\"Juan\")  #creates a person\nperson.name_changed += person_name_changed # we add 'person_name_changed' (subscriber) to event name_changed of 'person', this line will execute function under @event decorator (adder function)\nperson.name = \"Carlos\" # change the name to trigger the event (this will execute 'person_name_changed') \nperson.name_changed -= person_name_changed #unsubcribe the function, this line will execute function under @name_changed.remover decorator (remover function)\nperson.name = \"Something\" # change the name again to prove 'person_name_changed' is not executed anymore\n```\n\n\n##### Events with arguments\n\n  ```Python\n  from python_sharp import *\n  from typing import Callable\n\n  class MovedEventArgs(EventArgs):\n\n    _delta:int\n\n    def __init__(self,delta:int)-\u003eNone:\n      super().__init__()\n      self._delta = delta\n\n    @property\n    def delta(self)-\u003eint:\n      return self._delta\n\n\n  class Person:\n    \n    def __init__(self)-\u003eNone:\n      self._location = 0\n      self._moved = Delegate()\n\n    @property\n    def location(self)-\u003eint:\n      return self._location\n\n    @location.setter\n    def location(self,value:int)-\u003eNone:      \n      previous = self.location \n      self._location = value\n      self._on_moved(MovedEventArgs(self.location - previous))\n\n    def move(self,distance:int)-\u003eNone:\n      self.location += distance\n\n    def _on_moved(self,e:MovedEventArgs)-\u003eNone:\n      self._moved(self,e)\n\n    @event \n    def moved(self,value:Callable[[object, MovedEventArgs], None])-\u003eNone:\n      self._moved += value\n\n    @moved.remover\n    def moved(self,value:Callable[[object, MovedEventArgs], None])-\u003eNone:\n      self._moved -= value  \n\n\n  def person_moved(sender:object,e:MovedEventArgs)-\u003eNone:\n    print(\"Person moves %d units\" % e.delta)\n\n  person = Person()\n  person.move(15)\n  person.moved += person_moved\n  person.location = 25\n  person.moved -= person_moved\n  person.location = 0\n  ```\n\n  OUTPUT\n  ```\n  Person moves 10 units\n  ```\n\n*Events with arguments* are almost the same as *simple events* so, the next explanation will only address the differences between the 2 cases.\n\nOn this example an event named \"moved\" is implemented to notify when a person moves and provide how much does the person move.\n\n```Python\nclass MovedEventArgs(EventArgs):\n\n  _delta:int\n\n  def __init__(self,delta:int)-\u003eNone:\n    super().__init__()\n    self._delta = delta\n\n  @property\n  def delta(self)-\u003eint:\n    return self._delta\n```\n\nIn this case a custom EventArgs is created in order to be capable of store the event arguments, on this example the event is named \"moved\", and is going to be triggered when the person changes its location, in addition, it will provide HOW MUCH the person moves, this is the job of the *MovedEventArgs* and the main difference with a *simple event*.\n\nIn the next code block we can see how the event is being defined:\n\n```Python\n  @event \n  def moved(self,value:Callable[[object, MovedEventArgs], None])-\u003eNone:\n    self._moved += value\n\n  @moved.remover\n  def moved(self,value:Callable[[object, MovedEventArgs], None])-\u003eNone:\n    self._moved -= value  \n```\n\nin this case the only difference is the 'value' parameter annotation,  this indicates that the event requires a *Callable[[object, MovedEventArgs], None]* subscriber signature, in other words a *MovedEventArgs* will be provided to the subscriber.\n\nIt is **HIGHLY IMPORTANT** to realize *moved* event signature is *Callable[[object, MovedEventArgs], None]* therefore it can accept subscribers with the next signatures:\n\n- *Callable[[object, MovedEventArgs], None]*\n- *Callable[[object, EventArgs], None]*\n\nThese 2 signatures are ok because of polymorphism, it can be confusing due at first sight seems like we are assigning an *EventArgs* objeect to a *MovedEventArgs* variable (*MovedEventArgs* \u003c- *EventArgs*), this case in OOP (Object Oriented programming) is not valid, because it might throw a Traceback if a *MovedEventArgs* member is trying to be accessed in a *EventArgs* object. \n\nHowever in this example is not the case, the subscriber with *Callable[[object, EventArgs], None]* signature defines how the parameter object is going to be treated by the callable, in this case, the parameter will be used/treated as an *EventArgs*, and the event will provide a *MovedEventArgs* object to the callable so in reallity we are assigning a *MovedEventArgs* object to an *EventArgs* variable (*EventArgs* \u003c- *MovedEventArgs*) which by polymorphism will not cause any issue trying to access any of the *EventArgs* members in a *MovedEventArgs* object.\n\nNext code block explains a general case for what was explained above (subscriber signatures accepted by an event).\n\n\"...\" indicates that there is code is being omitted due it is not relevant to show the desire concept:\n\n```Python\nclass EventArgs:...\nclass FirstCustomEventArgs(EventArgs):...\nclass SecondCustomEventArgs(FirstCustomEventArgs):...\nclass ThirdCustomEventArgs(SecondCustomEventArgs):...\nclass FourthCustomEventArgs(ThirdCustomEventArgs):...\nclass FifthCustomEventArgs(FourthCustomEventArgs):...\n\nclass MyClassPublisher:\n\n  @event\n  def event_name(self,value:Callable[[object,ThirdCustomEventArgs],None])-\u003eNone:... #Example event that will provide a ThirdCustomEventArgs object to its subscribers.\n  ...\n\n\ndef subscriber(sender:object,e:EventArgs)-\u003eNone: ...                # Assignable\ndef subscriber_1(sender:object,e:FirstCustomEventArgs)-\u003eNone: ...   # Assignable\ndef subscriber_2(sender:object,e:SecondCustomEventArgs)-\u003eNone: ...  # Assignable\ndef subscriber_3(sender:object,e:ThirdCustomEventArgs)-\u003eNone: ...   # Assignable\ndef subscriber_4(sender:object,e:FourthCustomEventArgs)-\u003eNone: ...  # NO assignable\ndef subscriber_5(sender:object,e:FifthCustomEventArgs)-\u003eNone: ...   # NO assignable\n\n\npublisher = new MyClassPublisher()\n\npublisher.event_name += subscriber    # Correct\npublisher.event_name += subscriber_1  # Correct\npublisher.event_name += subscriber_2  # Correct\npublisher.event_name += subscriber_3  # Correct\npublisher.event_name += subscriber_4  # Wrong\npublisher.event_name += subscriber_5  # Wrong\n\n```\n\nThis feature allow you to have a subscriber being able to subscribe to a huge variety of event signatures, for example, if you have a piece of logic that needs to be executed by 2 events with different signatures is totally possible.\n\nIn order to get advantage of this, you need to keep in mind what is going to be the subscriber signature, if your subscriber signature is closer to *EventArgs* it is going to be more probable to be compatible with more event signatures. So as a good practice try to keep your *EventArgs* parameter as close as *EventArgs* possible.\n\nExample:\n\nIf you create a subscriber for an event with a signature *Callable[[object, ThirdCustomEventArgs],None]* and you do not need any specific information of a *ThirdCustomEventArgs* in your subsciber logic, you can consider lower your *EventArgs* to *SecondCustomEventArgs* (get closer to *EcentArgs*) or even lower, depending on what information you require in your subscriber logic, in this way your subscriber will be compatible with more event signatures in case you want to assign it to another events with different signature.\n\n\nSubscribers naming convention is not rigid, however it is **HIGHLY RECOMMENDED** that the name expresses in some way what is going to trigger its execution, most of the cases can be accomplish with:\n\n \"[your identifier]_[event name(s)]\"\n\n - persons_name_changed\n - elements_moved\n - compenents_rendering\n\n In that way is easy to understand/know what is going to cause the subscriber execution since its name contains the name of its triggers (event names).\n\n\nAnd the last difference but not less important is how the event is going to be triggered:\n\n```Python\n  @location.setter\n  def location(self,value:int)-\u003eNone:      \n    previous = self.location \n    self._location = value\n    self._on_moved(MovedEventArgs(self.location - previous))\n\n  def _on_moved(self,e:MovedEventArgs)-\u003eNone:\n    self._moved(self,e)\n```\n\nWe can see in the code block above now *\\_on\\_moved* method now requires a *MovedEventArgs*, as *simple events* did, this is for security reasons, if we are going to execute *\\_on\\_moved* method because the event happens, that is a way to say \"prove it or show the evidence!\".\n\nSecond difference is when *location* settter is calling *\\_on\\_moved* method, now it needs to create an instance of *MovedEventArgs* and to do so, it requires a quantity to be passed to the constructor, this quantity of \"how much the person moves\" can be calculated with a subtraction of previous and current location.\n\n\n**As summary:** \n\n*Events with arguments* and *simple arguments* are really similar and there are only some differences:\n\n- Needs a custom *EventArgs* class defined.\n- Events with custom *EventArgs* can accept different subscriber signatures\n- \\_on\\_[Event name] method uses the custom *EventArgs* class and this causes an extra security layer\n- Trigger code now needs to create an instance of the new custom *EventArgs* and to do so, it needs to provide/calculate the arguments needed by the custom *EventArgs* constructor\n\n\n##### Events with modifiable arguments \n\n  ```Python\n  from python_sharp import *\n  from typing import Callable\n\n  class LocationChangingEventArgs(CancellableEventArgs):\n\n    _value:int\n\n    def __init__(self,value:int)-\u003eNone:\n      super().__init__()\n      self._value = value\n\n    @property\n    def value(self)-\u003eint:\n      return self._value\n\n\n  class Person:\n    \n    def __init__(self)-\u003eNone:\n      self._location = 0\n      self._location_changing = Delegate()\n\n    @property\n    def location(self)-\u003eint:\n      return self._location\n\n    @location.setter\n    def location(self,value:int)-\u003eNone:\n      locationEventArgs = LocationChangingEventArgs(value)\n      self._on_location_changing(locationEventArgs)\n\n      if(not locationEventArgs.cancel):\n        self._location = value\n\n\n    def _on_location_changing(self,e:LocationChangingEventArgs)-\u003eNone:\n      self._location_changing(self,e)\n\n\n    @event\n    def location_changing(self,value:Callable[[object, LocationChangingEventArgs], None])-\u003eNone:\n      self._location_changing += value\n\n    @location_changing.remover\n    def location_changing(self,value:Callable[[object, LocationChangingEventArgs], None])-\u003eNone:\n      self._location_changing -= value\n\n\n  def person_location_changing(sender:object,e:LocationChangingEventArgs):\n    if e.value \u003e 100:\n      e.cancel = True\n\n  person = Person()\n  person.location = 50\n  person.location_changing += person_location_changing\n  person.location = 150\n  print(person.location)\n  ```\n\n OUTPUT\n ```\n 50\n ```\n\n*Events with modifiable arguments* are really similar to *Events with arguments* this explanation will address only the differences, so if you have doubts about the implementation go back to that section. \n\n*Events with modifiable arguments* are most likely implemented as **pre-events** this means the event advertise something that is about to happen, and it will let the subscribers provide information (Thorugh a custom *EventArgs*) to determine the future, like cancelling what was about to happen or modify how it was going to be done.\n\nOn this example an event named \"location_changing\" is implemented to notify when the person's location is about to be changed, this will let the subscribers cancel or modify the future behavior of that action.\n\nKey difference is the way the custom *EventArgs* is defined:\n\n```Python\nclass CancellableEventArgs(EventArgs): #Defined already on python_sharp module\n  \n  _cancel:bool\n\n  def __init__(self)-\u003eNone:\n    super().__init__()\n    self._cancel = False \n\n    \n  @property\n  def cancel(self)-\u003ebool:\n    return self._cancel\n    \n  @cancel.setter\n  def cancel(self,value:bool)-\u003eNone:\n    self._cancel = value\n\n\nclass LocationChangingEventArgs(CancellableEventArgs):\n\n  _value:int\n\n  def __init__(self,value:int)-\u003eNone:\n    super().__init__()\n    self._value = value\n\n  @property\n  def value(self)-\u003eint:\n    return self._value\n```\n\nAs you can see our custom *EventArgs* is *LocationChangingEventArgs* this class inherits from *CancellableEventArgs*, an *IMPORTANT* remark is Inherit from *CancellableEventArgs* is not necessary to create an *Event with modifiable argument*. *CancellableEventArgs* is just a built in custom *EventArgs* used for *Event with modifiable argument*, the fact *LocationChangingEventArgs* inherits from it is just to show case a use of it.\n\nKey factor to know when an event is an *Event with modifiable argument* is if the ***EventArgs* class class contains a property with a setter**.\n\n*LocationChangingEventArgs* does not contain a property with a setter by itself, but because it inherits from an *EventArgs* which contains it (*CancellableEventArgs*), we can consider that *LocationChangingEventArgs* actually contains a property with a setter.\n\nFor this particular example *cancel* property is set to *False* by default, when the  *EventArgs* object is passed to the subscribers now they have the ability to change *cancel* value property:\n\n```Python\ndef person_location_changing(sender:object,e:LocationChangingEventArgs):\n  if e.value \u003e 100:\n    e.cancel = True\n```\n\nIn the code block above is shown how the subscriber uses *e.value* to determine if *e.cancel* is going to be set to *True*, subsequently the publisher can use this value to modify some behavior:\n\n```Python\n  @location.setter\n  def location(self,value:int)-\u003eNone:\n    locationEventArgs = LocationChangingEventArgs(value)\n    self._on_location_changing(locationEventArgs)\n\n    if(not locationEventArgs.cancel):\n      self._location = value\n```\n\nCode above shows how the *LocationChangingEventArgs* is created and stored in *locationEventArgs* variable in order to keep a reference to the object, once that is done, the *LocationChangingEventArgs* object is send to *\\_on\\_location_changing* method to execute internal and external logic (external logic will execute all subscribers that might change *cancel* property value), and at the end of the  *\\_on\\_location_changing* execution we can check the *locationEventArgs* variable to evaluate if the *LocationChangingEventArgs* object *cancel* property is *True* or *False*, with this value we can alter the code behavior. For this particular example *cancel* property is being use to determine if the person should change its location or not\n\n\n##### Implementation summary\n\nIn the Implementation section there were examples about how to implement every single \"flavor\"/type of event, however the subscribers shown in those examples where a simple function that matches the event signature to keep the examples as understanding as possible. \n\nIt is important to remember that:\n\n- A subscriber can be subscribed to different event signatures\n- you can subscribe any callable to an event: function, method, class with \\_\\_call\\_\\_ implemented or even another delegate.\n- When a class contains a callable that is going to be subscribed to an event, and the subscription (+= operator over the event) is going to happen inside of the same class, is **HIGHLY RECOMMENDED** to keep the subscriber protected with \"_\" at the beginning of the subscriber identifier if the logic contained there is only of the class interest (only the class makes use of that logic).\n\n### Static events\n\nStatic events are almost the same as the events described previoulsy, they can be implemented as well as \"simple events\", \"with arguments\" or \"with modifiable arguments\", key difference is the event is applied as a class event, no an instance event.\n\nFor this section the example provided is a *simple static event* since it is the simplest way to show the differences, in case you want implement a *static event with arguments* go back to [Events with arguments](#Events-with-arguments) section and apply it to the class instead of the instance as the example of *simple static event* shows.\n\nImagine a class that provides the number of instances that it creates, this variable should be defined as an *static variable*, due there is no necessity for every single class instance to contain the same exactly number, and even worse, if the number changes it needs to be updated on every single instance created , that is the reason why this variable should be implemented as *static variable*.\n\nNow imagine we want to notify when an instance is created, in other words when the *static variable* changes its value, as this event is going to notify something is going on with a static variable, we need a static event:\n\n```Python\nfrom python_sharp import *\n\nclass Person:\n    \n  _instance_created:int = 0\n  _person_created:Delegate = Delegate()\n\n  def __init__(self)-\u003eNone:\n    Person._on_person_created(EventArgs())\n\n  @staticmethod\n  def get_instance_created()-\u003eint:\n    return Person._instance_created\n    \n  @staticmethod\n  def _set_instance_created(value:int)-\u003eNone:\n    Person._instance_created = value\n\n\n  @staticmethod\n  def _on_person_created(e:EventArgs)-\u003eNone:\n    Person._set_instance_created(Person.get_instance_created() + 1)\n    Person._person_created(None,e)\n        \n  @staticevent\n  def person_created(value:Callable[[object, EventArgs], None])-\u003eNone:\n    Person._person_created += value\n\n  @person_created.remover\n  def person_created(value:Callable[[object, EventArgs], None])-\u003eNone:\n    Person._person_created -= value\n\ndef person_created(sender:object,e:EventArgs)-\u003eNone:\n  print(\"There are %d persons created\" % Person.get_instance_created())\n\nperson_1 = Person()\nperson_2 = Person()\n\nPerson.person_created += person_created\n\nperson_3 = Person()\n```\n\nOUTPUT\n```\nThere are 3 persons created\n```\n\nAs you can see a *simple event* implementation is almost identical to *simple static event*\n\nKey differences:\n\n- All members used (variable, methods and event) are static (use of @staticevent instead of @event)\n- Get/Set methods to encapsulate the static variable are implemented as static methods due to the lack of static properties implementation in Python\n\nAnd that is it, those are all differences, so if you have questions about how this code works, it is **HIGHLY RECOMMENDED** go back to [Events](#Events) section or raise a question on the [issues](https://github.com/juanclopgar97/python_sharp/issues) section of this repository.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuanclopgar97%2Fpython_sharp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuanclopgar97%2Fpython_sharp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuanclopgar97%2Fpython_sharp/lists"}