Part 6 - Networking: http¶
Let’s go for the next step.
top5 folder to
top6 and enter it. For example, with:
cp -r top5 top6 cd top6
Under Windows and unless you have a proper shell installed (Cygwin, MSYS, GitBash, …) you are probably better off using the Windows Explorer to make a copy of the directory)
The application is growing, but so far the Pyroes have been sourced directly from a list of objects. A real life web application would fetch the information from the network. And that’s why we are also going to do it … virtually.
AnPyLar includes an Http client which can fetch data from the network using ajax, but can also deliver data from local sources. To avoid having to set up a server with an API, we’ll start by using the local sourcing facility.
The additions to this:
- Adding / Editing / Deleting Pyroes (Editing was the only operation previously supported)
- Searching for Pyroes
- Doing it all with the Http client
After we are done, the final layout will look like this
Notice that we will be adding:
pyro_searchdirectory for the
The top-level changes¶
Let’s first see the modification made at the top level to accomodate change
Pyroes in mock_pyroes.py are no longer Pyro instances. The
structure is an iterable of dict entries. And this is so to reflect data that would
flow from the network (because the translation to/from Json is
straightforward) and somehow the structure of database.
In app_module.py we do:
PyroSearchComponent. This is to make it usable in our
DashboardComponent, because the pyro_search is part of no route (we could have of course imported in the package defining our
DashboardComponent. Do it so if you prefer.
servicesdirective, so that it will be started for us
And fake the network service withif True: from .mock_pyroes import Pyroes Http.serve(Pyroes, index='pyd', url='api/pyroes/')
Httpclient in AnPyLar can accept an iterable of dict entries (you tell it what the key is for sorting purposes) and hijack requests that are directed to specicic routes. In our case:
url='api/pyroes'. There will be a basic CRUD interface mapped to the POST / GET / PUT / DELETE http methods.
With that in hand we can have a look at the changes/additions to the services.
PyroService has grown quite a bit, but it is still generic. It uses the
Http client to issue the aforementioned POST / GET / PUT / DELETE calls
in order to implement the CRUD interface.
PyroSearchService is also modelled as an Observable and simply
retrieves the Pyroes which match the search criterion (which is simply to
contain the sought text string)
It is here that the Json stream from the network gets translated to Pyro
instances. Recall that we have redefined the Pyroes in
be an iterable of dict entries, because it simulates network information and
a database structure.
Changes in the Dashboard¶
There is a single change for the dashboard in
which is the addition of the
<pyro-search> tag. Just below the Pyroes,
the dashboard will display our search component. Recall that we have imported
the component in
app_module.py (and it will for sure have to define:
selector = 'pyro-search'
Changes in the Pyroes¶
PyroesComponent has acquired in the html code
pyroes_component.html) an input field with an
Add button, which will
obviously serve to add new Pyro instances.
The Python code generates an extra
x appended to the name of the Pyro,
which serves as a button to delete them (the proper styling helps)
Delete actions are served by the methods
pyro_delete which piggyback on
PyroService for the actions.
Changes in the PyroDetail¶
The component changes its behavior in a subtle manner:
- Editions to the name of a Pyro have to be saved Before it was just a matter of editing and the changes were shared across components. But now, the component will be talking to the network service and updating the name remotely.
After that it is the responsibility of other components to fetch the changes
This is obviously not very efficient, because the application could cache the changes internally and avoid hitting the network for everything each and every time. But this is just a sample to show how various components coordinate over the network service.
To implement the behavior, there are new elements, namely:
Savebutton defined in the html code which binds to the method
save(no surprise here when it comes to naming conventions)
savemethod which updates the Pyro with the method
The new PyroSearch¶
We have already seen above that the dashboard has gained a
tag which will be implemented by a new component. And we have already created
PyroSearchService which will be used by it.
Let’s create the skeleton of the component with the cli. For example:
cd top6/app anpylar-component Dashboard
And we add the code, html and css content.
The Html code
- Defines an input for which we add a bidirectional binding to an observable (which will be defined in the python code) with:
- Define a place holder with
<ul class="search-result"></ul>. It will be inside in the form of dynamically created
<li>tags that the search results will be displayed.
The Python Code
Defines bindings (Observables) for the functionality (
searchterm) which will for example be cleared during the
unloadingphase. This is meant to clear the list of search results and the input field for the search.
Defines a dynamic rendering with
ul._render(sought_pyroes, self.pyroes_)Notice that unlike in previous similar bindings, we have defined
def sought_pyroesinline. Previously we used methods of the component. Just a different way of doing it.
Applies a chain of operations to the
searchterm_observable. Let’s see itself.searchterm_ \ .debounce(300) \ .distinct_until_changed() \ .switch_map(lambda x: self.pyro_search.search(x) if x else ) \ .catch_exception(lambda e: print('search error:', e) or ) \ .subscribe(self.pyroes_)
debounce(300): if the searchterm changes within 300ms, discard the previous term. This effectively buffers the sought term for a maximum of 300ms to see if there are any changes. When typing, this is quite common.
distinct_until_changed(): if the sought term is resent but it is the same as the last one, simply discard it
switch_map(lambda x: self.pyro_search.search(x) if x else ): if the result of the previous operations is an empty search term, return directly a list of empty results (which will converted internally to an Observable). Else: ask the network service for results. The network service returns an Observable which will be observed for the results
catch_exception(lambda e: print('search error:', e) or ): If any error happens log it to the console and return a list of empty results. This is a trick, because
Noneand the logical
orcondition will alwys return the 2nd part of the expression
subscribe(self.pyroes_): And pass whatever results the chain produces to the subscriptor which is our binding (observable)
self.pyroes_. Because we have bound the rendering under
<ul>to any changes in this binding, when the results arrive,
<ul>will be re-rendered
After some groundbreaking changes, let’s see how things work
And go the browser
The start screen with the dashboard
And because this is inviting, let’s type something exciting like
py. If you
type it fast, there will be no results displayed until
300ms after you end
typing it (this is a static document … difficult to show it).
In this case all Pyroes will be shown because they are all Py ro. Add a
t to the string to form a
The list has been reduced to 3 items. Play with the search functionality as much as you like. When you are done, let’s go to our usual Pyroes list
And let’s give our new
Delete functionalities a try. We’ll be
deleting the 2nd Pyro (our friend Mopynder) to see how this is reflected in
the dashboard and we’ll be adding a new super-pyro: Molpy Sanders, who
can locate any other Pyro.
And after pressing the
Going back to the Dashboard
Our old friend Mopynder Shuresh is no longer part of the Top Pyroes. In fact, if we search for him, we’ll realize he’s no longer part of the Pyroes at all (remember we have deleted him)
Last but not least, let’s see how our editor looks like.
Up to the reader: edit and Go back. The changes won’t be there. Edit and Save and the changes will be there (in the Dashboard -if part of the Top Pyroes-, in the search results -if found-, and in the list of Pyroes)
After all this … who knows what the Tour will bring for our Pyroes