Handling Errors While Parsing the yaml File in Yaydoc

Yaydoc, our automatic documentation generator uses a yaml file to read a user’s configuration. The internal configuration parser basically converts the yaml file to a python dictionary. Then, it serializes the values of that dictionary using a custom serialization format. From there it associates those values with environment variables which are then passed to bash scripts for various tasks such as deployment, generation, etc.. Some of those environment variables are again passed to another python layer which interacts with sphinx where they are deserialized before use. This whole system works pretty well for our use cases. Now let’s assume a user adds a yaml file where they have a malformed section in the file. For example, to specify a theme, one needs to add the following to the yaml file. build: theme: name: sphinx_fossasia_theme But our user has the following in their yaml file. build: theme: sphinx_fossasia_theme Now this will raise an error as we expect a dictionary as a value for the key ‘theme’ but we got a string. Now how do we handle such cases without ignoring the entire file as that would be too much of a penalty for such a small mistake? One approach would have been to wrap each call to connect with a bunch of try-catch but that would render the code unreadable as the initial motivation for implementing the connect method was to abstract the internal implementation so that other contributors who may not be well versed with python can also easily add config options without needing to learn a bunch of python constructs. So, what we did was that, while merging the dictionary containing default options and the dictionary containing the user preferences, we check whether the default has the same data type as that of the incoming value. If they are, It’s deemed safe to merge. There are certain relaxations though, like if the current type is a list, then the incoming value can be of any time as that can always be converted to a list of a single element. This is required to support the following syntax. key: - value key: value The above two blocks are equivalent due to the above-mentioned approach although the type is different. Now, after this pre-validation step is over we can ensure that the if the assumed type for a key is let’s say a dictionary, then it would be a dictionary. Hence no type errors would be raised like trying to access a dict method for another object, say a string which happened with the earlier implementation. After this, an extra parameter was added to the connect method to which we can now pass a validation function which if returns false, those values would be ignored. Usage of this feature has been implemented to a small level where we validate the links to subprojects and if they look like a valid github repo only then will they be included. Note that their existence is not checked. Only a regex based validation is…

Continue ReadingHandling Errors While Parsing the yaml File in Yaydoc

Showing Pull Request Build Status in Yaydoc

Yaydoc is integrated to various open source projects in FOSSASIA.  We have to make sure that the contributors PR should not break the build. So, I decided to check whether the PR is breaking the build or not. Then, I would notify the status of the build using GitHub status API. exports.registerHook = function (data, accessToken) { return new Promise(function(resolve, reject) { var hookurl = 'http://' + process.env.HOSTNAME + '/ci/webhook'; if (data.sub === true) { hookurl += `?sub=true`; } request({ url: `https://api.github.com/repos/${data.name}/hooks`, headers: { 'User-Agent': 'Yaydoc', 'Authorization': 'token ' + crypter.decrypt(accessToken) }, method: 'POST', json: { name: "web", active: true, events: [ "push", "pull_request" ], config: { url: hookurl, content_type: "json" } } }, function(error, response, body) { if (response.statusCode !== 201) { console.log(response.statusCode + ': ' + response.statusMessage); resolve({status: false, body:body}); } else { resolve({status: true, body: body}); } }); }); }; I’ll register the webhook, when user registers the repository to yaydoc for push and pull request event. Push event will be for building documentation and hosting the documentation to the GitHub pages. Pull_request event would be for checking the build of the pull request. github.createStatus(commitId, req.body.repository.full_name, "pending", "Yaydoc is checking your build", repositoryData.accessToken, function(error, data) { if (!error) { var user = req.body.pull_request.head.label.split(":")[0]; var targetBranch = req.body.pull_request.head.label.split(":")[1]; var gitURL = `https://github.com/${user}/${req.body.repository.name}.git`; var data = { email: "admin@fossasia.org", gitUrl: gitURL, docTheme: "", debug: true, docPath: "", buildStatus: true, targetBranch: targetBranch }; generator.executeScript({}, data, function(error, generatedData) { var status, description; if(error) { status = "failure"; description = error.message; } else { status = "success"; description = generatedData.message; } github.createStatus(commitId, req.body.repository.full_name, status, description, repositoryData.accessToken, function(error, data) { if (error) { console.log(error); } else { console.log(data); } }); }); } }); When anyone opens a new PR, GitHub will send  a request to yaydoc webhook. Then, I’ll send the status to GitHub saying that “Yaydoc is checking your build” with status `pending`. After, that I’ll documentation will be generated.Then, I’ll check the exit code. If the exit code is zero,  I’ll send the status `success` otherwise I’ll send `error` status. Resources: GitHub Status API Documentation GitHub Webhook Documentation Building CI Server - GitHub Developer Guide

