diff --git a/.travis.yml b/.travis.yml index 329a68c..7c7d90d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,9 @@ os: - linux smalltalk: - - Pharo-7.0 - - Pharo64-7.0 - Pharo64-8.0 + - Pharo64-9.0 + python: - 3.6 @@ -33,4 +33,4 @@ matrix: # - script: pipenv run python -m unittest discover tests # os: linux # python: 3.7 - \ No newline at end of file + diff --git a/README.md b/README.md index 30c4864..3de01fd 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ Depending on the internet connection, the script could take a couple of minutes A more in depth guide is present on the official website of this project https://objectprofile.github.io/PythonBridge/. +[Video installation for VisualWorks](https://vimeo.com/401196404) + # Simple test Evaluating the following code in a playground should return `3`: diff --git a/bridge_hooks.py b/bridge_hooks.py index 216f09e..773084d 100644 --- a/bridge_hooks.py +++ b/bridge_hooks.py @@ -1,7 +1,6 @@ from flask import Flask, request import http.client import argparse -import threading import sys import traceback from PythonBridge import bridge_globals diff --git a/docs/index.md b/docs/index.md index de375bd..d5a2851 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,6 +15,8 @@ PythonBridge gives Smalltalk developers the capability of interacting and reusin [Get started now](#getting-started){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 } [View it on GitHub](https://github.com/ObjectProfile/PythonBridge){: .btn .btn-green .fs-5 .mb-4 .mb-md-0 } + + --- ## Getting started in Pharo @@ -58,4 +60,4 @@ PBApplication do: [ ## Bridges based on PythonBridge -- [KerasBridge](https://objectprofile.github.io/KerasBridge) \ No newline at end of file +- [KerasBridge](https://objectprofile.github.io/KerasBridge) diff --git a/docs/pages/faq.md b/docs/pages/faq.md index b4148da..5caecf9 100644 --- a/docs/pages/faq.md +++ b/docs/pages/faq.md @@ -8,4 +8,4 @@ nav_order: 10 # Configuration {: .no_toc } -Section under work... \ No newline at end of file +Section under construction diff --git a/docs/pages/pharo-installation.md b/docs/pages/pharo-installation.md index 4c0abe3..fbab666 100644 --- a/docs/pages/pharo-installation.md +++ b/docs/pages/pharo-installation.md @@ -42,11 +42,11 @@ We suggest using `sudo apt-get install python3.6`, this should avoid most of the ### OSX We suggest to NOT use Homebrew, because we have experienced several issues handling the rest of the dependencies. Instead, we suggest using the installer provided in the Python webpage [https://www.python.org/downloads/release/python-368/](https://www.python.org/downloads/release/python-368/). -To verify python installed correctly just run `python3 --version` and you should get `Python 3.6.8`. +To verify python installed correctly just run `python3 --version` and you should get `Python 3.6.8`. If you have a more recent version, then it is all fine. ## Install Pipenv -To install Pipenv just use the following Pip instruction `pip install pipenv`, though depending on your python installation you may need to call it with `sudo`. This may happen if you are using Ubuntu or the OSX Homebrew python installation. If the command `pip` is not found, use `pip3` instead. +To install Pipenv just use the following Pip instruction `pip install pipenv` or `pip3 install pipenv` in case you have `pip3` installed and not `pip`, though depending on your python installation you may need to call it with `sudo`. This may happen if you are using Ubuntu or the OSX Homebrew python installation. If the command `pip` is not found, use `pip3` instead. To verify if you have pipenv just run in a terminal `pipenv --version`, it should print something like `pipenv, version 2018.11.26`. We strongly suggest you to upgrade your pipenv version if it is older than 2018.11.26, because it has important bugfixes and performance improvements. To upgrade it just run `pip install pipenv --upgrade`. @@ -105,7 +105,12 @@ which pipenv ``` Then you must set this path in PythonBridge by running the following script in a Playground: ```smalltalk -PBPipenvPyStrategy pipEnvPath: '/PATH/TO/PIPENV/BINARY' +PBPharoPipenvProcess pipenvPath: '/PATH/TO/PIPENV/BINARY' +``` + +An example of this command could be: +```Smalltalk +PBPharoPipenvProcess pipenvPath: '/Library/Frameworks/Python.framework/Versions/3.7/bin/pipenv' ``` ### Executing PythonBridge in Windows @@ -151,3 +156,13 @@ PBApplication do: [ ] ``` To try this code snippet using KerasBridge replace `PBApplication` -> `Keras` and `PBCF` -> `KCF`. + +## Changing communication protocol +By default, the communication protocol for the bridge is MessagePack through native sockets. To manage which protocol to use we run: +``` +PBPlatform current setSocketMessageBroker. "Set MessagePack over socket protocol" +``` +or +``` +PBPlatform current setHttpMessageBroker. "Set JSON over HTTP protocol" +``` diff --git a/docs/pages/pythonCommands.md b/docs/pages/pythonCommands.md index 774f142..1c037dd 100644 --- a/docs/pages/pythonCommands.md +++ b/docs/pages/pythonCommands.md @@ -25,7 +25,7 @@ The python statements are created in Pharo by using [Python3Generator](https://g #### Attribute accessing Python objects instance variables and methods are accessible in Python by operator `.`. From Pharo, this operator is defined using `=>` and allow accessing instance variables and invoking methods. ```smalltalk -foo asP3GI => #a "foo.a" +#foo asP3GI => #a "foo.a" ``` #### Method and function invoking @@ -33,12 +33,12 @@ Python functions and methods are called by using `callWith:` and `callWith:with: The `callWith:` method receive as argument a collection of objects which corresponds to the positional arguments of the call. ```smalltalk -foo asP3GI callWith: #(1 2 3) "foo(1,2,3)" +#foo asP3GI callWith: #(1 2 3) "foo(1,2,3)" ``` The `callWith:with:` method receives as first agument a collection of positional arguments and, as second argument, a dictionary of named arguments. ```smalltalk -foo asP3GI +#foo asP3GI callWith: #(1 2 3) with: { #nameArg1 -> 'bar'. @@ -52,14 +52,87 @@ Assignments in python allow you to store objects in globals, temporary variables Assigning the temporary variable `w` with the array `[1,2,3]`. ```smalltalk -foo asP3GI <- #(1 2 3) "foo = [1,2,3]" +#foo asP3GI <- #(1 2 3) "foo = [1,2,3]" ``` Assigning instance variable `a` from object in variable `foo` with the string `'bar'`. ```smalltalk -(foo asP3GI => #a) <- 'bar' "foo.a = 'bar'" +(#foo asP3GI => #a) <- 'bar' "foo.a = 'bar'" ``` + +## Importing an External Module + +PythonBridge can interact with any python modules. Consider the `numpy` Python module, which essential in scientific computing. We will perform the following Python instruction within Smalltalk: `numpy.arange(15).reshape(3, 5)` + +First, we need to augment the Pipenv environment with the `numpy` module. You can simply do it by modifying the `Pipfile` by adding the line `numpy = "*"`. The complete `Pipfile` should be: + +``` +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +flask = "*" +requests = "*" +msgpack = "*" +numpy = "*" + +[requires] +python_version = "3" +``` + +Once the `Pipfile` file modified, the pipenv environment must be upgraded, which can be done using the command line: +``` +pipenv update +``` + +You should see something like: +``` +➜ PythonBridge git:(master) ✗ pipenv update +Running $ pipenv lock then $ pipenv sync. +Locking [dev-packages] dependencies… +Locking [packages] dependencies… +✔ Success! +Updated Pipfile.lock (deca6d)! +Installing dependencies from Pipfile.lock (deca6d)… + 🐍 ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 14/14 — 00:00:07 +To activate this project's virtualenv, run pipenv shell. +Alternatively, run a command inside the virtualenv with pipenv run. +All dependencies are now up-to-date! +``` + +Numpy is now installed in your Pipenv environment, and it is ready to be used from Smalltalk. You can do + +```Smalltalk +PBApplication do: [ + "We import the numpy module" + PBCF << (P3GImport moduleIdentifier: #numpy asP3GI). + + "We construct the Python expression numpy.arange(15).reshape(3, 5)" + r := P3GCall target: (#numpy asP3GI => #arange) positionalArguments: #(15). + PBCF << (P3GCall target: r => #reshape positionalArguments: #(3 5)). + s := PBCF send waitForValue. + + "The variable s points to a Smalltalk wrapper of the Python object" + "We simply execute the Python expression str(s) to obtain a string that represents the numpy object" + PBCF << (P3GCall target: #str asP3GI positionalArguments: (Array with: s)). + PBCF send waitForValue. + ]. +``` + +Printing this expression will result in: +```Python +[[ 0 1 2 3 4] + [ 5 6 7 8 9] + [10 11 12 13 14]] +``` + +This small example shows how you can augment the Pipenv environment with a new Python module, and how you can access it from Smalltalk. + ## Command Factory Each PythonBridge extension defines its own CommandFactory as a global object. In the case of PythonBridge it's called `PBCF` and in KerasBridge is called `KCF`. diff --git a/docs/pages/tutorial01.md b/docs/pages/tutorial01.md new file mode 100644 index 0000000..bfd80fe --- /dev/null +++ b/docs/pages/tutorial01.md @@ -0,0 +1,194 @@ +--- +layout: default +permalink: /pages/tutorial01 +title: Tutorial: Using OpenCV from Smalltalk +nav_order: 5 +--- + +# Tutorial: Using OpenCV from Smalltalk +{: .no_toc } + +## Table of contents +{: .no_toc .text-delta } + +1. TOC +{:toc} + +This tutorial is a gentle introduction to PythonBridge. We use OpenCV as the running example. The tutorial assumes that you have PythonBridge correctly installed in your Smalltalk environment and have Python and Pipenv installed. If this is not the case, then you should follow the instruction given in [https://objectprofile.github.io/PythonBridge/](https://objectprofile.github.io/PythonBridge/). + +We produced a video describing this tutorial, available [online](https://vimeo.com/409987849). + +## How do I know if Python and Pipenv are correctly installed? +If you have PythonBridge installed, and you do not know if you Python environment is correctly setup, the easy way to check this is evaluate the following expression: + +```Smalltalk +PBApplication do: [ + PBCF << (P3GBinaryOperator new left: 1; right: 2; operator: $+; yourself). + PBCF send waitForValue ] +``` + +If it returns `3`, then your Python setting is properly installed and you can continue the tutorial. If it does not return 3, but raises en error, then you should revise your Python installation. In case you a VisualWorks user, do not forget to manually set the path to `Pipenv.exe` as specified [here](https://objectprofile.github.io/PythonBridge/pages/vw-installation). + +## Installing the OpenCV Python module + +Before jumping into Smalltalk, we need to make sure that the OpenCV Python library is installed in your Pipenv environment before continuing. You need to open a terminal that points to the `PythonBridge` folder, next to your image. Edit the file called `Pipenv` and add the line `opencv-python = "*"` in the `[packages]` section. You `Pipfile` should be very similar to: + +``` +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +flask = "*" +requests = "*" +msgpack = "*" +opencv-python = "*" + +[requires] +python_version = "3" +``` + +Note that you should leave the packages `flask`, `requests`, `msgpack` as they are used by the PythonBridge itself to operate. +Now that we edited the `Pipenv` file, you should then update your pipenv environment. To do so, simply execute the `pipenv update` expression. You should see something like: + +``` +➜ PythonBridge git:(master) ✗ pipenv update +Running $ pipenv lock then $ pipenv sync. +Locking [dev-packages] dependencies… +Locking [packages] dependencies… +✔ Success! +Updated Pipfile.lock (bc0ce1)! +Installing dependencies from Pipfile.lock (bc0ce1)… + 🐍 ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 14/14 — 00:00:10 +To activate this project's virtualenv, run pipenv shell. +Alternatively, run a command inside the virtualenv with pipenv run. +All dependencies are now up-to-date! +``` + +You can now close your terminal and stay within the confortable world of Smalltalk. We will not have to use the terminal anymore. + +## Your first OpenCV script + +We will first do some scripting to illustrate the essence of PythonBridge. We will then move into defining a proper application, without having the exposition to Python. + +This tutorial will simply open an image using OpenCV. It is highly inspired from the [OpenCV online tutorial](https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_gui/py_image_display/py_image_display.html). + +PythonBridge needs to launch a Python virtual machine and establish a communication channel to it. This is simply done using: +```Smalltalk +PBApplication start. +``` + +Starting the Python Virtual Machine (VM) may take a few seconds in some cases. The python code we will execute is the following: + +```Python +import cv2 +img = cv2.imread('/Users/alexandrebergel/Desktop/iss.jpg',0) +cv2.imshow('image',img) +cv2.waitKey(0) +cv2.destroyAllWindows() +``` + +I am here assuming spectacular picture from the [ISS](https://www.esa.int/Science_Exploration/Human_and_Robotic_Exploration/International_Space_Station/Where_is_the_International_Space_Station) is available on my desktop, under the name `iss.jpg`. + +We first need to import OpenCV, using: +```Smalltalk +PBCF sendAndWait: #cv2 asP3GI import. +``` + +We load the picture using OpenCV: + +```Smalltalk +ref := '/Users/alexandrebergel/Desktop/iss.jpg'. +img := PBCF sendAndWait: (#cv2 asP3GI => #imread callWith: (Array with: filename)). +``` + +If you print (in Smalltalk), the variable `img` you should see `a ndarray (Proxy)`. OpenCV handles the picture as a numpy structure. Within Smalltalk, we only see a proxy since the image is living within the Python World. Note that we are using the `sendAndWait:` instruction as we wait for the completion of the Python import. + +We can now open the image using: + +```Smalltalk +PBCF send: (#cv2 asP3GI => #imshow callWith: (Array with: 'image' with: img)). +PBCF send: (#cv2 asP3GI => #waitKey callWith: (Array with: 0)). +PBCF send: (#cv2 asP3GI => #destroyAllWindows callWith: (Array new)). +``` + +Note that we simply use `send:` to send the Python commands, as there is no need to wait for their completion. + +We can now close the Python connection: +```Smalltalk +PBApplication stop. +``` + +In theory, we should not need to shutdown the Python VM, however, due to a limitation of OpenCV in the way it handles the windows, it is simpler to do so. + +## Applying transformation to an image + +We can apply the OpenCV `cvtColor` [operation](https://docs.opencv.org/2.4/modules/imgproc/doc/miscellaneous_transformations.html#cvtcolor). We will turn a colored picture into gray using an adequate [transformation](https://docs.opencv.org/master/de/d25/imgproc_color_conversions.html#color_convert_rgb_gray). + +The complete script is: + +```Smalltalk +PBApplication start. +filename := '/Users/alexandrebergel/Desktop/iss.jpg'. +PBCF sendAndWait: #cv2 asP3GI import. +img := PBCF sendAndWait: (#cv2 asP3GI => #imread callWith: (Array with: filename)). +gray := PBCF sendAndWait: (#cv2 asP3GI => #cvtColor callWith: (Array with: img with: #cv2 asP3GI => #COLOR_BGR2GRAY)). + +PBCF send: (#cv2 asP3GI => #imshow callWith: (Array with: 'image' with: gray)). +PBCF send: (#cv2 asP3GI => #waitKey callWith: (Array with: 0)). +PBCF send: (#cv2 asP3GI => #destroyAllWindows callWith: (Array new)). + +PBApplication stop. +``` + +## Turning your script into an application + +Obviously, you do not wish to directly face the Python scripting instruction. You can easily turn the script into a small class, titled `ImageViewer`, to completely hide the Python instructions: + +```Smalltalk +Object subclass: #ImageViewer + instanceVariableNames: 'filename' + classVariableNames: '' + package: 'OpenCVExample' +``` + +An accessor to set the image filename: +```Smalltalk +ImageViewer>>filename: aString + filename := aString +``` + +The `show` methods to display the image: + +```Smalltalk +ImageViewer>>show + | img | + PBApplication start. + PBCF sendAndWait: #cv2 asP3GI import. + img := PBCF sendAndWait: (#cv2 asP3GI => #imread callWith: (Array with: filename)). + PBCF send: (#cv2 asP3GI => #imshow callWith: (Array with: 'image' with: img)). + PBCF send: (#cv2 asP3GI => #waitKey callWith: (Array with: 0)). + PBCF send: (#cv2 asP3GI => #destroyAllWindows callWith: (Array new)). +``` + +And to close the image: +```Smalltalk +ImageViewer>>delete + PBApplication stop +``` + +You can now simply execute the following Smalltalk script, line by line: +```Smalltalk +v := ImageViewer new filename: '/Users/alexandrebergel/Desktop/iss.jpg'. +v show. +v delete +``` + +## What have we seen? + +This short tutorial covers the basic functionalities of our bridge. We used an external Python modules within Smalltalk, and called a few Python functions within Pharo. We then wrapped our application into Smalltalk code to completely hide the Python code construction. + +Have fun! diff --git a/docs/pages/vw-installation.md b/docs/pages/vw-installation.md index 394d98c..4229fad 100644 --- a/docs/pages/vw-installation.md +++ b/docs/pages/vw-installation.md @@ -60,7 +60,7 @@ Currently PythonBridge version for VisualWorks is on closed beta. Therefore, we dir := Dialog requestDirectoryName: 'Choose the PythonBridge parcels directory'. dir isEmpty ifTrue: [^ self]. dir:= dir, (String with: Filename separator). -#('JSONReader' 'Sport' 'Swazoo' 'VwPharoPlatform' 'P3Generator' 'PythonBridgeBundle') do: [:fn | | file | +#('HTTP' 'JSONReader' 'Sport' 'Swazoo' 'MessagePack-All' 'VwPharoPlatform' 'P3Generator' 'Python-Bridge-VWExtensions' 'PythonBridge' 'PythonBridge-VwPlatform' 'PythonBridge-VwMsgPack') do: [:fn | | file | file := dir, fn, '.pcl'. file asFilename exists ifFalse: [self error: 'Missing parcel!', file asString]. Parcel loadParcelFrom: file asFilename @@ -91,6 +91,10 @@ To create the link you need to execute the following in a shell with ADMINISTRAT mklink /D PythonBridge . ``` +## Update the path of Pipenv +The method `PBVwProcess>>start` contains a hardcoded path to `pipenv.exe`. Update it to the correct path. + + ## Test your installation @@ -120,4 +124,4 @@ In case you wish to remove the pipenv environment run: ``` cd c:\PATH\TO\PYTHON\BRIDGE\REPOSITORY pipenv --rm -``` \ No newline at end of file +``` diff --git a/flask_platform.py b/flask_platform.py index a8e0332..c2925dc 100644 --- a/flask_platform.py +++ b/flask_platform.py @@ -5,6 +5,7 @@ from PythonBridge import bridge_globals, json_encoder, bridge_utils import sys import logging +import requests class FlaskMsgService: @@ -18,6 +19,8 @@ def __init__(self, port, pharo_port, feed_callback): self.feed_callback = feed_callback self.app = Flask('PythonBridge') self.app.use_reloader=False + self.session = requests.Session() + self.session.trust_env = True @self.app.route("/ENQUEUE", methods=["POST"]) def eval_expression(): @@ -56,11 +59,11 @@ def send_async_message(self, msg): def send_sync_message(self, msg): msg['__sync'] = bridge_utils.random_str() bridge_globals.logger.log("SYNC_MSG: " + json.dumps(msg)) - conn = http.client.HTTPConnection("localhost", str(self.pharo_port)) - conn.request("POST", "/" + msg["type"], json.dumps(msg), { - "Content-type": "application/json", - "Accept": "text/plain"}) - response = str(conn.getresponse().read().decode()) + response = self.session.post( + 'http://localhost:' + str(self.pharo_port) + '/' + msg['type'], + data=json.dumps(msg), + headers={'content-type': 'application/json'}, + allow_redirects=True).content.decode('utf-8') bridge_globals.logger.log("SYNC_ANS: " + response) return json.loads(response) diff --git a/msgpack_socket_platform.py b/msgpack_socket_platform.py index 30beca8..7982f70 100644 --- a/msgpack_socket_platform.py +++ b/msgpack_socket_platform.py @@ -3,6 +3,7 @@ import _thread import threading import time +import sys from PythonBridge import bridge_globals, stoppable_thread, msgpack_serializer from uuid import uuid1 @@ -28,12 +29,20 @@ def set_handler(self, msg_type, async_handler): def prim_handle(self): try: + bridge_globals.logger.log("loop func") data = self.client.recv(2048) - self.unpacker.feed(data) - for msg in self.unpacker: - self.prim_handle_msg(msg) + if len(data) == 0: + time.sleep(0.005) + else: + self.unpacker.feed(data) + for msg in self.unpacker: + bridge_globals.logger.log("prim handle message") + self.prim_handle_msg(msg) except OSError: - self.thread.stop() + bridge_globals.logger.log("OSError: " + str(err)) + self.stop() + sys.exit() + exit(-1) except Exception as err: bridge_globals.logger.log("ERROR message: " + str(err)) diff --git a/python_bridge.py b/python_bridge.py index 38e87e3..e9b1847 100644 --- a/python_bridge.py +++ b/python_bridge.py @@ -127,10 +127,8 @@ def run_bridge(): bridge_globals.pharoPort = args["pharo"] if args["log"]: - print("YES LOG") bridge_globals.logger = Logger() else: - print("NO LOG") bridge_globals.logger = NoLogger() bridge_globals.pyPort = args["port"] bridge_globals.globalCommandList = PythonCommandList() diff --git a/src/PythonBridge-Pharo/PBPharoPlatform.class.st b/src/PythonBridge-Pharo/PBPharoPlatform.class.st index fdd2ffa..024332c 100644 --- a/src/PythonBridge-Pharo/PBPharoPlatform.class.st +++ b/src/PythonBridge-Pharo/PBPharoPlatform.class.st @@ -94,7 +94,7 @@ PBPharoPlatform >> folderForApplication: application [ { #category : #utils } PBPharoPlatform >> forceInstallEnvironmentForApp: application [ | proc | - self assert: PBPharoPipenvProcess pipenvPath isEmptyOrNil not. + self assert: PBPharoPipenvProcess pipenvPath isEmptyOrNil not description: 'pipenv is apparently not accessible at a standard location. Please, have a look at the Troubleshooting section of https://objectprofile.github.io/PythonBridge/pages/pharo-installation'. proc := OSSUnixSubprocess new command: '/bin/bash'; addAllEnvVariablesFromParentWithoutOverride; diff --git a/src/PythonBridge-Pharo/SocketStream.extension.st b/src/PythonBridge-Pharo/SocketStream.extension.st index 311344a..2bef881 100644 --- a/src/PythonBridge-Pharo/SocketStream.extension.st +++ b/src/PythonBridge-Pharo/SocketStream.extension.st @@ -32,8 +32,8 @@ SocketStream >> uint16: anInteger [ (anInteger < 0 or: [ anInteger >= 16r10000 ]) ifTrue: [ self error: 'outside unsigned 16-bit integer range' ]. - self nextPut: (anInteger digitAt: 2). - self nextPut: (anInteger digitAt: 1) + self nextPut: (anInteger byteAt: 2). + self nextPut: (anInteger byteAt: 1) ] { #category : #'*PythonBridge-Pharo' } diff --git a/src/PythonBridge/PBAbstractMessageBroker.class.st b/src/PythonBridge/PBAbstractMessageBroker.class.st index 6848628..6db2612 100644 --- a/src/PythonBridge/PBAbstractMessageBroker.class.st +++ b/src/PythonBridge/PBAbstractMessageBroker.class.st @@ -71,7 +71,7 @@ PBAbstractMessageBroker >> pythonUri [ { #category : #private } PBAbstractMessageBroker >> resolveMessageFromType: aType [ - ^ PBAbstractMessage withAllSubclasses + ^ PBAbstractMessage allSubclasses detect: [ :cls | cls type = aType ] ifNone: [ Error signal: 'Message ' , aType , ' not understood.' ]. ] diff --git a/src/PythonBridge/PBApplication.class.st b/src/PythonBridge/PBApplication.class.st index 2636e86..0c0e451 100644 --- a/src/PythonBridge/PBApplication.class.st +++ b/src/PythonBridge/PBApplication.class.st @@ -252,7 +252,7 @@ PBApplication >> waitInitialization [ self isPythonReady ifTrue: [ ^ self ] ifFalse: [ (Delay forMilliseconds: 500) wait ] ]. - Error signal: 'Python application initialization failed!' + Error signal: 'Python application initialization failed! You should try to manually install the Pipenv environment. Have a look at the section "Manually creating Pipenv environment" in https://objectprofile.github.io/PythonBridge/pages/pharo-installation'. " Print the result of executing the following line: diff --git a/src/PythonBridge/PBMessageBrokerTest.class.st b/src/PythonBridge/PBMessageBrokerTest.class.st index 04064bf..7ed7016 100644 --- a/src/PythonBridge/PBMessageBrokerTest.class.st +++ b/src/PythonBridge/PBMessageBrokerTest.class.st @@ -81,9 +81,9 @@ PBMessageBrokerTest >> sendMessageToBroker: dict answerBlock: aBlock [ PBMessageBrokerTest >> sendMessageToBroker: dict answerEquals: ansDict [ | flag | flag := false. - self sendMessageToBroker: dict answerBlock: [ :ansAssoc | + self sendMessageToBroker: dict answerBlock: [ :aDict | flag := true. - self assert: ansAssoc value equals: ansDict ]. + self assert: aDict equals: ansDict ]. self assert: flag ]