{ "info": { "author": "Joerg Beckers", "author_email": "pypi@jobe-software.de", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Topic :: Home Automation" ], "description": "# raspend - a lightweight web service framework\n\n## Motivation\n\nSince I am doing a lot of home automation stuff on my Raspberry Pi and since the third Python script for that had the same structure, I decided, strictly following the [DRY principle](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), to create an easy to use framework to simplify my life for when I start my next project on my RPi.\n\n## Introduction\n\nIt should be mentioned that **raspend**, which is the abbreviation for **rasp**berry back**end**, was originally intended to be a web service framework for the Raspberry Pi. But since it's written entirely in Python and has no RPi specific code or dependencies, it can be used on any platform having a Python interpreter installed.\n\n**raspend** combines two core features. The first is the data processing part, where one or more threads share the same *dictionary* they read from or write to. The data contained in this shared dictionary is exposed to the outside world through an HTTP interface as a JSON string. Therefore the HTTP interface provides the */data* endpoint. The second feature is providing an easy to use interface, which lets you invoke your self-defined commands via the already mentioned HTTP interface. (see [How to use the HTTP interface](#how-to-use-the-http-interface) for more details)\n\n**raspend** comes with a ready-to-use application class called **RaspendApplication**. It manages your threads, your commands and the HTTP interface. Pass the port number, the HTTP interface should listen on, to the constructor of **RaspendApplication**. If you don't want to expose any data or invoke any commands on your system via HTTP interface, you can put **RaspendApplication** into \"offline\" mode by passing *None* instead of a port number. \n\nWhen all initialization stuff is done (adding commands, creating threads), then you start your application by calling the **run** method of **RaspendApplication**. The **RaspendApplication** class installs signal handlers for SIGTERM and SIGINT, so you can quit your application by pressing CTRL+C or sending one of the signals via the **kill** command of your shell. \n\n## Data processing with **raspend**\n\nWith **raspend**, creating threads is really simple and straight forward. All you need to do is, derive the **ThreadHandlerBase** class and implement the abstract methods *prepare* and *invoke*. The *prepare* method is called before the thread loop starts, while *invoke* will be called for every iteration of the thread loop. For tasks like cyclic reading temperature measurements or checking the CPU performance or something like that, you can use the **createWorkerThread** method of the **RaspendApplication** class. It takes an instance of your thread handler and a timeout value in seconds as parameters. The thread will sleep for *timeout* seconds past every iteration.\n\nIf you want a thread or task to do its work in a more scheduled way, such as run once a day, then you can use the **createScheduledWorkerThread** method of the **RaspendApplication** class. **createScheduledWorkerThread**, like **createWorkerThread**, takes an instance of your thread handler. But instead taking a timeout, you have to pass a start time, a start date, the type of repetition and a repetition factor. Start time and date can be *None*. If so, the current time and date will be used. If *repetitionType* is *None*, it defaults to **ScheduleRepetitionType.DAILY**. The repetition factor is applied to the repetition type and defaults to 1. For example, if you need to write your temperature measurements to a file every 15 minutes starting immediately, you would choose **ScheduleRepetitionType.MINUTELY** with a repetition factor set to 15 and start time and date set to *None*.\n\n``` python\nfrom raspend import RaspendApplication, ThreadHandlerBase, ScheduleRepetitionType\n\nclass ReadOneWireTemperature(ThreadHandlerBase):\n ...\n\nclass PublishOneWireTemperatures(ThreadHandlerBase):\n ...\n\nclass WriteOneWireTemperaturesToFile(ThreadHandlerBase):\n ...\n\n# Create instance of the application class passing a port number for the HTTP interface.\nmyApp = RaspendApplication(args.port)\n\n# Create worker threads for reading every temperature sensor once a minute.\nmyApp.createWorkerThread(ReadOneWireTemperature(\"basement\", \"party_room\", \"/sys/.../w1_slave\"), 60)\nmyApp.createWorkerThread(ReadOneWireTemperature(\"basement\", \"heating_room\", \"/sys/.../w1_slave\"), 60)\nmyApp.createWorkerThread(ReadOneWireTemperature(\"basement\", \"fitness_room\", \"/sys/.../w1_slave\"), 60)\nmyApp.createWorkerThread(ReadOneWireTemperature(\"ground_floor\", \"kitchen\", \"/sys/.../w1_slave\"), 60)\nmyApp.createWorkerThread(ReadOneWireTemperature(\"ground_floor\", \"living_room\", \"/sys/.../w1_slave\"), 60)\n\n# Publish temperatures to MySQL database every minute.\nendPointURL = \"http://localhost/raspend_demo/api/post_data.php\"\nmyApp.createWorkerThread(PublishOneWireTemperatures(endPointURL, username, password), 60)\n\n# Write temperatures to 1wire.csv every 15 minutes.\nmyApp.createScheduledWorkerThread(WriteOneWireTemperaturesToFile(\"./1wire.csv\"), \n None, \n None, \n ScheduleRepetitionType.MINUTELY, \n 15)\n\n# Run main loop.\nmyApp.run()\n```\n\n## Making commands invocable with **raspend**\n\nAs mentioned earlier, **raspend's** second core feature is providing an easy way to let you invoke self-defined commands via its HTTP interface. Such a command must be implemented as a method of a Python class. This method then has to be passed to the **addCommand** method of **RaspendApplication**. You now can call this method with a simple HTTP GET or POST request. \n\n``` python\nfrom raspend import RaspendApplication\n\nclass DoorBell():\n def __init__(self, *args, **kwargs):\n self.doorBellState = \"on\"\n\n def switchDoorBell(self, onoff):\n if type(onoff) == str:\n self.doorBellState = \"on\" if onoff == \"on\" else \"off\"\n elif type(onoff) == int:\n self.doorBellState = \"on\" if onoff >= 1 else \"off\"\n else:\n raise TypeError(\"State must be 'int' or 'string'.\")\n return self.doorBellState\n\n def getCurrentState(self):\n return self.doorBellState\n\n# Create instance of the application class passing a port number for the HTTP interface.\nmyApp = RaspendApplication(args.port)\n\n# Create instance of the class whos methods will be made invocable via HTTP interface.\ntheDoorBell = DoorBell()\n\n# Add the methods as a command.\nmyApp.addCommand(theDoorBell.switchDoorBell)\nmyApp.addCommand(theDoorBell.getCurrentState)\n\n# Run main loop.\nmyApp.run()\n``` \n\nPlease have a look at the examples included in this project. To see how I switch on/off my doorbell see [doorbell](https://github.com/jobe3774/doorbell). Another good example on how to use **raspend** is [mbpv](https://github.com/jobe3774/mbpv), which shows how I use **raspend's** worker threads to read out current values of my solar inverters. \n\n## How to use the HTTP interface?\n\n### The data part\n\nAs already mentioned, all worker threads share the same dictionary they read from or write to. You can query this data by sending a HTTP GET request to **http:///data**. The HTTP interface then responds with the whole shared dictionary as a JSON string. It is recommended not to write too much data in this dictionary, as this could reduce performance.\n\nLet's say you are measuring the temperatures of different rooms of your house, then the shared dictionary could have the following structure:\n\n``` json\n{\n \"basement\" : {\n \"party_room\": 17.8,\n \"heating_room\": 18,\n \"fitness_room\": 19.5\n },\n \"ground_floor\" : {\n \"kitchen\": 23.5,\n \"living_room\": 23.6\n }\n}\n```\n\nIf you only want to know the temperatures of the ground floor you can request **/data/ground_floor**. Then the response would be:\n\n``` json\n\"ground_floor\" : {\n \"kitchen\": 23.5,\n \"living_room\": 23.6\n }\n```\n\nOr if you only want to know the temperature of the fitness room in your basement you could use **/data/basement/fitness_room** and the response would be:\n\n``` json\n19.5\n```\n\n### The command part\n\nNow let's have a look at the command interface of **raspend**. If you want to know which commands are available you can request **/cmds**. Then the response for the above mentioned *doorbell* example would be:\n\n``` json\n{\n \"Commands\": [{\n \"Command\": {\n \"Name\": \"theDoorBell.switchDoorBell\",\n \"Args\": {\n \"onoff\": \"\"\n }\n }\n }, {\n \"Command\": {\n \"Name\": \"theDoorBell.getCurrentState\",\n \"Args\": {}\n }\n }]\n}\n```\n\nAs you can see in the response above, your variable names should be in a more descriptive manner, since the instance of your Python class is used instead of the class name. Among other things, this allows us to have more than one instance of the respective class. \n\nYou invoke a command by sending it's call information as described in the list above via HTTP POST request. \n\nHere a JavaScript example:\n\n``` javascript\n\nlet payload = {\n Command: {\n Name: \"theDoorBell.switchDoorBell\",\n Args: {\n onoff: \"off\"\n }\n }\n};\n\nlet response = await fetch(\"http://localhost:8080\", {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json; charset=utf-8'\n },\n body: JSON.stringify(payload)\n});\n\nif (response.status == 200) {\n let responsePayload = await response.json();\n console.log(responsePayload);\n}\n\n``` \n\nThe HTTP interface receives\n\n``` json\n{\n \"Command\": {\n \"Name\": \"theDoorBell.switchDoorBell\",\n \"Args\": {\n \"onoff\": \"off\"\n }\n }\n}\n``` \n\nand invokes the method. The response of this HTTP POST request will be **your** JSON enhanced with the result of the method invocation:\n\n``` json\n{\n \"Command\": {\n \"Name\": \"theDoorBell.switchDoorBell\",\n \"Args\": {\n \"onoff\": \"off\"\n },\n \"Result\": \"off\"\n }\n}\n``` \nSince remain untouched, you can attach any other information with that command such as an element-id of your frontend invoking it.\n\nStarting with version 1.1.0, you can also use HTTP GET requests to invoke commands. The request has the following structure:\n\n``` \n /cmd?name=command&arg1=val1&arg2=val2...&argN=valN\n```\n\nSo for the above mentioned example **theDoorBell.switchDoorBell**, the correct request would be:\n\n``` \n /cmd?name=theDoorBell.switchDoorBell&onoff=off\n```\n\nThe HTTP interface invokes the command and responds with the result of the invocation as a JSON string.\n\n## How to install?\n\nMake sure you have installed Python 3.5 or higher. I've tested the package on my Raspberry Pi 3 Model B+ running **Raspbian GNU/Linux 9 (stretch)** with Python 3.5.3 installed. \n\nUse\n```\n$ pip install raspend\n```\nor if Python 3 isn't the default use\n```\n$ pip3 install raspend\n```\nto install the package.\n\n## Remarks\n\nThere is a breaking change from version 1.4.0 to 2.0.0. The modules called **DataAcquisition** and **Publishing** fell victim to the DRY principle and have been replaced by the worker threads. This also means that the following methods have been removed from the **RaspendApplication** class: \n* **createDataAcquisitionThread**\n* **createPublishDataThread**\n* **createScheduledPublishDataThread**\n\nPlease use **createWorkerThread** or **createScheduledWorkerThread** instead. \n\nMigration should be easy. Just replace all derivations of **DataAcquisitionHandler** and **PublishDataHandler** by **ThreadHandlerBase**. Remove all ```from raspend.utils import dataacquisition as DataAcquisition``` and ```from raspend.utils import publishing as Publishing``` and use ```from raspend import ThreadHandlerBase``` instead. In your implementations of **DataAcquisitionHandler** rename the **acquireData** method to **invoke**. In your implementations of **PublishDataHandler** rename the **publishData** method to **invoke** as well. \n\n## License\n\nMIT. See LICENSE file.\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/jobe3774/raspend.git", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "raspend", "package_url": "https://pypi.org/project/raspend/", "platform": "", "project_url": "https://pypi.org/project/raspend/", "project_urls": { "Homepage": "https://github.com/jobe3774/raspend.git" }, "release_url": "https://pypi.org/project/raspend/2.0.4/", "requires_dist": null, "requires_python": ">=3.5", "summary": "A lightweight web service framework.", "version": "2.0.4", "yanked": false, "yanked_reason": null }, "last_serial": 6626033, "releases": { "0.9.9": [ { "comment_text": "", "digests": { "md5": "498108ec0079c21d066eb3f19d16f2d2", "sha256": "aa057f646b92fcba3e9868172c70f6ba4fcbd483cbe7c085b62e92629de16bca" }, "downloads": -1, "filename": "raspend-0.9.9-py3-none-any.whl", "has_sig": false, "md5_digest": "498108ec0079c21d066eb3f19d16f2d2", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 11641, "upload_time": "2019-09-18T22:16:31", "upload_time_iso_8601": "2019-09-18T22:16:31.579123Z", "url": "https://files.pythonhosted.org/packages/96/8d/0f2614db5206c991de3698cecd2181f924d7f5b5c967265a0e96c47e0e8d/raspend-0.9.9-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "21fbf3953ddf48d9dd25e662f0598855", "sha256": "6aee6eb6872dcaae8e374662f0b65a3777634f787acd1d33e94d804e28760046" }, "downloads": -1, "filename": "raspend-0.9.9.tar.gz", "has_sig": false, "md5_digest": "21fbf3953ddf48d9dd25e662f0598855", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 11232, "upload_time": "2019-09-18T22:16:33", "upload_time_iso_8601": "2019-09-18T22:16:33.580856Z", "url": "https://files.pythonhosted.org/packages/93/d4/f4136b459c676bf3ed7239a6006c993a9085bf409e41e4cf407837d9d2bf/raspend-0.9.9.tar.gz", "yanked": false, "yanked_reason": null } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "f7a0c5da338b4b70f961cc28b4dd2057", "sha256": "b70252cbac2f1852327f58b5ebb6527a521043c00e76dbfa63e3bcc1ed0c60e8" }, "downloads": -1, "filename": "raspend-1.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "f7a0c5da338b4b70f961cc28b4dd2057", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 11625, "upload_time": "2019-09-21T15:19:39", "upload_time_iso_8601": "2019-09-21T15:19:39.838323Z", "url": "https://files.pythonhosted.org/packages/6a/05/188d16a6a10867dfb2bf849f7b61d12d1fb9ae20eb230b95a7f2c2a70a89/raspend-1.0.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "e96ac57125c0f5fafd40b0bf316b46a3", "sha256": "335805197828c5f20ac75fa8030f762d4942d803cfe0d06f0f9cdc238f4f4e74" }, "downloads": -1, "filename": "raspend-1.0.0.tar.gz", "has_sig": false, "md5_digest": "e96ac57125c0f5fafd40b0bf316b46a3", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 11264, "upload_time": "2019-09-21T15:19:41", "upload_time_iso_8601": "2019-09-21T15:19:41.434190Z", "url": "https://files.pythonhosted.org/packages/51/03/c7b03ae98a2ae728ff23f723585ecf8d790031e74569b3d01000baf37305/raspend-1.0.0.tar.gz", "yanked": false, "yanked_reason": null } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "335896569082552364e14c6fcf272b44", "sha256": "8a99b95b3026413a61afed41370adb08d48d56c2581e575692c4c35a3cd17751" }, "downloads": -1, "filename": "raspend-1.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "335896569082552364e14c6fcf272b44", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 11597, "upload_time": "2019-09-22T11:47:09", "upload_time_iso_8601": "2019-09-22T11:47:09.490492Z", "url": "https://files.pythonhosted.org/packages/2f/65/d4580d94b8a118384e5ccc22cd096f55e11065d79c74780c705f9fe104ed/raspend-1.0.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "806f3a4a5e312e9c57cabee6c4573f20", "sha256": "f01cea290645e16e42a18f2b651e9fbfaeaef39b309ccfcef949c7617acf91a2" }, "downloads": -1, "filename": "raspend-1.0.1.tar.gz", "has_sig": false, "md5_digest": "806f3a4a5e312e9c57cabee6c4573f20", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 11243, "upload_time": "2019-09-22T11:47:10", "upload_time_iso_8601": "2019-09-22T11:47:10.863094Z", "url": "https://files.pythonhosted.org/packages/cc/17/30d48ca587f68b7cad7c9e1949fc328afdd16c7c5431e9e38ac79ce24a3f/raspend-1.0.1.tar.gz", "yanked": false, "yanked_reason": null } ], "1.0.2": [ { "comment_text": "", "digests": { "md5": "f9961b45f288d7a366ec28f7037ef839", "sha256": "9d229e58da0993bda87afd606151df04275afb6a816ff00de83545e35053b03d" }, "downloads": -1, "filename": "raspend-1.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "f9961b45f288d7a366ec28f7037ef839", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 11665, "upload_time": "2019-09-22T12:07:06", "upload_time_iso_8601": "2019-09-22T12:07:06.314058Z", "url": "https://files.pythonhosted.org/packages/0f/ca/70e4ec7f90868904fb1975318fe6099f351c5b6f6fe5885369ebe021a3ae/raspend-1.0.2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "2e78cb83f0b15c96a2795b5997ef3169", "sha256": "49b25bd72ae5f915356db3da629d51b039c43b01cca2abe5b2402a7db978920d" }, "downloads": -1, "filename": "raspend-1.0.2.tar.gz", "has_sig": false, "md5_digest": "2e78cb83f0b15c96a2795b5997ef3169", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 11407, "upload_time": "2019-09-22T12:07:08", "upload_time_iso_8601": "2019-09-22T12:07:08.057994Z", "url": "https://files.pythonhosted.org/packages/18/0f/6ad0e1040b54b94da499963eadb4bbb3dd984c9ed901d3e70ea83b10a123/raspend-1.0.2.tar.gz", "yanked": false, "yanked_reason": null } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "6ef2d834286f61a904676190ba23ccb9", "sha256": "20971a8bfc0e669b771c53162efd4e75ec2827789ba3752357e5d9a6b3087091" }, "downloads": -1, "filename": "raspend-1.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "6ef2d834286f61a904676190ba23ccb9", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 12085, "upload_time": "2019-09-29T20:07:39", "upload_time_iso_8601": "2019-09-29T20:07:39.250779Z", "url": "https://files.pythonhosted.org/packages/fb/8b/0146c199416b59123a6f062ba25f725e9eb6116623fdebd4bd4e1998c1c4/raspend-1.1.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "2b61e8ca7822708fefb08ff0c0ae4378", "sha256": "ccc6df66a50cb3206ab4ae14ecace0a5f0a806f547a7b2cd71165b2db17b28e1" }, "downloads": -1, "filename": "raspend-1.1.0.tar.gz", "has_sig": false, "md5_digest": "2b61e8ca7822708fefb08ff0c0ae4378", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 12023, "upload_time": "2019-09-29T20:07:41", "upload_time_iso_8601": "2019-09-29T20:07:41.144318Z", "url": "https://files.pythonhosted.org/packages/bd/4c/e4108dbb7dd31dbad91814be36d8b7bf914958a838da23566f42f3520572/raspend-1.1.0.tar.gz", "yanked": false, "yanked_reason": null } ], "1.2.0": [ { "comment_text": "", "digests": { "md5": "d14122724fd45108f46e0fdb771e2afa", "sha256": "fff5a120e1a2e1ce9d257b65318d894fa3484c09e357d820c3eac27009c489c0" }, "downloads": -1, "filename": "raspend-1.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "d14122724fd45108f46e0fdb771e2afa", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 13790, "upload_time": "2019-10-09T13:43:44", "upload_time_iso_8601": "2019-10-09T13:43:44.668978Z", "url": "https://files.pythonhosted.org/packages/57/c5/194a46be97eea5e6b3eeafdabe655f2ad604a66453aa530c3a0a1a9810f0/raspend-1.2.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "6317c906615c94def877bc81e69937ab", "sha256": "011d29e629029bcf267dadc96cf86c11ab437cf51210881983a55c23388682f6" }, "downloads": -1, "filename": "raspend-1.2.0.tar.gz", "has_sig": false, "md5_digest": "6317c906615c94def877bc81e69937ab", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 13586, "upload_time": "2019-10-09T13:43:46", "upload_time_iso_8601": "2019-10-09T13:43:46.554781Z", "url": "https://files.pythonhosted.org/packages/9e/6d/5af7980c369cef513fbbc2465204f256406720d619094f6457bf53e8b7ad/raspend-1.2.0.tar.gz", "yanked": false, "yanked_reason": null } ], "1.2.3": [ { "comment_text": "", "digests": { "md5": "28e59b537ff7fc523d225f58b14f4ab1", "sha256": "67cdd64c660ff29cb7e2bf5be23a75e3ad051de25f5b13d4da451dc6577a682d" }, "downloads": -1, "filename": "raspend-1.2.3-py3-none-any.whl", "has_sig": false, "md5_digest": "28e59b537ff7fc523d225f58b14f4ab1", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 14017, "upload_time": "2019-10-10T19:30:21", "upload_time_iso_8601": "2019-10-10T19:30:21.625436Z", "url": "https://files.pythonhosted.org/packages/eb/5d/9f3f9563e956364219ff04ce2bb62af334299f1ec125023c26dc9a683519/raspend-1.2.3-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "620e612e118f01f6536ea3fb906bdc07", "sha256": "c2713b4912652132387181c38577b8eeb2e78af0b891a6417726ec9544edf3fb" }, "downloads": -1, "filename": "raspend-1.2.3.tar.gz", "has_sig": false, "md5_digest": "620e612e118f01f6536ea3fb906bdc07", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 13783, "upload_time": "2019-10-10T19:30:23", "upload_time_iso_8601": "2019-10-10T19:30:23.702783Z", "url": "https://files.pythonhosted.org/packages/59/7f/deacaa4fd21ffb1de88c563c8fc5c7e67e27b7058d831cb928fc46b1193e/raspend-1.2.3.tar.gz", "yanked": false, "yanked_reason": null } ], "1.3.0": [ { "comment_text": "", "digests": { "md5": "b8e2f39bba7944f709f12a576696dedb", "sha256": "89ab3da84a5df425f8baf4b9bcf0b1fd575646dc587769ef15a275b8ee5f14c9" }, "downloads": -1, "filename": "raspend-1.3.0-py3-none-any.whl", "has_sig": false, "md5_digest": "b8e2f39bba7944f709f12a576696dedb", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 15886, "upload_time": "2019-10-20T19:26:54", "upload_time_iso_8601": "2019-10-20T19:26:54.559793Z", "url": "https://files.pythonhosted.org/packages/b4/55/1ade559eec7d360615d0ef18b441e759f4ab92cda513eab64a763b5e56f8/raspend-1.3.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "496908e3af1556c30500f7b0aa957e6c", "sha256": "808e532b628f8bf38d10862061de01b269e04f7a3f584544b9f4339a899f4f29" }, "downloads": -1, "filename": "raspend-1.3.0.tar.gz", "has_sig": false, "md5_digest": "496908e3af1556c30500f7b0aa957e6c", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 15459, "upload_time": "2019-10-20T19:26:56", "upload_time_iso_8601": "2019-10-20T19:26:56.163641Z", "url": "https://files.pythonhosted.org/packages/19/8e/b9d0b65e2eed6c916fc7cfc96efcf0e69fec669f5d02cd778887278353ea/raspend-1.3.0.tar.gz", "yanked": false, "yanked_reason": null } ], "1.4.0": [ { "comment_text": "", "digests": { "md5": "4dbd6043b537045a9fd02f6c6cfcdc5b", "sha256": "84e2d4d75e5b0ef05239bf5b55e487b293f581f7400ee635b38e6f6776982a77" }, "downloads": -1, "filename": "raspend-1.4.0-py3-none-any.whl", "has_sig": false, "md5_digest": "4dbd6043b537045a9fd02f6c6cfcdc5b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 17314, "upload_time": "2019-10-28T21:58:58", "upload_time_iso_8601": "2019-10-28T21:58:58.358907Z", "url": "https://files.pythonhosted.org/packages/85/ac/ad093e7594979c45ffcddaa9aa3936d7f531ff93ba4c39abd0440b6c8a56/raspend-1.4.0-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "491a52370714f16f58461a44ac4dcf79", "sha256": "d6e1abb108f1b9e57368e318c33d5977def6ca9664aa71c725caa4abafc046d9" }, "downloads": -1, "filename": "raspend-1.4.0.tar.gz", "has_sig": false, "md5_digest": "491a52370714f16f58461a44ac4dcf79", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 17432, "upload_time": "2019-10-28T21:59:00", "upload_time_iso_8601": "2019-10-28T21:59:00.331644Z", "url": "https://files.pythonhosted.org/packages/af/96/8bc6281808305de3e9774757bca865ac1bb3998c449acb626ea27dfffa2d/raspend-1.4.0.tar.gz", "yanked": false, "yanked_reason": null } ], "2.0.1": [ { "comment_text": "", "digests": { "md5": "c61646b4b47a1d772678e70d39a218ea", "sha256": "ec7e5e3d28bb74dc34a46dd146f08a04d1f05aac487c434d82499c819ae0523a" }, "downloads": -1, "filename": "raspend-2.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "c61646b4b47a1d772678e70d39a218ea", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 15741, "upload_time": "2019-11-12T19:41:30", "upload_time_iso_8601": "2019-11-12T19:41:30.386192Z", "url": "https://files.pythonhosted.org/packages/f6/b9/0e3031cec50b9fc3a2ebb0f29aa5ae5f89066e3ad1bb1470179a8c474e43/raspend-2.0.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "469b4b04b4ec76118aab48e9aa6f7094", "sha256": "fcceece743dd42d74f391ce13cb389c1730890fed39fa523aa4ae64fac26bffa" }, "downloads": -1, "filename": "raspend-2.0.1.tar.gz", "has_sig": false, "md5_digest": "469b4b04b4ec76118aab48e9aa6f7094", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 16423, "upload_time": "2019-11-12T19:41:32", "upload_time_iso_8601": "2019-11-12T19:41:32.189846Z", "url": "https://files.pythonhosted.org/packages/43/a0/9b3c5f443ec4b475feebc659834835656ed1ac6847a93cd2cf34810c729e/raspend-2.0.1.tar.gz", "yanked": false, "yanked_reason": null } ], "2.0.2": [ { "comment_text": "", "digests": { "md5": "7de7f7c0173a6baf2534838a1bbcdc4c", "sha256": "788f7684896e009bb7066391775839d7943ebdc43e0313e24e01e028cc5c55c4" }, "downloads": -1, "filename": "raspend-2.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "7de7f7c0173a6baf2534838a1bbcdc4c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 15905, "upload_time": "2020-01-22T21:47:02", "upload_time_iso_8601": "2020-01-22T21:47:02.430214Z", "url": "https://files.pythonhosted.org/packages/67/3a/3b825577ab1689550925e5e2be89a10c7ecd21a6a5e6d067a47194febdf2/raspend-2.0.2-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "8a0c01c2d29126c1a5ff8490338ddde7", "sha256": "562e3a78491839fc12f7e23ad65acb2832bc594db8df0928e9430dfae272a092" }, "downloads": -1, "filename": "raspend-2.0.2.tar.gz", "has_sig": false, "md5_digest": "8a0c01c2d29126c1a5ff8490338ddde7", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 16549, "upload_time": "2020-01-22T21:47:04", "upload_time_iso_8601": "2020-01-22T21:47:04.323257Z", "url": "https://files.pythonhosted.org/packages/bf/a6/bc088acf08833c938356db1f95a213c07274c287a91015b76bcf9ff885d6/raspend-2.0.2.tar.gz", "yanked": false, "yanked_reason": null } ], "2.0.3": [ { "comment_text": "", "digests": { "md5": "6897223b5a9528852360478d486c4cf4", "sha256": "0855968994af21949bddd07b1a1dd0376efc04b7fa7a6ee49988f3f988e3ffc2" }, "downloads": -1, "filename": "raspend-2.0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "6897223b5a9528852360478d486c4cf4", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 15989, "upload_time": "2020-02-05T14:23:55", "upload_time_iso_8601": "2020-02-05T14:23:55.013639Z", "url": "https://files.pythonhosted.org/packages/d5/19/4d83d8c76cb7c8cf0dae9e369efaf1d0cf6038ea5c358098d2957e8fd0cd/raspend-2.0.3-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "80b15139d78cc900f593f2d204550d68", "sha256": "6281fdf4e0fc8791b6a769e26646c226c2839f7a04a0ddfecb9749f8c63be12f" }, "downloads": -1, "filename": "raspend-2.0.3.tar.gz", "has_sig": false, "md5_digest": "80b15139d78cc900f593f2d204550d68", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 16636, "upload_time": "2020-02-05T14:23:56", "upload_time_iso_8601": "2020-02-05T14:23:56.660100Z", "url": "https://files.pythonhosted.org/packages/c6/5f/6af1a3b4dbec3ba8441cbcddd00ba0259487e12c3d3b570bf592a30116de/raspend-2.0.3.tar.gz", "yanked": false, "yanked_reason": null } ], "2.0.4": [ { "comment_text": "", "digests": { "md5": "993ce40f2bc5da4372c0a61967f7fa3e", "sha256": "f4d1c1216c2424762071809d4c0e48060aa8e15558bca5b33a451195a646a129" }, "downloads": -1, "filename": "raspend-2.0.4-py3-none-any.whl", "has_sig": false, "md5_digest": "993ce40f2bc5da4372c0a61967f7fa3e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 16060, "upload_time": "2020-02-13T21:55:07", "upload_time_iso_8601": "2020-02-13T21:55:07.624378Z", "url": "https://files.pythonhosted.org/packages/c5/29/0d299840ebbddf559a53bb50c6a3f837196b3802cb9b18aefb79ec2a181c/raspend-2.0.4-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "27c354c6509ef80ca98ad124c5e41e72", "sha256": "2457b0019deebc45f0cdecacd69812e94a91a8de29173a616668869a3a5187f7" }, "downloads": -1, "filename": "raspend-2.0.4.tar.gz", "has_sig": false, "md5_digest": "27c354c6509ef80ca98ad124c5e41e72", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 16697, "upload_time": "2020-02-13T21:55:09", "upload_time_iso_8601": "2020-02-13T21:55:09.142828Z", "url": "https://files.pythonhosted.org/packages/7f/8d/ea950e79ae72769490456d22ada40b1a114c0822af914bc4e2cac0f6d3d1/raspend-2.0.4.tar.gz", "yanked": false, "yanked_reason": null } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "993ce40f2bc5da4372c0a61967f7fa3e", "sha256": "f4d1c1216c2424762071809d4c0e48060aa8e15558bca5b33a451195a646a129" }, "downloads": -1, "filename": "raspend-2.0.4-py3-none-any.whl", "has_sig": false, "md5_digest": "993ce40f2bc5da4372c0a61967f7fa3e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.5", "size": 16060, "upload_time": "2020-02-13T21:55:07", "upload_time_iso_8601": "2020-02-13T21:55:07.624378Z", "url": "https://files.pythonhosted.org/packages/c5/29/0d299840ebbddf559a53bb50c6a3f837196b3802cb9b18aefb79ec2a181c/raspend-2.0.4-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "27c354c6509ef80ca98ad124c5e41e72", "sha256": "2457b0019deebc45f0cdecacd69812e94a91a8de29173a616668869a3a5187f7" }, "downloads": -1, "filename": "raspend-2.0.4.tar.gz", "has_sig": false, "md5_digest": "27c354c6509ef80ca98ad124c5e41e72", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.5", "size": 16697, "upload_time": "2020-02-13T21:55:09", "upload_time_iso_8601": "2020-02-13T21:55:09.142828Z", "url": "https://files.pythonhosted.org/packages/7f/8d/ea950e79ae72769490456d22ada40b1a114c0822af914bc4e2cac0f6d3d1/raspend-2.0.4.tar.gz", "yanked": false, "yanked_reason": null } ], "vulnerabilities": [] }