Continue ReadingShowing Pull Request Build Status in Yaydoc

Creating an Elementary Oscilloscope in PSLab’s Remote Framework

The last couple of blog posts explained how we could put together the versatility of ember components, the visual appeal of jqplot, the flexibility of Python Flask, and the simplicity of Python itself in order to make simple scripts for PSLab that would could be run on a server by a remote client anywhere on the web. We have also seen how callbacks could be assigned to widgets created in these scripts in order to make object oriented applications. In this blog post, we shall see how to assign a capture method to a button, and update a plot with the received data. It will also demonstrate how to use ember-lodash to perform array manipulations. Specifying the return data type in the callback success routine For a more instructive write-up on assigning callbacks, please refer to these posts . Whenever the callback assigned to a button is a function that returns an array of elements, and the target for the resultant data is a plot, the stacking order of the returned array must be specified in order to change its shape to suit the plotting library. The default return data from a capture routine (oscilloscope) is made up of separate arrays for X coordinate and Y coordinate values. Since JQplot requires [X,Y] pairs , we must specify a stacking order of ‘xy’ so that the application knows that it must convert them to pairs (using lodash/zip)  before passing the result to the plot widget. Similarly, different stacking orders for capture2, and capture4 must also be defined. Creating an action that performs necessary array manipulations and plots the received data It can be seen from the excerpt below, that if the onSuccess target for a callback is specified to be a plot in the actionDefinition object, then the stacking order is checked, and the returned data is modified accordingly Relevant excerpt from controllers/user-home.js/runButtonAction if (actionDefinition.success.type === 'update-plot') { if (actionDefinition.success.stacking === 'xy') { $.jqplot(actionDefinition.success.target, [zip(...resultValue)]).replot(); } else if (actionDefinition.success.stacking === 'xyy') { $.jqplot(actionDefinition.success.target, [zip(...[resultValue[0], resultValue[1]]), zip(...[resultValue[0], resultValue[2]])]).replot(); } else if (actionDefinition.success.stacking === 'xyyyy') { $.jqplot(actionDefinition.success.target, [zip(...[resultValue[0], resultValue[1]]), zip(...[resultValue[0], resultValue[2]]), zip(...[resultValue[0], resultValue[3]]), zip(...[resultValue[0], resultValue[4]])]).replot(); } else { $.jqplot(actionDefinition.success.target, resultValue).replot(); } }   With the above framework in place, we can add a plot with the line plt = plot(x, np.sin(x)) , and associate a button with a capture routine that will update its contents with a single line of code: button('capture1',"capture1('CH1',100,10)","update-plot",target=plt) Final Result The following script created on the pslab-remote platform makes three buttons and plots, and sets the buttons to invoke capture1, capture2, and capture4 respectively when clicked. import numpy as np x=np.linspace(0,2*np.pi,30) plt = plot(x, np.sin(x)) button('capture 1',"capture1('CH1',100,10)","update-plot",target=plt) plt2 = plot(x, np.sin(x)) button('capture 2',"capture2(50,10)","update-plot",target=plt2,stacking='xyy') plt3 = plot(x, np.sin(x)) button('capture 4',"capture4(50,10)","update-plot",target=plt3,stacking='xyyyy')                         Resources Ember components documentation : https://www.emberjs.com/api/ember/2.14/classes/Ember.Component Jqplot homepage : http://www.jqplot.com/ JQplot examples : http://www.jqplot.com/examples/line-charts.php Designing A Virtual Laboratory With PSLab Designing a Remote Laboratory with PSLab using Python Flask Framework Designing A Remote Laboratory With PSLab: execution of function…

