Getting started

After you installed the package you can start setting up your project. There are some parts you always need to add to your project. This tutorial will guide you through all necessary parts, will explain them and shows alternatives. At the end of this tutorial you will have a login application. The focus is to introduce the pynetworking library and not how to write a secure login application. At the end of this site you will find the complete code.

STEP 1: Creating the files

  1. Create a new python project. We will name our project ‘login_example’, but you can choose whatever you want.

  2. Create 3 files inside this project and name them client.py, server.py and interface.py.

    Note

    There are different ways for a project structure. For more information see Possible project structures. The advantage of this approach is, that it is very straightforward what each file does, and easy to maintain.

STEP 2: Defining the core functions

In this step we will define all functions that we need for a login application. We don’t really care about how we could integrate the pynetworking library. The login process should proceed like this:

  1. The client requests a login to the server
  2. The server gets all necessary data for login from the client
  3. The data is checked and if it is correct the client will be logged in
  4. The return value of the login method will indicate whether the login was successful or failed

We start with the client functions:

client.py

def login():
    pass


if __name__ == '__main__':
    login()

We now need a function that can be called, so we add the request_login function to the server.

server.py

def request_login():
    pass

We add a few functions to the client and the server that we need later on:

client.py

def get_username():
    return input("Enter username: ")


def get_password():
    return input("Enter password: ")

server.py

def is_valid_data(username, password):
    # This function just simulates a possible database access and validation
    if len(username) > 5 and len(password) > 5:
        return True
    return False

Before we connect everything with the pynetworking library, we will show you how the relevant functions login() and request_login would be implemented when everything were just client side and no network between. We leave out all the imports for simplicity.

def login():
    successfully_logged_in = request_login()
    if successfully_logged_in:
        print("Successfully logged in")
    else:
        print("Login failed")

def request_login():
    username = get_username()
    password = get_password()
    return is_valid_data(username, password)

STEP 3: Setup the ‘pynetworking’ stuff

But because there is always a network between a server and a client, we need another way of calling a server-side function. This is where the pynetworking library comes in. We now need to setup a few things in the interface. We start by importing the pynetworking library and overriding the Functions classes. These classes define which functions can be called at the server/client.

interface.py

import pynetworking as net

class ServerFunctions(net.ServerFunctions):
    """All server functions, that can be called by the client"""
    from server import request_login

class ClientFunctions(net.ClientFunctions):
    """All client functions, that can be called by the server"""
    from client import get_username, get_password

Take a look at the Functions classes page to see alternative ways of overriding these classes.

Now we need to override the classes that are responsible for communication. You do not need to know how the communication classes work. It is only important, that you don’t forget to override them and don’t change the attributes.

interface.py



class ServerCommunicator(net.ServerCommunicator):
    remote_functions = ServerFunctions
    local_functions = ClientFunctions


class ClientCommunicator(net.ClientCommunicator):

For easier use in the later code we also create an alias of the server functions. Add this to the interface.py file. This way we can later call functions directly from the ‘server’ object.

interface.py

# Put this Before all classes
server = None # prevents import error, when using: `from interface import server`

class ServerFunctions(net.ServerFunctions):
    ...

class ClientCommunicator(net.ClientCommunicator):
    ...

# Put this after all classes
server = ServerCommunicator.remote_functions

STEP 4: Connect everything

In this step we will connect everything. We start by importing the interface to both files. We can use the import interface or from interface import ... syntax. in this example we go with the second one.

client.py, server.py

from interface import server

Now we call the request_login() function inside login()

client.py

def login():
    successfully_logged_in = server.request_login()
    if successfully_logged_in:
        print("Successfully logged in")
    else:
        print("Login failed")

As you can see the only thing that changed in compare to the client only code, is that we prepended the server object to the request_login() function.

At the server things are a bit different. The problem is, that one server can be connected with multiple clients. To handle this, you will need the ClientManager again. Among other things, this class has a function get() which return the proper ClientCommunicator. Note that you don’t need to add any arguments to the call and get methods. The call will return the same object as the one previously created. The proper ClientCommunicator is the one who called this function. So the new code in server is:

server.py

def request_login():
    username = net.ClientManager().get().remote_functions.get_username()
    password = net.ClientManager().get().remote_functions.get_password()
    return is_valid_data(username, password)

