Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update documents and prep for pypi #79

Merged
merged 5 commits into from Aug 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Expand Up @@ -66,6 +66,7 @@ instance/

# Sphinx documentation
docs/_build/
docs-src/source/_build/

# PyBuilder
target/
Expand Down Expand Up @@ -107,6 +108,15 @@ ENV/
/.idea/*
*.iml

# VSCode
.vscode

# vim
*.swp

# osx
.DS_Store

# Pyre Typecheck
/.pyre_configuration
/.pyre
21 changes: 10 additions & 11 deletions README.md
@@ -1,12 +1,12 @@
Pyrlang - Erlang node in Python
===============================

This is a drop-in Erlang node implementation in Python 3.5, implementing
a network Erlang node protocol. It was designed to allow interoperation between
existing Python projects and BEAM languages: Erlang, Elixir, Alpaca, Luaerl,
LFE, Clojerl and such.
This is a drop-in Erlang node implementation in Python 3, implementing
a network Erlang node protocol. It was designed to allow interoperation between
existing Python projects and BEAM languages: Erlang, Elixir, Alpaca, Luaerl,
LFE, Clojerl and such.

With just a few lines of startup code your Python program becomes an Erlang
With just a few lines of startup code your Python program becomes an Erlang
network node, participating in the Erlang cluster.


Expand All @@ -26,14 +26,13 @@ For those times when you absolutely need assistance and email is too slow, here'
Features
--------

* Switchable async engine backends: Gevent and Asyncio.
* Erlang distribution protocol for Erlang versions 19, 20, and 21
* Registry of Python 'processes', which have an Erlang-compatible process
* Erlang distribution protocol for Erlang versions 21, 22 and 23
* Registry of Python 'processes', which have an Erlang-compatible process
identifier and an optional registered name
* Send and receive messages locally and remotely by pid or name
* Can link and monitor Erlang from Python and Python from Erlang
* `net_adm` pings supported
* RPC calls to Python (Erlang `rpc:call`). Exceptions are propagated
* RPC calls to Python (Erlang `rpc:call`). Exceptions are propagated
from Python back to Erlang;
* `Pyrlang.GenServer` descendant from `Pyrlang.Process` allows accepting
generic calls mapped to Python class members
* `pyrlang.gen.server.GenServer` descendant from `pyrlang.process.Process`
allows accepting generic calls mapped to Python class members
19 changes: 11 additions & 8 deletions docs-src/source/build-library.rst
@@ -1,10 +1,17 @@
Building the Library
====================

To run pyrlang you need to have term installed. Term is an external package
that pyrlang uses to encode / decode Erlangs `external term fomrat
<http://erlang
.org/doc/apps/erts/erl_ext_dist.html>`__. Install it with:
If you just want to get going just install pyralng using :code:`pip` the dependancy
:code:`pyrlang-term` will be installed automatically

.. code-block:: console

pip3 install pyrlang


To install pyrlang from source you need to install term manually. Term is an external package
that pyrlang uses to encode / decode Erlangs
`external term fomrat <http://erlang .org/doc/apps/erts/erl_ext_dist.html>`__. Install it with:

.. code-block:: console

Expand All @@ -19,10 +26,6 @@ To install Pyrlang you checkout the project and install it:
pip install .


Install requirements via: :code:`pip3 install -r requirements.txt`, this will
also
pull requirements for Sphinx documentation generator (not needed if you only
use the library).

To build the docs you need to install Sphinx and some other pacakges you can
do that with the requirements file:
Expand Down
75 changes: 33 additions & 42 deletions docs-src/source/cookbook.rst
Expand Up @@ -9,33 +9,26 @@ Start the Node

.. code-block:: python

from pyrlang import Node, Atom, GeventEngine # or AsyncioEngine
from pyrlang import Node
from term import Atom

def main():
eng = GeventEngine() # or AsyncioEngine
node = Node(node_name="py@127.0.0.1", cookie="COOKIE", engine=eng)
node = Node(node_name="py@127.0.0.1", cookie="COOKIE")

fake_pid = node.register_new_process()

# To be able to send to Erlang shell by name first give it a
# registered name: `erlang:register(shell, self()).`
# To see an incoming message in shell: `flush().`
node.send(sender=fake_pid,
receiver=(Atom('erl@127.0.0.1'), Atom('shell')),
message=Atom('hello'))
node.send_nowait(sender=fake_pid,
receiver=(Atom('erl@127.0.0.1'), Atom('shell')),
message=Atom('hello'))

eng.run_forever()

if __name__ == "__main__":
main()

Here ``event_engine`` is a pluggable adapter which allows Pyrlang to run both
with gevent (:py:class:`~pyrlang.Engine.gevent_engine.GeventEngine`)
and asyncio-driven (:py:class:`~pyrlang.Engine.asyncio_engine.AsyncioEngine`)
event loops. Pyrlang in this case performs mostly
protocols handling, while event engine will open connections, start tasks
and sleep asynchronously.


Connect nodes
-------------
Expand Down Expand Up @@ -94,7 +87,7 @@ function which will transparently pass all args to the ``print`` operator.

.. code-block:: erlang

rpc:call('py@127.0.0.1', 'Pyrlang.logger', 'tty', ["Hello World"]).
rpc:call('py@127.0.0.1', time, time, []).

.. note::
You do not need to import module to perform the call, this will be done by Rex.
Expand All @@ -111,7 +104,7 @@ value from Python.
.. code-block:: erlang

gen_server:call({rex, 'py@127.0.0.1'},
{call, 'Pyrlang.logger', tty, ["Hello"], self()}).
{call, time, time, [], self()}).


Send from Python locally
Expand Down Expand Up @@ -170,8 +163,8 @@ Send to a Python object
-----------------------

A python object inherited from :py:class:`~pyrlang.process.Process` will be
a Greenlet (i.e. running in parallel with the rest of the system).
A process is able to register itself (optional) with a name and handle
an async task, which adds to the event loop and can coexists with other
tasks. A process is able to register itself (optional) with a name and handle
incoming messages.

Messages sent to a pid or name will be automatically routed to such a
Expand All @@ -182,7 +175,8 @@ constantly call ``self.handle_inbox()`` so you can check the messages yourself.

class MyProcess(Process):
def __init__(self) -> None:
Process.__init__(self)
super().__init__()
node = self.node_db.get()
node.register_name(self, Atom('my_process')) # optional

def handle_one_inbox_message(self, msg):
Expand Down Expand Up @@ -245,7 +239,7 @@ Gen_server-like Processes
-------------------------

To have a :py:class:`~pyrlang.process.Process` descendant which responds to
``gen_server:call``, inherit your class from :py:class:`~pyrlang.gen_server.GenServer`.
``gen_server:call``, inherit your class from :py:class:`~pyrlang.gen.server.GenServer`.
When calling ``GenServer`` constructor in your ``__init__`` specify an
additional parameter ``accepted_calls`` which is a list of strings.

Expand All @@ -254,35 +248,32 @@ and their result will be transparently 'replied' back to the caller.

.. code-block:: python

class MyProcess(GenServer):
def __init__(self) -> None:
GenServer.__init__(self, accepted_calls=['hello'])
from pyrlang.gen.decorators import call
class MyGenServer(GenServer):
def __init__(self):
super().__init__()
node = self.node_db.get()
node.register_name(self, Atom('my_gen_server')) # optional

def hello(self):
@call(1, lambda msg: msg == 'hello')
def hello(self, msg):
return self.pid_

When you perform a ``gen_server:call`` with an atom, the atom becomes Python
method name:

.. code-block:: python
@call(2, lambda msg: True)
def catch_all_call(self, msg):
ret = "I don't understand"
return ret

# 1> gen_server:call(Pid, my_method)
# becomes a call to
class MyClass:
def my_method(self):
pass # return None -> atom 'undefined' in Erlang
The decorator will be used by the meta class so that when a call comes it matches
all functions that have ``call`` decorator. The first number is the priority, just
as in erlang, where the first function clause that matches is the one that gets the
call.

When you perform a ``gen_server:call`` with a tuple where first element is an
atom, the atom becomes Python method name, and following tuple elements become
python **\*args**.

.. code-block:: python
.. code-block:: erlang

# 1> gen_server:call(Pid, {my_method, 1000, "hello"})
# becomes a call to
class MyClass:
def my_method(self, i: int, s: bytes):
pass # return None -> atom 'undefined' in Erlang
Server = {my_gen_server, 'py@127.0.0.1'}.
gen_server:call(Server, hello).
gen_server:call(Server, somethingelse).


Linking/Monitoring from Erlang to Python
Expand Down
3 changes: 0 additions & 3 deletions requirements.txt
@@ -1,6 +1,3 @@
# Pyrlang Term library
git+git://github.com/Pyrlang/Term@master#egg=term

# Documentation requirements
Jinja2>=2.10.1
MarkupSafe>=1.0
Expand Down
22 changes: 15 additions & 7 deletions setup.py
@@ -1,13 +1,21 @@
from setuptools import setup, find_packages


with open("README.md", "r", encoding='utf-8') as fp:
LONG_DESCRIPTION = fp.read()


setup(name='pyrlang',
version='0.9',
description='Erlang Node implemented in Python 3.5/Gevent/Asyncio',
version='0.11-beta1',
description='Erlang Node implemented in Python using asyncio',
long_description=LONG_DESCRIPTION,
long_description_content_type="text/markdown",
author='Erlang Solutions Ltd and S2HC Sweden AB',
author_email='dmytro.lytovchenko@gmail.com,pyrlang@s2hc.com',
url='http://pyrlang.readthedocs.io/',
url='https://github.com/Pyrlang/Pyrlang/',
packages=find_packages(),
# The library requires either asyncio or Gevent, you can relax this
# dependency if Gevent is not desired
install_requires=[]
)
install_requires=["pyrlang-term>=1.2"],
classifiers=[
"Programming Language :: Python :: 3.7",
"License :: OSI Approved :: Apache Software License"
])