Continue ReadingCreating an Elementary Oscilloscope in PSLab’s Remote Framework

Copying Event in Open Event API Server

The Event Copy feature of Open Event API Server provides the ability to create a xerox copy of event copies with just one API call. This feature creates the complete copy of event by copying the related objects as well like tracks, sponsors, micro-locations, etc. This API is based on the simple method where an object is first removed is from current DB session and then applied make_transient. Next step is to remove the unique identifying columns like “id”, “identifier” and generating the new identifier and saving the new record. The process seems simple but becomes a little complex when you have to generate copies of media files associated and copies of related multiple objects ensuring no orders, attendees, access_codes relations are copied. Initial Step The first thing to copy the event is first to get the event object and all related objects first if view_kwargs.get('identifier').isdigit(): identifier = 'id' event = safe_query(db, Event, identifier, view_kwargs['identifier'], 'event_'+identifier) Next thing is to get all related objects to this event. Creating the new event After removing the current event object from "db.session", It is required to remove “id” attribute and regenerate “identifier” of the event. db.session.expunge(event) # expunge the object from session make_transient(event) delattr(event, 'id') event.identifier = get_new_event_identifier() db.session.add(event) db.session.commit() Updating related object with new event The new event created has new “id” and “identifier”. This new “id” is added into foreign keys columns of the related object thus providing a relationship with the new event created. for ticket in tickets: ticket_id = ticket.id db.session.expunge(ticket) # expunge the object from session make_transient(ticket) ticket.event_id = event.id delattr(ticket, 'id') db.session.add(ticket) db.session.commit() Finishing up The last step of Updating related objects is repeated for all related objects to create the copy. Thus a new event is created with all related objects copied with the single endpoint. References How to clone a sqlalchemy object https://stackoverflow.com/questions/28871406/how-to-clone-a-sqlalchemy-db-object-with-new-primary-key

Continue ReadingCopying Event in Open Event API Server

Reset password in Open Event API Server

The addition of reset password API in the Open Event API Server enables the user to send a forgot password request to the server so that user can reset the password. Reset Password API is a two step process. The first endpoint allows you to request a token to reset the password and this token is sent to the user via email. The second process is making a PATCH request with the token and new password to set the new password on user’s account. Creating a Reset token This endpoint is not JSON spec based API. A reset token is simply a hash of random bits which is stored in a specific column of user’s table. hash_ = random.getrandbits(128) self.reset_password = str(hash_) Once the user completed the resetting of the password using the specific token, the old token is flushed and the new token is generated. These tokens are all one time use only. Requesting a Token A token can be requested on a specific endpoint  POST /v1/auth/reset-password The token with the direct link will be sent to registered email. link = make_frontend_url('/reset-password', {'token': user.reset_password}) send_email_with_action(user, PASSWORD_RESET, app_name=get_settings()['app_name'], link=link) Flow with frontend The flow is broken into 2 steps with front end is serving to the backend. The user when click on forget password will be redirected to reset password page in the front end which will call the API endpoint in the backend with an email to send the token. The email received will contain the link for the front end URL which when clicked will redirect the user to the front end page of providing the new password. The new password entered with the token will be sent to API server by the front end and reset password will complete. Updating Password Once clicked on the link in the email, the user will be asked to provide the new password. This password will be sent to the endpoint PATCH /v1/auth/reset-password. The body will receive the token and the new password to update. The user will be identified using the token and password is updated for the respective user. try: user = User.query.filter_by(reset_password=token).one() except NoResultFound: return abort( make_response(jsonify(error="User not found"), 404) ) else: user.password = password save_to_db(user) References Understand Self-service reset password https://en.wikipedia.org/wiki/Self-service_password_reset Python - getrandbits() https://docs.python.org/2/library/random.html

Continue ReadingReset password in Open Event API Server

Post Payment Charging in Open Event API Server