Tip: If you want better code completion in your IDE you can do the following:

from interface import ClientCommunicator

client: ClientCommunicator = net.ClientManager().get()
username = client.remote_functions.get_username()
...

STEP 5: Connecting the server and client

Finally we need to start the server and the client, and connect the client to the server.

At the client-side you can use the ServerCommunicator, which we created previously in interface.py, to connect to the server. We are using the localhost address, because we only want to simulate a server. The address and port must be changed, when you move to a external server.

client.py

if __name__ == '__main__':
    address = ("127.0.0.1", 5000)
    interface.ServerCommunicator.connect(address)

When the client is connected we can login, do more stuff and finally close the connection.

client.py

if __name__ == '__main__':
    address = ("127.0.0.1", 5000)
    interface.ServerCommunicator.connect(address)
    login()
    # Do more stuff
    interface.ServerCommunicator.close_connection()

At the server side we need something that allows new connections and stores them. For this purpose there is the ClientManager class. The server ip address should be either the localhost or empty. As port you can choose any high number. The ClientManager takes the address and the overwritten ClientCommunicator class as arguments. We then start it and wait for 20 seconds. After 20 seconds we close and stop everything. Normally you would run the server forever.

server.py

if __name__ == '__main__':
    address = ("127.0.0.1", 5000)
    client_manager = net.ClientManager(address, interface.ClientCommunicator)
    client_manager.start()
    time.sleep(20)
    client_manager.stop_listening()
    client_manager.stop_connections()

CONCLUSION

Congratulations you finished your first program that communicates over the network with help of the pynetworking library. Now you can start your server, then start the client. At the client you are prompted in the console to enter your username and password. Enter any random value and see if you were successfully logged in. Some parts of the code could look pretty complicated. The good thing is, you don’t have to understand what is going on, you just need to know how to set everything up. (If you are really interested in the stuff behind the scenes you are welcome to have a look at the Github repository ). The code that you have created can be pretty much copy and pasted to every new project. You only need to change the function includes in interface.py.

WHAT IS NEXT?

You can experiment with this example code by adding own functions. Try for example creating a function with parameters and see if it still works. You can also try what happens when an error is risen. The cool thing is, that you can code like you are really calling these functions, but keep in mind everything works over a network and is transmitted over a tcp-connection. When you are finished with your experiments you can start your own project. When starting a new project this checklist for new projects will help you to add everything necessary. You can also checkout other example at the Github repository.

FINAL CODE

Your final code should look something like this:

client.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import interface
from interface import server


def login():
    successfully_logged_in = server.request_login()
    if successfully_logged_in:
        print("Successfully logged in")
    else:
        print("Login failed")


def get_username():
    return input("Enter username: ")


def get_password():
    return input("Enter password: ")


if __name__ == '__main__':
    address = ("127.0.0.1", 5000)
    interface.ServerCommunicator.connect(address)
    login()
    # Do more stuff
    interface.ServerCommunicator.close_connection()

interface.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import pynetworking as net

server = None


class ServerFunctions(net.ServerFunctions):
    """All server functions, that can be called by the client"""
    from server import request_login


class ClientFunctions(net.ClientFunctions):
    """All client functions, that can be called by the server"""
    from client import get_username, get_password


class ServerCommunicator(net.ServerCommunicator):
    remote_functions = ServerFunctions
    local_functions = ClientFunctions


class ClientCommunicator(net.ClientCommunicator):
    remote_functions = ClientFunctions
    local_functions = ServerFunctions


server = ServerCommunicator.remote_functions

server.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import interface
import pynetworking as net
import time


def request_login():
    username = net.ClientManager().get().remote_functions.get_username()
    password = net.ClientManager().get().remote_functions.get_password()
    return is_valid_data(username, password)


def is_valid_data(username, password):
    # This function just simulates a possible database access and validation
    if len(username) > 4 and len(password) > 4:
        return True
    return False


if __name__ == '__main__':
    address = ("127.0.0.1", 5000)
    client_manager = net.ClientManager(address, interface.ClientCommunicator)
    client_manager.start()
    time.sleep(20)
    client_manager.stop_listening()
    client_manager.stop_connections()