Order flow in Open Event API Server follows very simple process. On successfully creating a order through API server the user receives the payment-url. API server out of the box provides support for two payment gateways, stripe and paypal. The process followed is very simple, on creating the order you will receive the payment-url. The frontend will complete the payment through that url and on completion it will hit the specific endpoint which will confirm the payment and update the order status. Getting the payment-url Payment Url will be sent on successfully creating the order. There are three type of payment-modes which can be provided to Order API on creating order. The three payment modes are “free”, “stripe” and “paypal”. To get the payment-url just send the payment mode as stripe or paypal. POST Payment After payment processing through frontend, the charges endpoint will be called so that payment verification can be done on the server end. POST /v1/orders/<identifier>/charge This endpoint receives the stripe token if the payment mode is stripe else no token is required to process payment for paypal. The response will have the order details on successful verification. Implementation The implementation of charging is based on the custom data layer in Orga Server. The custom layer overrides the Base data layer and provide the custom implementation to “create_object” method thus, not using Alchemy layer. def create_object(self, data, view_kwargs): order = Order.query.filter_by(id=view_kwargs['id']).first() if order.payment_mode == 'stripe': if data.get('stripe') is None: raise UnprocessableEntity({'source': ''}, "stripe token is missing") success, response = TicketingManager.charge_stripe_order_payment(order, data['stripe']) if not success: raise UnprocessableEntity({'source': 'stripe_token_id'}, response) elif order.payment_mode == 'paypal': success, response = TicketingManager.charge_paypal_order_payment(order) if not success: raise UnprocessableEntity({'source': ''}, response) return order With the resource class as class ChargeList(ResourceList): methods = ['POST', ] schema = ChargeSchema data_layer = { 'class': ChargesLayer, 'session': db.session } Resources Paypal Payments API https://developer.paypal.com/docs/api/payments/ Flask-json-api custom layer docs http://flask-rest-jsonapi.readthedocs.io/en/latest/data_layer.html#custom-data-layer Stripe Payments API https://stripe.com/docs/charges

Continue ReadingPost Payment Charging in Open Event API Server

Implementing SUSI Linux App as a Finite State Machine

SUSI Linux app provides access to SUSI on Linux distributions on desktop as well as hardware devices like Raspberry Pi. It is a headless client that can be used to interact to SUSI via voice only. As more and more features like multiple hotword detection support and wake button support was added to SUSI Linux, the code became complex to understand and manage. A system was needed to model the app after. Finite State Machine is a perfect approach for such system. The Wikipedia definition of State Machine is “It is an abstract machine that can be in exactly one of a finite number of states at any given time. The FSM can change from one state to another in response to some external inputs; the change from one state to another is called a transition.” This means that if you can model your app into a finite number of states, you may consider using the State Machine implementation. State Machine implementation has following advantages: Better control over the working of the app. Improved Error handling by making an Error State to handle errors. States work independently which helps to modularize code in a better form. To begin with, we declare an abstract State class. This class declares all the common properties of a state and transition method. from abc import ABC, abstractclassmethod import logging class State(ABC): def __init__(self, components): self.components = components self.allowedStateTransitions = {} @abstractclassmethod def on_enter(self, payload=None): pass @abstractclassmethod def on_exit(self): pass def transition(self, state, payload=None): if not self.__can_transition(state): logging.warning("Invalid transition to State{0}".format(state)) return self.on_exit() state.on_enter(payload) def __can_transition(self, state): return state in self.allowedStateTransitions.values() We declared the on_enter() and on_exit() abstract method. These methods are executed on the entering and exiting a state respectively. The task designated for the state can be performed in the on_enter() method and it can free up resources or stop listening to callbacks in the on_exit() method. The transition method is to transition between one state to another. In a state machine, a state can transition to one of the allowed states only. Thus, we check if the transition is allowed or not before continuing it. The on_enter() and transition() methods additionally accepts a payload argument. This can be used to transfer some data to the state from the previous state. We also added the components property to the State. Components store the shared components that can be used across all the State and are needed to be initialized only once. We create a component class declaring all the components that are needed to be used by states. class Components: def __init__(self): recognizer = Recognizer() recognizer.dynamic_energy_threshold = False recognizer.energy_threshold = 1000 self.recognizer = recognizer self.microphone = Microphone() self.susi = susi self.config = json_config.connect('config.json') if self.config['hotword_engine'] == 'Snowboy': from main.hotword_engine import SnowboyDetector self.hotword_detector = SnowboyDetector() else: from main.hotword_engine import PocketSphinxDetector self.hotword_detector = PocketSphinxDetector() if self.config['wake_button'] == 'enabled': if self.config['device'] == 'RaspberryPi': from ..hardware_components import RaspberryPiWakeButton self.wake_button = RaspberryPiWakeButton() else: self.wake_button = None else: self.wake_button = None Now, we list out all the states that…

Continue ReadingImplementing SUSI Linux App as a Finite State Machine

Implementing a Custom Serializer for Yaydoc

At the crux of it, Yaydoc is comprised of a number of specialized bash scripts which perform various tasks such as generating documentation, publishing it to github pages, heroku, etc. These bash scripts also serve as the central communication portal for various technologies used in Yaydoc. The core generator is composed of several Python modules extending the sphinx documentation generator. The web Interface has been built using Node, Express, etc. Yaydoc also contains a Python package dedicated to reading configuration options from a Yaml file. Till now the options were read and then converted to strings irrespective of the actual data type, based on some simple rules. List was converted to a comma separated string.(Nested lists were not handled) Boolean values were converted to true | false respectively. None was converted to an empty string. While these simple rules were enough at that time, It was certain that a better solution would be required as the project grew in size. It was also getting tough to maintain because a lot of hard-coding was required when we wanted to convert those strings to python objects. To handle these cases, I decided to create a custom serialization format which would be simple for our use cases and easily parseable from a bash script yet can handle all edge cases. The format is mostly similar to its earlier form apart from lists where it takes heavy inspiration from the python language itself. With the new implementation, Lists would get converted to comma separated strings enclosed by square brackets. This allowed us to encode the type of the object in the string so that it can later be decoded. This handled the case of an empty list or a list with single element well. The implementation also handled nested lists. Two methods were created namely serialize and deserialize which detected the type of the corresponding object using several heuristics and applied the proper serialization or deserialization rule. def serialize(value): """ Serializes a python object to a string. None is serialized to an empty string. bool values are converted to strings True False. list or tuples are recursively handled and are comma separated. """ if value is None: return '' if isinstance(value, str): return value if isinstance(value, bool): return "true" if value else "false" if isinstance(value, (list, tuple)): return '[' + ','.join(serialize(_) for _ in value) + ']' return str(value) To deserialize we also had to handle the case of nested lists. The following snippet does that properly. def deserialize(value, numeric=True): """ Deserializes a string to a python object. Strings True False are converted to bools. `numeric` controls whether strings should be converted to ints or floats if possible. List strings are handled recursively. """ if value.lower() in ("true", "false"): return value.lower() == "true" if numeric and _is_numeric(value): return _to_numeric(value) if value.startswith('[') and value.endswith(']'): split = [] element = '' level = 0 for c in value: if c == '[': level += 1 if level != 1: element += c elif c == ']':…

Continue ReadingImplementing a Custom Serializer for Yaydoc

Using Order Endpoints in Open Event API Server

The main feature i.e., Ordering API is added into API server. These endpoints provide the ability to work with the ordering system. This API is not simple like other as it checks for the discount codes and various other things as well. The process in layman terms is very simple, first, a user must be registered or added as an attendee into Server without any order_id associated and then the attendee details will be sent to API server as a relationship. Things needed to take care: Validating the discount code and ensure it is not exhausted Calculating the total amount on the server side by applying coupon Do not calculate amount if the user is the event admin Do not use coupon if user is event admin Handling payment modes and generating payment links Ensure that default status is always pending, unless the user is event admin Creating Order Prerequisite Before initiating the order, attendee records needs to be created associated with the event. These records will not have any order_id associated with them initially. The Order API will add the relationships. Required Body Order API requires you to send event relationship and attendee records to create order_tickets Permissions Only organizers can provide custom amount and status. Others users will get their status as pending and amount will be recalculated in server. The response will reflect the calculated amount and updated status. Also to initiate any order, user must be logged in. Guest can not create any order Payment Modes There are three payment modes, free, stripe and paypal. If payment_mode is not provided then API will consider it as “free”. Discount Codes Discount code can be sent as a relationship to the API. The Server will validate the code and will act accordingly. Validating Discount Codes Discount codes are checked to ensure they are valid, first check ensures that the user is not co-organizer # Apply discount only if the user is not event admin if data.get('discount') and not has_access('is_coorganizer', event_id=data['event']): Second, check ensures that the discount code is active if not discount_code.is_active:   raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code") The third, Check ensures its validity is not expired if not (valid_from <= now <= valid_till):   raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code") Fourth Check ensure that the quantity is not exhausted if not TicketingManager.match_discount_quantity(discount_code, data['ticket_holders']):   raise UnprocessableEntity({'source': 'discount_code_id'}, 'Discount Usage Exceeded') Lastly, the fifth check ensures that event id matches with given discount associated event if discount_code.event.id != data['event'] and discount_code.user_for == TICKET:   raise UnprocessableEntity({'source': 'discount_code_id'}, "Invalid Discount Code") Calculating Order Amount The next important thing is to recalculate the order amount and it will calculated only if user is not the event admin if not has_access('is_coorganizer', **view_kwargs):   TicketingManager.calculate_update_amount(order) API Response The API response apart from general fields will provide you the payment-url depending upon the payment mode you selected. Stripe : will give payment-url as stripe Paypal: will provide the payment completing url in payment-url This all explains the flow and requirements to create an order. Order API consists…

Continue ReadingUsing Order Endpoints in Open Event API Server

Enhancing the Functionality of User Submitted Scripts in the PSLab-remote framework

The remote-lab framework of the pocket science lab enables users to access their devices remotely via the internet. Its design involves an API server built with Python-Flask and a webapp that uses EmberJS. This post is the latest in a series of blog posts which have explored and elaborated various aspect of the remote-lab such as designing the API server and testing with Postman, remote execution of function strings, automatic deployment on various domains etc. It also supports creating and submitting python scripts which will be run on the remote server, and the console output relayed to the webapp. In this post, we shall take a look at how we can extend the functionality by providing support for object oriented code in user submitted scripts. Let’s take an example of a Python script where the user wishes to create a button which when clicked will read a voltage via the API server, and display the value to the remote user. Clearly, an interpreter that only provides the console output is not enough for this task. We need the interpreter to generate an app structure that also includes callbacks for widgets such as buttons, and JSON objects are an obvious choice for relaying such a structure to the webapp. In a nutshell, we had earlier created an API method that could execute a python script and return a string output, and now we will modify this method to return a JSON encoded structure which will be parsed by the webapp in order to display an output. Let’s elaborate this with an example : Example.py print ('testing') print ('testing some changes..... ') print_('highlighted print statement')   JSON returned by the API [localhost:8000/runScriptById] , for the above script: {"Date": "Tue, 01 Aug 2017 21:39:12 GMT", "Filename": "example.py", "Id": 4, "result": [ {"name": "print", "type": "span", "value": "('testing',)"}, {"name": "print", "type": "span", "value": "('testing some changes..... ',)"}, {"class": "row well", "name": "print", "type": "span", "value": "highlighted print statement"} ], "status": true} Screenshot of the EmberJS webapp showing the output rendered with the above JSON Adding Support for Widgets In the previous section, we laid the groundwork for a flexible platform. Instead of returning a string, the webapp accepts a JSON object and parses it. We shall now add support for a clickable button which can be associated with a valid PSLab function. An elementary JS twiddle has been made by Niranjan Rajendran which will help newbies to understand how to render dynamic templates via JSON objects retrieved from APIs. The twiddle uses two API endpoints; one to retrieve the compiled JSON output, and another to act as a voltmeter method which returns a voltage value. To understand how this works in pslab-remote, consider a one line script called button.py: button('get voltage',"get_voltage('CH1')") The objective is to create a button with the text ‘get voltage’ on it , and which when clicked will run the command ‘get_voltage(‘CH1’)’ on the API server, and display the result. When this script is run on the API server, it returns…

Continue ReadingEnhancing the Functionality of User Submitted Scripts in the PSLab-remote framework