diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..de9cab0237 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + + # Maintain dependencies for Pip + - package-ecosystem: "pip" + directory: "." + schedule: + interval: "weekly" + target-branch: "develop" + labels: + - "dependencies" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index bc8affdffe..30d6a0c9e9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -49,7 +49,7 @@ Describe in short the main changes with the new release. _Put an `x` in the boxes that apply._ - [ ] I have read the [CONTRIBUTING](../CONTRIBUTING.md) doc -- [ ] I am making a pull request against the `master` branch (left side), from `develop` +- [ ] I am making a pull request against the `main` branch (left side), from `develop` - [ ] Lint and unit tests pass locally - [ ] I have checked the fingerprint hashes are correct by running (`scripts/generate_ipfs_hashes.py`) - [ ] I have regenerated the latest API docs diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 5dce59cbd3..f2d0dc3cb7 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -4,7 +4,7 @@ on: push: branches: - develop - - master + - main pull_request: jobs: @@ -26,6 +26,20 @@ jobs: pip install pipenv - name: Pipenv lock run: pipenv lock + - name: Check plugin consistency + run: | + # these two files should not be different; + # we vendorize main "cosmos.py" module in fetchai crypto plugin package + diff plugins/aea-ledger-cosmos/aea_ledger_cosmos/cosmos.py plugins/aea-ledger-fetchai/aea_ledger_fetchai/_cosmos.py + + # check diff between plugins' LICENSE and main LICENSE + diff LICENSE plugins/aea-ledger-cosmos/LICENSE + diff LICENSE plugins/aea-ledger-ethereum/LICENSE + diff LICENSE plugins/aea-ledger-fetchai/LICENSE + - name: Check go code consistency + run: | + # check diff between go code in libs and packages + diff libs/go/libp2p_node packages/fetchai/connections/p2p_libp2p/libp2p_node -r common_checks_2: continue-on-error: False @@ -58,13 +72,20 @@ jobs: run: tox -e vulture - name: Static type check run: tox -e mypy - - name: Golang code style check + - name: Golang code style check (libp2p_node) + uses: golangci/golangci-lint-action@v1 + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: true + with: + version: v1.28 + working-directory: libs/go/libp2p_node + - name: Golang code style check (aealite) uses: golangci/golangci-lint-action@v1 env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true with: version: v1.28 - working-directory: packages/fetchai/connections/p2p_libp2p/ + working-directory: libs/go/aealite common_checks_3: continue-on-error: False @@ -100,6 +121,7 @@ jobs: sudo apt-get autoremove sudo apt-get autoclean pip install tox + pip install --user --upgrade setuptools # install Protobuf compiler wget https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip unzip protoc-3.11.4-linux-x86_64.zip -d protoc @@ -130,7 +152,7 @@ jobs: continue-on-error: False runs-on: ubuntu-latest timeout-minutes: 10 - if: github.base_ref == 'master' + if: github.base_ref == 'main' steps: - uses: actions/checkout@master - uses: actions/setup-python@master @@ -350,6 +372,9 @@ jobs: name: Unit tests run: | tox -e py${{ matrix.python_version }} -- -m 'not integration and not unstable' + - name: Plugin tests + run: | + tox -e plugins-py${{ matrix.python_version }} -- --cov-append -m 'not integration and not unstable' platform_checks_sync_aea_loop: continue-on-error: True @@ -404,8 +429,12 @@ jobs: with: go-version: '^1.14.0' - if: matrix.python-version == '3.6' - name: Golang unit tests - working-directory: ./packages/fetchai/connections/p2p_libp2p + name: Golang unit tests (libp2p_node) + working-directory: ./libs/go/libp2p_node + run: go test -p 1 -timeout 0 -count 1 -v ./... + - if: matrix.python-version == '3.6' + name: Golang unit tests (aealite) + working-directory: ./libs/go/aealite run: go test -p 1 -timeout 0 -count 1 -v ./... coverage_checks: @@ -438,7 +467,9 @@ jobs: make protolint_install # sudo apt-get install -y protobuf-compiler - name: Run all tests - run: tox -e py3.7-cov -- --ignore=tests/test_docs --ignore=tests/test_examples --ignore=tests/test_packages/test_contracts --ignore=tests/test_packages/test_skills_integration -m 'not unstable' + run: | + tox -e py3.7-cov -- --ignore=tests/test_docs --ignore=tests/test_examples --ignore=tests/test_packages/test_contracts --ignore=tests/test_packages/test_skills_integration -m 'not unstable' + tox -e plugins-py3.7-cov -- --cov-append -m 'not unstable' continue-on-error: true - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 diff --git a/.gitignore b/.gitignore index d736bd090b..ad656d5e38 100644 --- a/.gitignore +++ b/.gitignore @@ -124,6 +124,8 @@ output_file !packages/fetchai/contracts/oracle/build !packages/fetchai/contracts/oracle_client/build !packages/fetchai/contracts/fet_erc20/build -packages/fetchai/connections/p2p_libp2p/libp2p_node +packages/fetchai/connections/p2p_libp2p/libp2p_node/libp2p_node !tests/data/dummy_contract/build +!plugins/aea-ledger-ethereum/tests/data/dummy_contract/build +!plugins/aea-ledger-cosmos/tests/data/dummy_contract/build diff --git a/.pylintrc b/.pylintrc index 76f042c633..fb3b657e18 100644 --- a/.pylintrc +++ b/.pylintrc @@ -22,7 +22,7 @@ disable=C0103,C0201,C0301,C0302,C0330,W0105,W0107,W0707,W1202,W1203,R0801 # R0801: similar lines, # too granular [IMPORTS] -ignored-modules=aiohttp,defusedxml,gym,fetch,matplotlib,memory_profiler,numpy,oef,openapi_core,psutil,tensorflow,temper,skimage,vyper,web3,aioprometheus +ignored-modules=bech32,ecdsa,lru,eth_typing,eth_keys,eth_account,ipfshttpclient,werkzeug,openapi_spec_validator,aiohttp,yoti_python_sdk,defusedxml,gym,fetch,matplotlib,memory_profiler,numpy,oef,openapi_core,psutil,tensorflow,temper,skimage,vyper,web3,aioprometheus [DESIGN] min-public-methods=1 diff --git a/.spelling b/.spelling index df2409fe57..c5a054c27e 100644 --- a/.spelling +++ b/.spelling @@ -170,6 +170,8 @@ Yoti PowerShell deregisters plugin +Fetch.AI +AEALite - docs/language-agnostic-definition.md fetchai protocol_id diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c72ea7545f..55ead6b088 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,4 +7,4 @@ If you need support, want to report/fix a bug, ask for/implement features, you c or [submit a Pull request](https://github.com/fetchai/agents-aea/pulls) For other kinds of feedback, you can contact one of the -[authors](https://github.com/fetchai/agents-aea/blob/master/AUTHORS.md) by email. +[authors](https://github.com/fetchai/agents-aea/blob/main/AUTHORS.md) by email. diff --git a/HISTORY.md b/HISTORY.md index 600e5b7fa8..be3366bff6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,30 @@ # Release History +## 0.11.0 (2020-03-04) + +- Adds slots usage in frequently used framework objects, including `Dialogue` +- Fixes a bug in `aea upgrade` command where eject prompt was not offered +- Refactors skill component configurations to allow for skill components (`Handler`, `Behaviour`, `Model`) to be placed anywhere in a skill +- Extends skill component configuration to specify optional `file_path` field +- Extracts all ledger specific functionality in plugins +- Improves error logging in http server connection +- Updates `Development - Use case` documentation +- Adds restart support to `p2p_libp2p` connection on read/write failure +- Adds validation of default routing and default connection configuration +- Refactors and significantly simplifies routing between components +- Limits usage of `EnvelopeContext` +- Adds support for new CosmWasm message format in ledger plugins +- Adds project loading checks and optional auto removal in `MultiAgentManager` +- Adds support for reuse of threaded `Multiplexer` +- Fixes bug in TAC which caused agents to make suboptimal trades +- Adds support to specify dependencies on `aea-config.yaml` level +- Improves release scripts +- Adds lightweight Golang AEALite library +- Adds support for skill-to-skill messages +- Removes CLI GUI +- Multiple docs updates based on user feedback +- Multiple additional tests and test stability fixes + ## 0.10.1 (2020-02-21) - Changes default URL of `soef` connection to https diff --git a/MANIFEST.in b/MANIFEST.in index cf69a128e5..dd44b74cb2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,5 +4,6 @@ recursive-include aea *.json *.yaml *.proto *.ico *png *.html *.js *.css *.md *. recursive-include docs * recursive-include examples * recursive-include packages * +recursive-include plugins * recursive-include scripts * recursive-include tests * diff --git a/Makefile b/Makefile index debe1b410a..43988bb1a1 100644 --- a/Makefile +++ b/Makefile @@ -40,24 +40,30 @@ clean-test: .PHONY: lint lint: - black aea benchmark examples packages scripts tests - isort aea benchmark examples packages scripts tests - flake8 aea benchmark examples packages scripts tests + black aea benchmark examples packages plugins scripts tests + isort aea benchmark examples packages plugins scripts tests + flake8 aea benchmark examples packages plugins scripts tests vulture aea scripts/whitelist.py --exclude "*_pb2.py" .PHONY: pylint pylint: - pylint -j4 aea benchmark packages scripts examples/* + pylint -j4 aea benchmark packages scripts plugins/aea-ledger-fetchai/aea_ledger_fetchai plugins/aea-ledger-ethereum/aea_ledger_ethereum plugins/aea-ledger-cosmos/aea_ledger_cosmos examples/* .PHONY: security security: - bandit -r aea benchmark examples packages - bandit -s B101 -r tests scripts + bandit -r aea benchmark examples packages \ + plugins/aea-ledger-fetchai/aea_ledger_fetchai \ + plugins/aea-ledger-ethereum/aea_ledger_ethereum \ + plugins/aea-ledger-cosmos/aea_ledger_cosmos + bandit -s B101 -r tests scripts \ + plugins/aea-ledger-fetchai/tests \ + plugins/aea-ledger-ethereum/tests \ + plugins/aea-ledger-cosmos/tests safety check -i 37524 -i 38038 -i 37776 -i 38039 .PHONY: static static: - mypy aea benchmark examples packages scripts --disallow-untyped-defs + mypy aea benchmark examples packages plugins/aea-ledger-fetchai/aea_ledger_fetchai plugins/aea-ledger-ethereum/aea_ledger_ethereum plugins/aea-ledger-cosmos/aea_ledger_cosmos scripts --disallow-untyped-defs mypy tests .PHONY: package_checks @@ -75,6 +81,9 @@ common_checks: security misc_checks lint static docs .PHONY: test test: + pytest -rfE plugins/aea-ledger-fetchai/tests --cov=aea_ledger_fetchai --cov-report=term --cov-report=term-missing --cov-config=.coveragerc + pytest -rfE plugins/aea-ledger-ethereum/tests --cov=aea_ledger_ethereum --cov-report=term --cov-report=term-missing --cov-config=.coveragerc + pytest -rfE plugins/aea-ledger-cosmos/tests --cov=aea_ledger_cosmos --cov-report=term --cov-report=term-missing --cov-config=.coveragerc pytest -rfE --doctest-modules aea packages/fetchai/protocols packages/fetchai/connections packages/fetchai/skills/confirmation_aw1 packages/fetchai/skills/confirmation_aw2 packages/fetchai/skills/confirmation_aw3 packages/fetchai/skills/generic_buyer packages/fetchai/skills/generic_seller packages/fetchai/skills/tac_control packages/fetchai/skills/tac_control_contract packages/fetchai/skills/tac_participation packages/fetchai/skills/tac_negotiation packages/fetchai/skills/simple_buyer packages/fetchai/skills/simple_data_request packages/fetchai/skills/simple_seller packages/fetchai/skills/simple_service_registration packages/fetchai/skills/simple_service_search packages/fetchai/skills/coin_price packages/fetchai/skills/fetch_beacon packages/fetchai/skills/simple_oracle packages/fetchai/skills/simple_oracle_client tests/ --cov-report=html --cov-report=xml --cov-report=term-missing --cov-report=term --cov=aea --cov=packages/fetchai/protocols --cov=packages/fetchai/connections --cov=packages/fetchai/skills/confirmation_aw1 --cov=packages/fetchai/skills/confirmation_aw2 --cov=packages/fetchai/skills/confirmation_aw3 --cov=packages/fetchai/skills/generic_buyer --cov=packages/fetchai/skills/generic_seller --cov=packages/fetchai/skills/tac_control --cov=packages/fetchai/skills/tac_control_contract --cov=packages/fetchai/skills/tac_participation --cov=packages/fetchai/skills/tac_negotiation --cov=packages/fetchai/skills/simple_buyer --cov=packages/fetchai/skills/simple_data_request --cov=packages/fetchai/skills/simple_seller --cov=packages/fetchai/skills/simple_service_registration --cov=packages/fetchai/skills/simple_service_search --cov=packages/fetchai/skills/coin_price --cov=packages/fetchai/skills/fetch_beacon --cov=packages/fetchai/skills/simple_oracle --cov=packages/fetchai/skills/simple_oracle_client --cov-config=.coveragerc find . -name ".coverage*" -not -name ".coveragerc" -exec rm -fr "{}" \; @@ -106,13 +115,13 @@ h := $(shell git rev-parse --abbrev-ref HEAD) .PHONY: release_check release: - if [ "$h" = "master" ];\ + if [ "$h" = "main" ];\ then\ - echo "Please ensure everything is merged into master & tagged there";\ + echo "Please ensure everything is merged into main & tagged there";\ pip install twine;\ twine upload dist/*;\ else\ - echo "Please change to master branch for release.";\ + echo "Please change to main branch for release.";\ fi v := $(shell pip -V | grep virtualenvs) @@ -123,9 +132,12 @@ new_env: clean then\ pipenv --rm;\ pipenv --python 3.7;\ - pipenv install --dev --skip-lock;\ - pipenv run pip uninstall typing -y;\ - pipenv run pip install -e .[all];\ + pipenv install --dev --skip-lock;\ + pipenv run pip uninstall typing -y;\ + pipenv run pip install -e .[all];\ + pipenv run pip install --no-deps file:plugins/aea-ledger-ethereum;\ + pipenv run pip install --no-deps file:plugins/aea-ledger-cosmos;\ + pipenv run pip install --no-deps file:plugins/aea-ledger-fetchai;\ echo "Enter virtual environment with all development dependencies now: 'pipenv shell'.";\ else\ echo "In a virtual environment! Exit first: 'exit'.";\ @@ -137,4 +149,4 @@ protolint: protolint_install_win: powershell -command '$$env:GO111MODULE="on"; go get -u -v github.com/yoheimuta/protolint/cmd/protolint@v0.27.0' protolint_win: - protolint lint -config_path=./protolint.yaml -fix ./aea/mail ./packages/fetchai/protocols + protolint lint -config_path=./protolint.yaml -fix ./aea/mail ./packages/fetchai/protocols \ No newline at end of file diff --git a/Pipfile b/Pipfile index 727352ae33..92bc96260a 100644 --- a/Pipfile +++ b/Pipfile @@ -12,11 +12,14 @@ name = "test-pypi" aiohttp = "==3.6.2" aioprometheus = "==20.0.1" bandit = "==1.6.2" +bech32 = "==1.2.0" black = "==19.10b0" bs4 = "==0.0.1" colorlog = "==4.1.0" defusedxml = "==0.6.0" docker = "==4.2.0" +ecdsa = ">=0.15" +eth-account = "==0.5.2" flake8 = "==3.7.9" flake8-bugbear = "==20.1.4" flake8-docstrings = "==1.5.0" @@ -55,7 +58,9 @@ tox = "==3.15.1" vulture = "==2.1" vyper = "==0.1.0b12" isort = "==5.5.2" +web3 = "==5.12.0" yoti = "==2.14.0" +pytest-custom-exit-code = "==0.3.0" [packages] # we don't specify dependencies for the library here for intallation as per: https://pipenv-fork.readthedocs.io/en/latest/advanced.html#pipfile-vs-setuppy diff --git a/README.md b/README.md index 46f341f8d0..629ccc7b16 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - + @@ -24,7 +24,7 @@
-
+
@@ -125,7 +125,7 @@ You can have more control on the installed dependencies by leveraging the setupt
The following dependency is **only relevant if you intend to contribute** to the repository:
-- All Pull Requests should be opened against the `develop` branch. Do **not** open a Pull Request against `master`!
+- All Pull Requests should be opened against the `develop` branch. Do **not** open a Pull Request against `main`!
- The project uses [Google Protocol Buffers](https://developers.google.com/protocol-buffers/) compiler for message serialization. A guide on how to install it is found [here](https://fetchai.github.io/oef-sdk-python/user/install.html#protobuf-compiler).
@@ -183,15 +183,15 @@ The following steps are **only relevant if you intend to contribute** to the rep
### Go Development
-The `fetchai/p2p_libp2p` package is partially developed in Go.
+- The `fetchai/p2p_libp2p` package is partially developed in Go.
- To install Go visit the [Golang site](https://golang.org/doc/install).
- We use [`golines`](https://github.com/segmentio/golines) and [`golangci-lint`](https://golangci-lint.run) for linting.
-- To run tests, use `go test -p 1 -timeout 0 -count 1 -v ./...` from the root directory of the package.
+- To run tests, use `go test -p 1 -timeout 0 -count 1 -v ./...` from the root directory of the package. If you experience installation or build issues run `go clean -modcache`.
-### Documentation
+### Documentation
- To start a live-reloading docs server on localhost: `mkdocs serve`. To amend the docs, create a new documentation file in `docs/` and add a reference to it in `mkdocs.yml`.
diff --git a/SECURITY.md b/SECURITY.md
index d26fec8f96..8805f9c830 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -8,8 +8,8 @@ The following table shows which versions of `aea` are currently being supported
| Version | Supported |
| --------- | ------------------ |
-| `0.10.x` | :white_check_mark: |
-| `< 0.10.0` | :x: |
+| `0.11.x` | :white_check_mark: |
+| `< 0.11.0` | :x: |
## Reporting a Vulnerability
diff --git a/aea/__init__.py b/aea/__init__.py
index edfc9acaef..2ee575b9cd 100644
--- a/aea/__init__.py
+++ b/aea/__init__.py
@@ -34,10 +34,13 @@
__url__,
__version__,
)
+from aea.crypto.plugin import load_all_plugins
AEA_DIR = os.path.dirname(inspect.getfile(inspect.currentframe())) # type: ignore
+load_all_plugins()
+
def get_current_aea_version() -> Version:
"""Get current version."""
diff --git a/aea/__version__.py b/aea/__version__.py
index a83a7caca1..e9d93369c4 100644
--- a/aea/__version__.py
+++ b/aea/__version__.py
@@ -22,7 +22,7 @@
__title__ = "aea"
__description__ = "Autonomous Economic Agent framework"
__url__ = "https://github.com/fetchai/agents-aea.git"
-__version__ = "0.10.1"
+__version__ = "0.11.0"
__author__ = "Fetch.AI Limited"
__license__ = "Apache-2.0"
__copyright__ = "2019 Fetch.AI Limited"
diff --git a/aea/aea.py b/aea/aea.py
index ebb4e074a2..217964ceff 100644
--- a/aea/aea.py
+++ b/aea/aea.py
@@ -211,6 +211,7 @@ def __init__(
data_dir,
storage_callable=lambda: self.runtime.storage,
build_dir=self.get_build_dir(),
+ send_to_skill=self.runtime.agent_loop.send_to_skill,
**kwargs,
)
self._execution_timeout = execution_timeout
@@ -282,26 +283,34 @@ def _get_msg_and_handlers_for_envelope(
envelope.protocol_specification_id
)
- msg, handlers = self._handle_decoding(envelope, protocol)
+ error_handler = self._get_error_handler()
+
+ if protocol is None:
+ error_handler.send_unsupported_protocol(envelope, self.logger)
+ return None, []
+
+ msg, handlers = self._handle_decoding(envelope, protocol, error_handler)
return msg, handlers
def _handle_decoding(
- self, envelope: Envelope, protocol: Optional[Protocol]
+ self,
+ envelope: Envelope,
+ protocol: Protocol,
+ error_handler: Type[AbstractErrorHandler],
) -> Tuple[Optional[Message], List[Handler]]:
- handler = self._get_error_handler()
-
- if protocol is None:
- handler.send_unsupported_protocol(envelope, self.logger)
- return None, [] # Tuple[Optional[Message], List[Handler]]
-
handlers = self.filter.get_active_handlers(
- protocol.public_id, envelope.skill_id
+ protocol.public_id, envelope.to_as_public_id
)
if len(handlers) == 0:
- handler.send_unsupported_skill(envelope, self.logger)
+ reason = (
+ f"no active handler for protocol={protocol.public_id} in skill={envelope.to_as_public_id}"
+ if envelope.is_component_to_component_message
+ else f"no active handler for protocol={protocol.public_id}"
+ )
+ error_handler.send_no_active_handler(envelope, reason, self.logger)
return None, []
if isinstance(envelope.message, Message):
@@ -313,8 +322,7 @@ def _handle_decoding(
msg.to = envelope.to
return msg, handlers
except Exception as e: # pylint: disable=broad-except # thats ok, because we send the decoding error back
- self.logger.warning("Decoding error. Exception: {}".format(str(e)))
- handler.send_decoding_error(envelope, self.logger)
+ error_handler.send_decoding_error(envelope, e, self.logger)
return None, []
def handle_envelope(self, envelope: Envelope) -> None:
@@ -393,6 +401,7 @@ def get_message_handlers(self) -> List[Tuple[Callable[[Any], None], Callable]]:
"""
return super().get_message_handlers() + [
(self.filter.handle_internal_message, self.filter.get_internal_message,),
+ (self.handle_envelope, self.runtime.agent_loop.skill2skill_queue.get),
]
def exception_handler(self, exception: Exception, function: Callable) -> bool:
diff --git a/aea/aea_builder.py b/aea/aea_builder.py
index aa572e5c33..f1860a0a6f 100644
--- a/aea/aea_builder.py
+++ b/aea/aea_builder.py
@@ -64,11 +64,11 @@
)
from aea.configurations.constants import (
DOTTED_PATH_MODULE_ELEMENT_SEPARATOR,
- FETCHAI,
PROTOCOLS,
SIGNING_PROTOCOL,
SKILLS,
STATE_UPDATE_PROTOCOL,
+ _FETCHAI_IDENTIFIER,
)
from aea.configurations.loader import ConfigLoader, load_component_configuration
from aea.configurations.manager import (
@@ -560,6 +560,22 @@ def set_default_routing(
:return: self
"""
+ for protocol_id, connection_id in default_routing.items():
+ if (
+ ComponentId("protocol", protocol_id)
+ not in self._package_dependency_manager.protocols
+ ):
+ raise ValueError(
+ f"Protocol {protocol_id} specified in `default_routing` is not a project dependency!"
+ )
+ if (
+ ComponentId("connection", connection_id)
+ not in self._package_dependency_manager.connections
+ ):
+ raise ValueError(
+ f"Connection {connection_id} specified in `default_routing` is not a project dependency!"
+ )
+
self._default_routing = default_routing # pragma: nocover
return self
@@ -642,17 +658,26 @@ def _add_default_packages(self) -> None:
# add default protocol
default_protocol = PublicId.from_str(DEFAULT_PROTOCOL)
self.add_protocol(
- Path(self.registry_dir, FETCHAI, PROTOCOLS, default_protocol.name)
+ Path(
+ self.registry_dir, _FETCHAI_IDENTIFIER, PROTOCOLS, default_protocol.name
+ )
)
# add signing protocol
signing_protocol = PublicId.from_str(SIGNING_PROTOCOL)
self.add_protocol(
- Path(self.registry_dir, FETCHAI, PROTOCOLS, signing_protocol.name)
+ Path(
+ self.registry_dir, _FETCHAI_IDENTIFIER, PROTOCOLS, signing_protocol.name
+ )
)
# add state update protocol
state_update_protocol = PublicId.from_str(STATE_UPDATE_PROTOCOL)
self.add_protocol(
- Path(self.registry_dir, FETCHAI, PROTOCOLS, state_update_protocol.name)
+ Path(
+ self.registry_dir,
+ _FETCHAI_IDENTIFIER,
+ PROTOCOLS,
+ state_update_protocol.name,
+ )
)
def _check_can_remove(self, component_id: ComponentId) -> None:
@@ -700,6 +725,14 @@ def set_default_connection(
:param public_id: the public id of the default connection package.
:return: the AEABuilder
"""
+ if (
+ public_id
+ and ComponentId("connection", public_id)
+ not in self._package_dependency_manager.connections
+ ):
+ raise ValueError(
+ f"Connection {public_id} specified as `default_connection` is not a project dependency!"
+ )
self._default_connection = public_id
return self
@@ -1533,10 +1566,11 @@ def set_from_configuration(
self.set_default_ledger(agent_configuration.default_ledger)
self.set_build_entrypoint(agent_configuration.build_entrypoint)
self.set_currency_denominations(agent_configuration.currency_denominations)
- self.set_default_connection(agent_configuration.default_connection)
+
self.set_period(agent_configuration.period)
self.set_execution_timeout(agent_configuration.execution_timeout)
self.set_max_reactions(agent_configuration.max_reactions)
+
if agent_configuration.decision_maker_handler != {}:
dotted_path = agent_configuration.decision_maker_handler["dotted_path"]
file_path = agent_configuration.decision_maker_handler["file_path"]
@@ -1553,7 +1587,7 @@ def set_from_configuration(
self.set_connection_exception_policy(
ExceptionPolicyEnum(agent_configuration.connection_exception_policy)
)
- self.set_default_routing(agent_configuration.default_routing)
+
self.set_loop_mode(agent_configuration.loop_mode)
self.set_runtime_mode(agent_configuration.runtime_mode)
self.set_storage_uri(agent_configuration.storage_uri)
@@ -1593,6 +1627,9 @@ def set_from_configuration(
agent_configuration.component_configurations
)
+ self.set_default_connection(agent_configuration.default_connection)
+ self.set_default_routing(agent_configuration.default_routing)
+
@staticmethod
def _find_import_order(
component_ids: List[ComponentId],
diff --git a/aea/agent.py b/aea/agent.py
index 74f5f0d5b5..375e910316 100644
--- a/aea/agent.py
+++ b/aea/agent.py
@@ -273,7 +273,9 @@ def get_message_handlers(self) -> List[Tuple[Callable[[Any], None], Callable]]:
:return: List of tuples of callables: handler and coroutine to get a message
"""
- return [(self.handle_envelope, self.inbox.async_get)]
+ return [
+ (self.handle_envelope, self.inbox.async_get),
+ ]
def exception_handler(
self, exception: Exception, function: Callable
diff --git a/aea/agent_loop.py b/aea/agent_loop.py
index d4272ec76f..dba8acd11b 100644
--- a/aea/agent_loop.py
+++ b/aea/agent_loop.py
@@ -16,19 +16,18 @@
# limitations under the License.
#
# ------------------------------------------------------------------------------
-
-
"""This module contains the implementation of an agent loop using asyncio."""
import asyncio
import datetime
from abc import ABC, abstractmethod
from asyncio import CancelledError
from asyncio.events import AbstractEventLoop
+from asyncio.queues import Queue
from asyncio.tasks import Task
from contextlib import suppress
from enum import Enum
from functools import partial
-from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple
+from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union, cast
from aea.abstract_agent import AbstractAgent
from aea.configurations.constants import LAUNCH_SUCCEED_MESSAGE
@@ -41,6 +40,8 @@
)
from aea.helpers.exec_timeout import ExecTimeoutThreadGuard, TimeoutException
from aea.helpers.logging import WithLogger, get_logger
+from aea.mail.base import Envelope, EnvelopeContext
+from aea.protocols.base import Message
class AgentLoopException(AEAException):
@@ -91,6 +92,19 @@ def state(self) -> AgentLoopStates:
"""Get current main loop state."""
return self._state.get()
+ async def wait_state(
+ self, state_or_states: Union[Any, Sequence[Any]]
+ ) -> Tuple[Any, Any]:
+ """
+ Wait state to be set.
+
+ :param state_or_states: state or list of states.
+
+ :return: tuple of previous state and new state.
+ """
+
+ return await self._state.wait(state_or_states)
+
@property
def is_running(self) -> bool:
"""Get running state of the loop."""
@@ -101,7 +115,7 @@ def set_loop(self, loop: AbstractEventLoop) -> None:
self._loop: AbstractEventLoop = loop
def _setup(self) -> None: # pylint: disable=no-self-use
- """Set up loop before started."""
+ """Set up agent loop before started."""
# start and stop methods are classmethods cause one instance shared across muiltiple threads
ExecTimeoutThreadGuard.start()
@@ -112,7 +126,7 @@ def _teardown(self) -> None: # pylint: disable=no-self-use
async def run(self) -> None:
"""Run agent loop."""
- self.logger.debug("agent loop started")
+ self.logger.debug("agent loop starting...")
self._state.set(AgentLoopStates.starting)
self._setup()
self._set_tasks()
@@ -149,6 +163,26 @@ def _stop_tasks(self) -> None:
continue # pragma: nocover
task.cancel()
+ @abstractmethod
+ def send_to_skill(
+ self,
+ message_or_envelope: Union[Message, Envelope],
+ context: Optional[EnvelopeContext] = None,
+ ) -> None:
+ """
+ Send message or envelope to another skill.
+
+ :param message_or_envelope: envelope to send to another skill.
+ if message passed it will be wrapped into envelope with optional envelope context.
+
+ :return: None
+ """
+
+ @property
+ @abstractmethod
+ def skill2skill_queue(self) -> Queue:
+ """Get skill to skill message queue."""
+
class AsyncAgentLoop(BaseAgentLoop):
"""Asyncio based agent loop suitable only for AEA."""
@@ -166,11 +200,58 @@ def __init__(
:param agent: AEA instance
:param loop: asyncio loop to use. optional
+ :param threaded: is a new thread to be started for the agent loop
"""
super().__init__(agent=agent, loop=loop, threaded=threaded)
self._agent: AbstractAgent = self._agent
self._periodic_tasks: Dict[Callable, PeriodicCaller] = {}
+ self._skill2skill_message_queue: Optional[asyncio.Queue] = None
+
+ def _setup(self) -> None:
+ """Set up agent loop before started."""
+ self._skill2skill_message_queue = asyncio.Queue()
+ super()._setup()
+
+ @property
+ def skill2skill_queue(self) -> Queue:
+ """Get skill to skill message queue."""
+ if not self._skill2skill_message_queue: # pragma: nocover
+ raise ValueError("_skill2skill_message_queue is not set!")
+ return self._skill2skill_message_queue
+
+ def send_to_skill(
+ self,
+ message_or_envelope: Union[Message, Envelope],
+ context: Optional[EnvelopeContext] = None,
+ ) -> None:
+ """
+ Send message or envelope to another skill.
+
+ :param message_or_envelope: envelope to send to another skill.
+ if message passed it will be wrapped into envelope with optional envelope context.
+
+ :return: None
+ """
+ if isinstance(message_or_envelope, Envelope):
+ envelope = message_or_envelope
+ message = cast(Message, envelope.message)
+ elif isinstance(message_or_envelope, Message):
+ message = message_or_envelope
+ envelope = Envelope(
+ to=message.to, sender=message.sender, message=message, context=context,
+ )
+ else:
+ raise ValueError(
+ f"Unsupported message or envelope type: {type(message_or_envelope)}"
+ )
+
+ if not message.has_to: # pragma: nocover
+ raise ValueError("Provided message has message.to not set.")
+ if not message.has_sender: # pragma: nocover
+ raise ValueError("Provided message has message.sender not set.")
+
+ self.skill2skill_queue.put_nowait(envelope)
def _periodic_task_exception_callback( # pylint: disable=unused-argument
self, task_callable: Callable, exc: Exception
diff --git a/aea/cli/core.py b/aea/cli/core.py
index 899901d3b9..96feecac3e 100644
--- a/aea/cli/core.py
+++ b/aea/cli/core.py
@@ -60,8 +60,6 @@
from aea.cli.transfer import transfer
from aea.cli.upgrade import upgrade
from aea.cli.utils.click_utils import registry_path_option
-from aea.cli.utils.config import get_or_create_cli_config
-from aea.cli.utils.constants import AUTHOR_KEY
from aea.cli.utils.context import Context
from aea.cli.utils.loggers import logger, simple_verbosity_option
from aea.helpers.win32 import enable_ctrl_c_support
@@ -96,36 +94,6 @@ def cli(
enable_ctrl_c_support()
-@cli.command()
-@click.option("-p", "--port", default=8080)
-@click.option("--local", is_flag=True, help="For using local folder.")
-@click.pass_context
-def gui( # pylint: disable=unused-argument
- click_context: click.Context, port: int, local: bool
-) -> None: # pragma: no cover
- """Run the CLI GUI."""
- _init_gui()
- import aea.cli_gui # pylint: disable=import-outside-toplevel,redefined-outer-name
-
- click.echo("Running the GUI.....(press Ctrl+C to exit)")
- aea.cli_gui.run(port)
-
-
-def _init_gui() -> None:
- """
- Initialize GUI before start.
-
- :return: None
- :raisees: ClickException if author is not set up.
- """
- config_ = get_or_create_cli_config()
- author = config_.get(AUTHOR_KEY, None)
- if author is None:
- raise click.ClickException(
- "Author is not set up. Please run 'aea init' and then restart."
- )
-
-
cli.add_command(_list)
cli.add_command(add_key)
cli.add_command(add)
diff --git a/aea/cli/upgrade.py b/aea/cli/upgrade.py
index f5c824af48..c1f0b0af56 100644
--- a/aea/cli/upgrade.py
+++ b/aea/cli/upgrade.py
@@ -511,12 +511,15 @@ def get_latest_versions(self) -> None:
Stores the result in 'item_to_new_version'.
"""
for package_id in self.adjacency_list.keys():
- new_item = get_latest_version_available_in_registry(
- self.ctx,
- str(package_id.package_type),
- package_id.public_id.to_latest(),
- aea_version=self._current_aea_version,
- )
+ try:
+ new_item = get_latest_version_available_in_registry(
+ self.ctx,
+ str(package_id.package_type),
+ package_id.public_id.to_latest(),
+ aea_version=self._current_aea_version,
+ )
+ except click.ClickException:
+ continue
if package_id.public_id.version == new_item.version:
continue
new_version = new_item.version
diff --git a/aea/cli/utils/context.py b/aea/cli/utils/context.py
index e9aae87ced..ef2cbcbc60 100644
--- a/aea/cli/utils/context.py
+++ b/aea/cli/utils/context.py
@@ -124,6 +124,9 @@ def get_dependencies(self) -> Dependencies:
:return a list of dependency version specification. e.g. ["gym >= 1.0.0"]
"""
dependencies = {} # type: Dependencies
+
+ dependencies.update(self.agent_config.dependencies)
+
for protocol_id in self.agent_config.protocols:
dependencies.update(self._get_item_dependencies(PROTOCOL, protocol_id))
diff --git a/aea/cli/utils/package_utils.py b/aea/cli/utils/package_utils.py
index 998045436d..cd276e26d1 100644
--- a/aea/cli/utils/package_utils.py
+++ b/aea/cli/utils/package_utils.py
@@ -68,7 +68,7 @@
get_wallet_from_agent_config,
private_key_verify_or_create,
)
-from aea.crypto.ledger_apis import DEFAULT_LEDGER_CONFIGS, LedgerApis
+from aea.crypto.ledger_apis import LedgerApis
from aea.crypto.wallet import Wallet
from aea.exceptions import AEAEnforceError
from aea.helpers.base import compute_specifier_from_version, recursive_update
@@ -635,7 +635,7 @@ def try_get_balance( # pylint: disable=unused-argument
:retun: token balance.
"""
try:
- if type_ not in DEFAULT_LEDGER_CONFIGS: # pragma: no cover
+ if not LedgerApis.has_ledger(type_): # pragma: no cover
raise ValueError("No ledger api config for {} available.".format(type_))
address = wallet.addresses.get(type_)
if address is None: # pragma: no cover
diff --git a/aea/cli_gui/__init__.py b/aea/cli_gui/__init__.py
deleted file mode 100644
index 51b0bbd579..0000000000
--- a/aea/cli_gui/__init__.py
+++ /dev/null
@@ -1,457 +0,0 @@
-# -*- coding: utf-8 -*-
-# ------------------------------------------------------------------------------
-#
-# Copyright 2018-2019 Fetch.AI Limited
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# ------------------------------------------------------------------------------
-"""Key pieces of functionality for CLI GUI."""
-
-import glob
-import os
-import sys
-import threading
-from typing import Any, Dict, List, Tuple, Union
-
-import connexion
-import flask
-from click import ClickException
-
-from aea.cli.add import add_item as cli_add_item
-from aea.cli.create import create_aea as cli_create_aea
-from aea.cli.delete import delete_aea as cli_delete_aea
-from aea.cli.fetch import fetch_agent_locally as cli_fetch_agent_locally
-from aea.cli.list import list_agent_items as cli_list_agent_items
-from aea.cli.registry.fetch import fetch_agent as cli_fetch_agent
-from aea.cli.remove import remove_item as cli_remove_item
-from aea.cli.scaffold import scaffold_item as cli_scaffold_item
-from aea.cli.search import search_items as cli_search_items
-from aea.cli.search import setup_search_ctx as cli_setup_search_ctx
-from aea.cli.utils.config import try_to_load_agent_config
-from aea.cli.utils.context import Context
-from aea.cli.utils.formatting import sort_items
-from aea.cli_gui.utils import (
- AppContext,
- ProcessState,
- call_aea_async,
- get_process_status,
- is_agent_dir,
- read_error,
- read_tty,
- stop_agent_process,
- terminate_processes,
-)
-from aea.common import JSONLike
-from aea.configurations.base import PublicId
-from aea.configurations.constants import AGENT, CONNECTION, CONTRACT, PROTOCOL, SKILL
-
-
-elements = [
- ["local", AGENT, "localAgents"],
- ["registered", PROTOCOL, "registeredProtocols"],
- ["registered", CONNECTION, "registeredConections"],
- ["registered", SKILL, "registeredSkills"],
- ["local", PROTOCOL, "localProtocols"],
- ["local", CONNECTION, "localConnections"],
- ["local", CONTRACT, "localContracts"],
- ["local", SKILL, "localSkills"],
-]
-
-
-max_log_lines = 100
-
-
-app_context = AppContext()
-
-
-def get_agents() -> List[Dict]:
- """Return list of all local agents."""
- file_list = glob.glob(os.path.join(app_context.agents_dir, "*"))
-
- agent_list = []
-
- for path in file_list:
- if is_agent_dir(path):
- _head, tail = os.path.split(path)
- agent_list.append(
- {
- "public_id": tail, # it is not a public_id actually, just a folder name.
- # the reason it's called here so is the view that is used to represent items with public_ids
- # used also for agent displaying
- # change it when we will have a separate view for an agent.
- "description": "placeholder description",
- }
- )
-
- return agent_list
-
-
-def get_registered_items(
- item_type: str,
-) -> Union[Tuple[List[Dict[Any, Any]], int], Tuple[Dict[str, str], int]]:
- """Create a new AEA project."""
- # need to place ourselves one directory down so the cher can find the packages
- ctx = Context(cwd=app_context.agents_dir)
- try:
- cli_setup_search_ctx(ctx, local=app_context.local)
- result, _ = cli_search_items(ctx, item_type, query="", page=1)
- except ClickException:
- return {"detail": "Failed to search items."}, 400 # 400 Bad request
- else:
- sorted_items = sort_items(result)
- return sorted_items, 200 # 200 (Success)
-
-
-def search_registered_items(
- item_type: str, search_term: str
-) -> Union[Tuple[str, int], Tuple[Dict[str, Any], int]]:
- """Create a new AEA project."""
- # need to place ourselves one directory down so the searcher can find the packages
- ctx = Context(cwd=app_context.agents_dir)
- try:
- cli_setup_search_ctx(ctx, local=app_context.local)
- result, _ = cli_search_items(ctx, item_type, query=search_term, page=1)
- except ClickException:
- return {"detail": "Failed to search items."}, 400 # 400 Bad request
- else:
- sorted_items = sort_items(result)
- response = {
- "search_result": sorted_items,
- "item_type": item_type,
- "search_term": search_term,
- }
- return response, 200 # 200 (Success)
-
-
-def create_agent(agent_id: str) -> Union[Tuple[str, int], Tuple[Dict[str, str], int]]:
- """Create a new AEA project."""
- ctx = Context(cwd=app_context.agents_dir)
- try:
- cli_create_aea(ctx, agent_id, local=app_context.local)
- except ClickException as e:
- return (
- {"detail": "Failed to create Agent. {}".format(str(e))},
- 400,
- ) # 400 Bad request
- else:
- return agent_id, 201 # 201 (Created)
-
-
-def delete_agent(agent_id: str) -> Union[Tuple[str, int], Tuple[Dict[str, str], int]]:
- """Delete an existing AEA project."""
- ctx = Context(cwd=app_context.agents_dir)
- try:
- cli_delete_aea(ctx, agent_id)
- except ClickException:
- return (
- {"detail": "Failed to delete Agent {} - it may not exist".format(agent_id)},
- 400,
- ) # 400 Bad request
- else:
- return "Agent {} deleted".format(agent_id), 200 # 200 (OK)
-
-
-def add_item(
- agent_id: str, item_type: str, item_id: str
-) -> Union[Tuple[str, int], Tuple[Dict[str, str], int]]:
- """Add a protocol, skill or connection to the register to a local agent."""
- ctx = Context(cwd=os.path.join(app_context.agents_dir, agent_id))
- ctx.set_config("is_local", app_context.local)
- try:
- try_to_load_agent_config(ctx)
- cli_add_item(ctx, item_type, PublicId.from_str(item_id))
- except ClickException as e:
- return (
- {
- "detail": "Failed to add {} {} to agent {}. {}".format(
- item_type, item_id, agent_id, str(e)
- )
- },
- 400,
- ) # 400 Bad request
- else:
- return agent_id, 201 # 200 (OK)
-
-
-def fetch_agent(agent_id: str) -> Union[Tuple[str, int], Tuple[Dict[str, str], int]]:
- """Fetch an agent."""
- ctx = Context(cwd=app_context.agents_dir)
- fetch_agent_ = cli_fetch_agent_locally if app_context.local else cli_fetch_agent
- try:
- agent_public_id = PublicId.from_str(agent_id)
- fetch_agent_(ctx, agent_public_id)
- except ClickException as e:
- return (
- {"detail": "Failed to fetch an agent {}. {}".format(agent_id, str(e))},
- 400,
- ) # 400 Bad request
- else:
- return agent_public_id.name, 201 # 200 (OK)
-
-
-def remove_local_item(
- agent_id: str, item_type: str, item_id: str
-) -> Union[Tuple[str, int], Tuple[Dict[str, str], int]]:
- """Remove a protocol, skill or connection from a local agent."""
- agent_dir = os.path.join(app_context.agents_dir, agent_id)
- ctx = Context(cwd=agent_dir)
- try:
- try_to_load_agent_config(ctx)
- cli_remove_item(ctx, item_type, PublicId.from_str(item_id))
- except ClickException:
- return (
- {
- "detail": "Failed to remove {} {} from agent {}".format(
- item_type, item_id, agent_id
- )
- },
- 400,
- ) # 400 Bad request
- else:
- return agent_id, 201 # 200 (OK)
-
-
-def get_local_items(agent_id: str, item_type: str) -> Tuple[List[Dict[Any, Any]], int]:
- """
- Return a list of protocols, skills or connections supported by a local agent.
-
- :param agent_id: the id of the agent
- :param item_type: the type of item
- """
- if agent_id == "NONE":
- return [], 200 # 200 (Success)
-
- # need to place ourselves one directory down so the searcher can find the packages
- ctx = Context(cwd=os.path.join(app_context.agents_dir, agent_id))
- try:
- try_to_load_agent_config(ctx)
- result = cli_list_agent_items(ctx, item_type)
- except ClickException:
- return [{"detail": "Failed to list agent items."}], 400 # 400 Bad request
- else:
- sorted_items = sort_items(result)
- return sorted_items, 200 # 200 (Success)
-
-
-def scaffold_item(
- agent_id: str, item_type: str, item_id: str
-) -> Union[Tuple[str, int], Tuple[Dict[str, str], int]]:
- """Scaffold a moslty empty item on an agent (either protocol, skill or connection)."""
- agent_dir = os.path.join(app_context.agents_dir, agent_id)
- ctx = Context(cwd=agent_dir)
- try:
- try_to_load_agent_config(ctx)
- cli_scaffold_item(ctx, item_type, item_id)
- except ClickException:
- return (
- {
- "detail": "Failed to scaffold a new {} in to agent {}".format(
- item_type, agent_id
- )
- },
- 400,
- ) # 400 Bad request
- else:
- return agent_id, 201 # 200 (OK)
-
-
-def start_agent(
- agent_id: str, connection_id: PublicId
-) -> Union[Tuple[str, int], Tuple[Dict[str, str], int]]:
- """Start a local agent running."""
- # Test if it is already running in some form
- if agent_id in app_context.agent_processes:
- if (
- get_process_status(app_context.agent_processes[agent_id])
- != ProcessState.RUNNING
- ): # pragma: no cover
- if app_context.agent_processes[agent_id] is not None:
- app_context.agent_processes[agent_id].terminate()
- app_context.agent_processes[agent_id].wait()
- del app_context.agent_processes[agent_id]
- del app_context.agent_tty[agent_id]
- del app_context.agent_error[agent_id]
- else:
- return (
- {"detail": "Agent {} is already running".format(agent_id)},
- 400,
- ) # 400 Bad request
-
- agent_dir = os.path.join(app_context.agents_dir, agent_id)
-
- if connection_id is not None and connection_id != "":
- connections = get_local_items(agent_id, CONNECTION)[0]
- has_named_connection = False
- for element in connections:
- if element["public_id"] == connection_id:
- has_named_connection = True
- if has_named_connection:
- agent_process = call_aea_async(
- [
- sys.executable,
- "-m",
- "aea.cli",
- "run",
- "--connections",
- str(connection_id),
- ],
- agent_dir,
- )
- else:
- return (
- {
- "detail": "Trying to run agent {} with non-existent connection: {}".format(
- agent_id, connection_id
- )
- },
- 400,
- ) # 400 Bad request
- else:
- agent_process = call_aea_async(
- [sys.executable, "-m", "aea.cli", "run", "--install-deps"], agent_dir
- )
-
- if agent_process is None:
- return (
- {"detail": "Failed to run agent {}".format(agent_id)},
- 400,
- ) # 400 Bad request
- app_context.agent_processes[agent_id] = agent_process
- app_context.agent_tty[agent_id] = []
- app_context.agent_error[agent_id] = []
-
- # we don't seem to ever join this
- tty_read_thread = threading.Thread(
- target=read_tty,
- args=(app_context.agent_processes[agent_id], app_context.agent_tty[agent_id],),
- )
- tty_read_thread.start()
-
- # we don't seem to ever join this
- error_read_thread = threading.Thread(
- target=read_error,
- args=(
- app_context.agent_processes[agent_id],
- app_context.agent_error[agent_id],
- ),
- )
- error_read_thread.start()
-
- return agent_id, 201 # 200 (OK)
-
-
-def get_agent_status(agent_id: str) -> Tuple[JSONLike, int]:
- """Get the status of the running agent Node."""
- status_str = str(ProcessState.NOT_STARTED).replace("ProcessState.", "")
- tty_str = ""
- error_str = ""
-
- # agent_id will not be in lists if we haven't run it yet
- if (
- agent_id in app_context.agent_processes
- and app_context.agent_processes[agent_id] is not None
- ):
- status_str = str(
- get_process_status(app_context.agent_processes[agent_id])
- ).replace("ProcessState.", "")
-
- if agent_id in app_context.agent_tty:
- total_num_lines = len(app_context.agent_tty[agent_id])
- for i in range(max(0, total_num_lines - max_log_lines), total_num_lines):
- tty_str += app_context.agent_tty[agent_id][i]
-
- else:
- tty_str = ""
-
- tty_str = tty_str.replace("\n", "
")
-
- if agent_id in app_context.agent_error:
- total_num_lines = len(app_context.agent_error[agent_id])
- for i in range(max(0, total_num_lines - max_log_lines), total_num_lines):
- error_str += app_context.agent_error[agent_id][i]
-
- else:
- error_str = ""
-
- error_str = error_str.replace("\n", "
")
-
- return {"status": status_str, "tty": tty_str, "error": error_str}, 200 # (OK)
-
-
-def stop_agent(agent_id: str) -> Tuple[str, int]:
- """Stop agent running."""
- # pass to private function to make it easier to mock
- return stop_agent_process(agent_id, app_context)
-
-
-def create_app() -> connexion.FlaskApp:
- """Run the flask server."""
- CUR_DIR = os.path.abspath(os.path.dirname(__file__))
- app = connexion.FlaskApp(__name__, specification_dir=CUR_DIR)
- global app_context # pylint: disable=global-statement
- app_context = AppContext()
-
- app_context.agent_processes = {}
- app_context.agent_tty = {}
- app_context.agent_error = {}
- app_context.ui_is_starting = False
- app_context.agents_dir = os.path.abspath(os.getcwd())
- app_context.module_dir = os.path.join(
- os.path.abspath(os.path.dirname(__file__)), "../../"
- )
-
- app.add_api("aea_cli_rest.yaml")
-
- @app.route("/")
- def home() -> str: # pylint: disable=unused-variable
- """Respond to browser URL: localhost:5000/."""
- return flask.render_template(
- "home.html", len=len(elements), htmlElements=elements
- )
-
- @app.route("/static/js/home.js")
- def homejs() -> str: # pylint: disable=unused-variable
- """Serve the home.js file (as it needs templating)."""
- return flask.render_template(
- "home.js", len=len(elements), htmlElements=elements
- )
-
- @app.route("/favicon.ico")
- def favicon() -> str: # pylint: disable=unused-variable
- """Return an icon to be displayed in the browser."""
- return flask.send_from_directory(
- os.path.join(app.root_path, "static"),
- "favicon.ico",
- mimetype="image/vnd.microsoft.icon",
- )
-
- return app
-
-
-def run(port: int, host: str = "127.0.0.1") -> connexion.FlaskApp:
- """Run the GUI."""
-
- app = create_app()
- try:
- app.run(host=host, port=port, debug=False)
- finally:
- terminate_processes()
-
- return app
-
-
-def run_test() -> connexion.FlaskApp:
- """Run the gui in the form where we can run tests against it."""
- app = create_app()
- return app.app.test_client()
diff --git a/aea/cli_gui/__main__.py b/aea/cli_gui/__main__.py
deleted file mode 100644
index 7bed31763d..0000000000
--- a/aea/cli_gui/__main__.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-# ------------------------------------------------------------------------------
-#
-# Copyright 2018-2019 Fetch.AI Limited
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# ------------------------------------------------------------------------------
-
-"""Main entry point for CLI GUI.""" # pragma: no cover
-
-import argparse # pragma: no cover
-
-import aea.cli_gui # pragma: no cover
-
-
-parser = argparse.ArgumentParser(
- description="Launch the gui through python"
-) # pragma: no cover
-parser.add_argument(
- "-p", "--port", help="Port that the web server listens on", type=int, default=8080
-) # pragma: no cover
-
-parser.add_argument(
- "-H",
- "--host",
- help="host that the web server serves from",
- type=str,
- default="127.0.0.1",
-) # pragma: no cover
-
-args, unknown = parser.parse_known_args() # pragma: no cover
-
-# If we're running in stand alone mode, run the application
-if __name__ == "__main__": # pragma: no cover
- aea.cli_gui.run(args.port, args.host)
diff --git a/aea/cli_gui/aea_cli_rest.yaml b/aea/cli_gui/aea_cli_rest.yaml
deleted file mode 100644
index 5bde8807ef..0000000000
--- a/aea/cli_gui/aea_cli_rest.yaml
+++ /dev/null
@@ -1,343 +0,0 @@
-swagger: "2.0"
-info:
- description: This is the swagger file that goes with our server code
- version: "1.0.0"
- title: Swagger Rest Article
-consumes:
- - application/json
-produces:
- - application/json
-
-basePath: /api
-
-# Paths supported by the server application
-paths:
- /agent:
- get:
- operationId: aea.cli_gui.get_agents
- tags:
- - agents
- summary: Return list of all aea projects
- description: List of local folders under the user name (which correspond to aea projects)
- responses:
- 200:
- description: Successfully read agent list operation
- schema:
- type: array
- items:
- type: string
- post:
- operationId: aea.cli_gui.create_agent
- tags:
- - People
- summary: Create a new AEA project (an agent)
- parameters:
- - name: agent_id
- in: body
- description: Name of aea project to create
- required: True
- schema:
- type: string
-
- responses:
- 201:
- description: Successfully created person in list
-
- 400:
- description: Cannot create agent
- schema:
- type: string
-
- /agent/{agent_id}:
- delete:
- operationId: aea.cli_gui.delete_agent
- tags:
- - agents
- summary: Delete an aea project
- parameters:
- - name: agent_id
- in: path
- description: id of agent to delete
- type: string
- required: True
- responses:
- 200:
- description: Agent deleted successfully
- schema:
- type: string
-
- 400:
- description: Cannot delete agent
- schema:
- type: string
-
- /agent/{agent_id}/{item_type}:
- post:
- operationId: aea.cli_gui.add_item
- tags:
- - agents
- summary: Fetch a protocol from the registry to the currently selected agent
- parameters:
- - name: agent_id
- in: path
- description: id of agent to add protocol to
- type: string
- required: True
- - name: item_type
- in: path
- description: type of item to add ("protocol", "connection" or "skill")
- type: string
- required: True
- - name: item_id
- in: body
- description: id of protocol to add
- schema:
- type: string
- required: True
- responses:
- 201:
- description: Protocol added successfully
- schema:
- type: string
-
- 400:
- description: Cannot add protocol to agent
- schema:
- type: string
- get:
- operationId: aea.cli_gui.get_local_items
- tags:
- - agents
- summary: Return list of all items if a given type supported by this agent
- parameters:
- - name: agent_id
- in: path
- description: id of agent to searh in
- type: string
- required: True
- - name: item_type
- in: path
- description: type of item to list ("protocol", "connection" or "skill")
- type: string
- required: True
-
- responses:
- 200:
- description: Successfully read protocol list operation
- schema:
- type: array
- items:
- type: string
- 400:
- description: Cannot find protocols in agent
- schema:
- type: string
-
- /agent/{agent_id}/{item_type}/remove:
- post:
- operationId: aea.cli_gui.remove_local_item
- tags:
- - agents
- summary: Delete a protocol, connection or skill from an agent
- parameters:
- - name: agent_id
- in: path
- description: id of agent to delete
- type: string
- required: True
- - name: item_type
- in: path
- description: type of item to remove
- type: string
- required: True
- - name: item_id
- in: body
- description: id of item to remove
- schema:
- type: string
- required: True
-
- responses:
- 201:
- description: Agent deleted successfully
- schema:
- type: string
-
- 400:
- description: Cannot delete agent
- schema:
- type: string
-
- /agent/{agent_id}/{item_type}/scaffold:
- post:
- operationId: aea.cli_gui.scaffold_item
- tags:
- - agents
- summary: Scaffold a new (mostly empty) item (either a protocol, connection or skill) on to an agent
- parameters:
- - name: agent_id
- in: path
- description: id of agent to delete
- type: string
- required: True
- - name: item_type
- in: path
- description: type of item to remove
- type: string
- required: True
- - name: item_id
- in: body
- description: id of item to scaffold
- schema:
- type: string
- required: True
-
- responses:
- 201:
- description: Agent deleted successfully
- schema:
- type: string
-
- 400:
- description: Cannot delete agent
- schema:
- type: string
-
- /fetch-agent:
- post:
- operationId: aea.cli_gui.fetch_agent
- tags:
- - agents
- summary: Fetch an agent from the registry
- parameters:
- - name: agent_id
- in: body
- description: id of agent to fetch
- schema:
- type: string
- required: True
- responses:
- 201:
- description: Agent fetched successfully
- schema:
- type: string
-
- 400:
- description: Cannot fetch agent
- schema:
- type: string
-
- /{item_type}:
- get:
- operationId: aea.cli_gui.get_registered_items
- tags:
- - agents
- summary: Return list of all registered items (protocols, connections, skills)
- parameters:
- - name: item_type
- in: path
- description: type of item to remove
- type: string
- required: True
- responses:
- 200:
- description: Successfully read item list operation
- schema:
- type: array
- items:
- type: string
-
- /{item_type}/{search_term}:
- get:
- operationId: aea.cli_gui.search_registered_items
- tags:
- - agents
- summary: Return list of all registered items (protocols, connections, skills)
- parameters:
- - name: item_type
- in: path
- description: type of item to remove
- type: string
- required: True
- - name: search_term
- in: path
- description: type of item to remove
- type: string
- required: True
- responses:
- 200:
- description: Successfully read item list operation
- schema:
- type: object
-
- /agent/{agent_id}/run:
- post:
- operationId: aea.cli_gui.start_agent
- tags:
- - agents
- summary: Start an agent
- parameters:
- - name: agent_id
- in: path
- description: id of agent to run
- type: string
- required: True
- - name: connection_id
- in: body
- description: id f the connection to activate when running
- schema:
- type: string
- required: True
- responses:
- 201:
- description: Start the agent
- schema:
- type: string
- 400:
- description: Cannot start agent
- schema:
- type: string
- get:
- operationId: aea.cli_gui.get_agent_status
- tags:
- - agents
- summary: Get status of a running agent
- parameters:
- - name: agent_id
- in: path
- description: get status of agent
- type: string
- required: True
- responses:
- 200:
- description: successfully got status data
- schema:
- type: string
-
- 400:
- description: Cannot get status data
- schema:
- type: string
-
- delete:
- operationId: aea.cli_gui.stop_agent
- tags:
- - agents
- summary: Stops an agent
- parameters:
- - name: agent_id
- in: path
- description: id of agent to stop
- type: string
- required: True
-
- responses:
- 200:
- description: successfully started agent
- schema:
- type: string
-
- 400:
- description: Cannot stop agent
- schema:
- type: string
diff --git a/aea/cli_gui/static/css/home.css b/aea/cli_gui/static/css/home.css
deleted file mode 100644
index e392c656a6..0000000000
--- a/aea/cli_gui/static/css/home.css
+++ /dev/null
@@ -1,157 +0,0 @@
-hr {
- border: 1px solid #FFFFFF;
- opacity: 1;
-}
-
-nav {
- background: #1E2844 0% 0% no-repeat padding-box;
- height: 80px;
-}
-
-body {
- background: #161E33 0% 0% no-repeat padding-box;
-}
-
-table {
- color: #A1B6C0 !important;
- border-color: #A1B6C0;
-}
-
-thead {
- color: #FFFFFF;
-}
-
-button {
- height: 26px;
- border-radius: 2px;
- text-align: center;
- letter-spacing: -0.14px;
- color: #FFFFFF;
- border: none;
-}
-
-button:hover {
- color: #161E33;
- background: #FFFFFF;
-}
-
-input[type="text"] {
- height: 23px;
- border-radius: 2px;
- border: 1px solid #FFFFFF;
- color: #FFFFFF;
- background: transparent;
- text-indent: 4px;
-}
-
-input[type="radio"] {
- width: 19px;
- height: 19px;
- border: 1px solid #FFFFFF;
-}
-
-input:focus {
- outline: none !important;
-}
-
-::selection {
- color: #FFFFFF;
- background: #b3d7ff;
-}
-
-/* header items */
-.fetchai-logo {
- width: auto;
- margin-left: 40px;
- top: 20px;
- left: 47px;
- width: 164px;
- height: 51px;
-}
-
-.app-title {
- text-align: right;
- letter-spacing: -0.2px;
- color: #FFFFFF !important;
-}
-
-/* boxes */
-.app-box {
- border-radius: 4px;
-}
-
-.app-box-green {
- background: rgba(0, 185, 162, 0.1) 0% 0% no-repeat padding-box;
-}
-
-.app-box-blue {
- background: rgba(76, 130, 220, 0.1) 0% 0% no-repeat padding-box;
-}
-
-.app-box-violet {
- background: rgba(164, 90, 149, 0.1) 0% 0% no-repeat padding-box;
-}
-
-.box-title {
- text-align: center;
- letter-spacing: -0.18px;
-}
-
-/* buttons */
-.app-btn-green {
- background-color: rgba(0, 185, 162, 1);
-}
-
-.app-btn-blue {
- background-color: rgba(76, 130, 220, 1);
-}
-
-.app-btn-s {
- width: 82px;
-}
-
-.app-btn-m {
- width: 110px;
-}
-
-.app-btn-l {
- width: 172px;
-}
-
-/* inputs */
-.app-create-agent-input {
- width: 183px;
-}
-
-.app-search-input {
- width: 203px;
-}
-
-.app-connection-id-input {
- width: 183px;
-}
-
-/* miscellaneous */
-.app-output {
- height: 230px;
- border: 1px solid #FFFFFF;
- border-radius: 4px;
- overflow: auto !important;
-}
-
-.error {
- position: fixed;
- /* Sit on top of the page content */
- width: 100%;
- /* Full width (cover the whole page) */
- height: 10%;
- /* A bit of the page */
- bottom: 0;
- visibility: hidden;
- border: 1px solid lightgrey;
- border-radius: 0px;
- background-color: #fbbd;
- text-align: center;
- font-weight: bold;
- opacity: 50%;
-}
diff --git a/aea/cli_gui/static/favicon.ico b/aea/cli_gui/static/favicon.ico
deleted file mode 100644
index 7b45f6eedb..0000000000
Binary files a/aea/cli_gui/static/favicon.ico and /dev/null differ
diff --git a/aea/cli_gui/static/logo.png b/aea/cli_gui/static/logo.png
deleted file mode 100644
index 051c4501f5..0000000000
Binary files a/aea/cli_gui/static/logo.png and /dev/null differ
diff --git a/aea/cli_gui/templates/home.html b/aea/cli_gui/templates/home.html
deleted file mode 100644
index 104c61a51e..0000000000
--- a/aea/cli_gui/templates/home.html
+++ /dev/null
@@ -1,276 +0,0 @@
-
-
-
-
-
- Local
-
-
-
-
-
-
-
-
- Local agents
-
-
-
- Agent name
- Description
-
-
- Registry
-
- Search in Registry
-
-
-
-
-
-
-
- Search results
-
-
-
-
- NONE ID
- Description
-
-
- NONE's {{htmlElements[i][1]}}s
-
-
-
-
-
-
-
- {{htmlElements[i][1]}} ID
- Description
-
-
- Run "NONE" agent
-
-
-
-
-
-
-
-
-
- `;
- }
- $('.' + tableName + ' table > tbody').append(rows);
- }
- }
-
- error(error_msg) {
- $('.error')
- .html("${data[i].public_id} ${data[i].description}
" + error_msg)
- .css('visibility', 'visible');
- setTimeout(function() {
- $('.error').css('visibility', 'hidden');
- }, 3000)
- }
-
-}
-
-class Controller{
- constructor(m, v){
- this.model = m;
- this.view = v;
- this.$event_pump = $('body');
-
- // Get the data from the model after the controller is done initializing
- var self = this;
- setTimeout(function() {
- for (var i = 0; i < elements.length; ++i){
- self.model.readData(elements[i]);
- }
- }, 100)
-
- // Go through each of the element types setting up call-back and table building functions on the
- // Items which exist
- var self = this;
- for (var i = 0; i < elements.length; i++) {
- var element = elements[i]
- var combineName = element["combined"]
- $('#' + combineName + 'Create').click({el: element}, function(e){
- var id =$('#' + e.data.el["combined"] + 'CreateId').val();
-
- e.preventDefault();
-
- if (self.validateId(id)){
- self.model.createItem(e.data.el, id)
- } else {
- alert('Error: Problem with id');
- }
- });
-
- $('#' + combineName + 'Delete').click({el: element}, function(e) {
- var id =$('#' + e.data.el["combined"] + 'SelectionId').html();
- if (confirm("This will completely remove agent: " + id + "'s code and is non-recoverable. Press OK to do this - otherwise press cancel")){
-
- e.preventDefault();
-
- if (self.validateId(id)) {
- self.model.deleteItem(e.data.el, id)
- self.view.setSelectedId(e.data.el["combined"], "NONE")
- } else {
- alert('Error: Problem with selected id');
- }
- e.preventDefault();
- }
- });
-
- $('#' + combineName + 'Add').click({el: element}, function(e) {
- var agentId = $('#localAgentsSelectionId').html();
- var itemId =$('#' + e.data.el["combined"] + 'SelectionId').html();
-
- e.preventDefault();
-
- if (self.validateId(agentId) && self.validateId(itemId) ) {
- self.model.addItem(e.data.el, agentId, itemId)
- self.view.setSelectedId(e.data.el["combined"], "NONE")
- var tableBody = $("."+ e.data.el["combined"] +"registeredTable");
- self.clearTable(tableBody);
-
-
- } else {
- alert('Error: Problem with one of the selected ids (either agent or ' + element['type']);
- }
- e.preventDefault();
- });
- $('#' + combineName + 'Remove').click({el: element}, function(e) {
- var agentId = $('#localAgentsSelectionId').html();
- var itemId =$('#' + e.data.el["combined"] + 'SelectionId').html();
-
- e.preventDefault();
-
- if (self.validateId(agentId) && self.validateId(itemId) ) {
- self.model.removeItem(e.data.el, agentId, itemId)
- self.view.setSelectedId(e.data.el["combined"], "NONE")
-
-
- } else {
- alert('Error: Problem with one of the selected ids (either agent or ' + element['type']);
- }
- e.preventDefault();
- });
-
- $('.' + combineName + ' table > tbody ').on('click', 'tr', {el: element}, function(e) {
-
- var $target = $(e.target),
- id,
- description;
-
- id = $target
- .parent()
- .find('td.id')
- .text();
-
-
- self.view.setSelectedId(e.data.el["combined"], id);
-
- // Select the appropriate row
- var tableBody = $(e.target).closest("."+ e.data.el["combined"] +"registeredTable");
- self.clearTable(tableBody);
-
- $(this).addClass("aea_selected")
- if (e.data.el["combined"] == "localAgents"){
- self.refreshAgentData(id)
- }
- self.handleButtonStates()
- });
-
- $('#' + combineName + 'Scaffold').click({el: element}, function(e){
- var agentId = $('#localAgentsSelectionId').html();
- var itemId =$('#' + e.data.el["combined"] + 'ScaffoldId').val();
-
- e.preventDefault();
-
- if (self.validateId(agentId) && self.validateId(itemId)){
- self.model.scaffoldItem(e.data.el, agentId, itemId)
- } else {
- alert('Error: Problem with id');
- }
- });
-
- // Handle the model events
- this.$event_pump.on('model_'+ combineName + 'ReadSuccess', {el: element}, function(e, data) {
- self.view.build_table(data, e.data.el["combined"]);
- });
-
- this.$event_pump.on('model_'+ combineName + 'CreateSuccess', {el: element}, function(e, data) {
- self.model.readData(e.data.el);
- self.view.setSelectedId(e.data.el["combined"], data)
- self.view.setCreateId(e.data.el["combined"], "")
- self.refreshAgentData(data)
- self.handleButtonStates()
- });
-
- this.$event_pump.on('model_'+ combineName + 'DeleteSuccess', {el: element}, function(e, data) {
- self.model.readData(e.data.el);
-
- self.refreshAgentData("NONE")
- self.handleButtonStates()
-
- });
- this.$event_pump.on('model_'+ combineName + 'AddSuccess', {el: element}, function(e, data) {
- self.refreshAgentData(data)
- self.handleButtonStates()
-
- });
- this.$event_pump.on('model_'+ combineName + 'RemoveSuccess', {el: element}, function(e, data) {
- self.refreshAgentData(data)
- self.handleButtonStates()
-
- });
- this.$event_pump.on('model_'+ combineName + 'ScaffoldSuccess', {el: element}, function(e, data) {
- self.refreshAgentData(data)
- self.view.setScaffoldId(e.data.el["combined"], "")
- self.handleButtonStates()
- });
-
- }
-
- this.$event_pump.on('model_AgentStatusReadSuccess', function(e, data) {
- self.view.setAgentStatus("Agent Status: " + data["status"])
- self.view.setAgentTTY(data["tty"])
- self.view.setAgentError(data["error"])
- self.handleButtonStates()
- });
-
- this.$event_pump.on('model_searchReadSuccess', function(e, data) {
- self.view.setSearchType(data["item_type"])
- self.view.build_table(data["search_result"], 'searchItemsTable');
- self.handleButtonStates()
- });
-
- $('#startAgent').click({el: element}, function(e) {
- e.preventDefault();
- var agentId = $('#localAgentsSelectionId').html()
- var connectionId = $('#runConnectionId').val()
- if (self.validateId(agentId)){
- self.model.startAgent(agentId, connectionId)
- }
- else{
- alert('Error: Attempting to start agent with ID: ' + agentId);
- }
-
- e.preventDefault();
- });
- $('#stopAgent').click({el: element}, function(e) {
- e.preventDefault();
- var agentId = $('#localAgentsSelectionId').html()
- if (self.validateId(agentId)){
- self.model.stopAgent(agentId)
- }
- else{
- alert('Error: Attempting to stop agent with ID: ' + agentId);
- }
-
- e.preventDefault();
- });
- $('#searchInputButton').click({el: element}, function(e) {
- e.preventDefault();
- var searchTerm = $('#searchInput').val()
- if (self.validateId(searchTerm)){
- var itemType = $("input[name='itemType']:checked").attr('id')
- self.model.searchItems(itemType, searchTerm)
- }
- else{
- alert('Error: Attempting to stop search for: ' + searchTerm);
- }
-
- e.preventDefault();
- });
-
- $('.searchItemsTable table > tbody ').on('click', 'tr', {el: element}, function(e) {
-
- var $target = $(e.target),
- id,
- description;
-
- id = $target
- .parent()
- .find('td.id')
- .text();
-
-
- self.view.setSelectedId("searchItemsTable", id);
-
- // Select the appropriate row
- var tableBody = $(e.target).closest(".searchItemsTableRegisteredTable");
- self.clearTable(tableBody);
-
- $(this).addClass("aea_selected")
-
- self.handleButtonStates()
- });
-
-
- $('#searchItemsAdd').click({el: element}, function(e) {
- var agentId = $('#localAgentsSelectionId').html();
- var itemId = $('#searchItemsTableSelectionId').html();
- // It doesn't matter too much what the combined name is as long as it exists
- var itemType = {"type": $("#searchItemTypeSelected").html(), "combined": "localSkills"}
-
- e.preventDefault();
-
- if (self.validateId(agentId) && self.validateId(itemId) ) {
- self.model.addItem(itemType, agentId, itemId)
- self.view.setSelectedId("searchItemsTable", "NONE")
- var tableBody = $(e.target).closest(".searchItemsTableRegisteredTable");
- self.clearTable(tableBody);
- } else {
- alert('Error: Problem with one of the selected ids (either agent or ' + itemType);
- }
- e.preventDefault();
- });
-
- $('#searchAgentsFetch').click({el: element}, function(e) {
- var agentId = $('#searchItemsTableSelectionId').html();
- // It doesn't matter too much what the combined name is as long as it exists
- var itemType = {"type": $("#searchItemTypeSelected").html(), "combined": "localSkills"}
-
- e.preventDefault();
-
- if (self.validateId(agentId) ) {
- self.model.fetchAgent(agentId)
- self.view.setSelectedId("searchItemsTable", "NONE")
- var tableBody = $(e.target).closest(".searchItemsTableRegisteredTable");
- self.clearTable(tableBody);
- } else {
- alert('Error: Problem with one of the selected ids (either agent or ' + itemType);
- }
- e.preventDefault();
- });
-
-
- this.$event_pump.on('model_error', {el: element}, function(e, xhr, textStatus, errorThrown) {
- var error_msg = textStatus + ': ' + errorThrown + ' - ' + xhr.responseJSON.detail;
- self.view.error(error_msg);
- console.log(error_msg);
- })
-
- this.handleButtonStates(this);
-
-
- $('#localAgentsCreateId').on('input', function(e){
- self.handleButtonStates()
- });
- $('#localAgentsSelectionId').on('input', function(e){
- self.handleButtonStates()
- });
- $('#searchInput').on('input', function(e){
- self.handleButtonStates()
- });
-
- for (var j = 0; j < elements.length; j++) {
- $('#'+ elements[j]["combined"] + 'ScaffoldId').on('input', function(e){
- self.handleButtonStates()});
- }
-
- this.getAgentStatus();
-
- }
-
- clearTable (tableBody) {
- tableBody.children().each(function(i) {
- $(this).removeClass("aea_selected")
- });
- }
-
- handleButtonStates(){
- var agentCreateId = $('#localAgentsCreateId').val();
- var agentSelectionId = $('#localAgentsSelectionId').html();
- $('#localAgentsCreate').prop('disabled', !this.validateId(agentCreateId));
- $('#localAgentsDelete').prop('disabled', !this.validateId(agentSelectionId));
-
- for (var j = 0; j < elements.length; j++) {
- if (elements[j]["location"] == "local" && elements[j]["type"] != "agent"){
- var itemSelectionId = $('#' + elements[j]["combined"] + 'SelectionId').html();
- var isDisabled = !this.validateId(itemSelectionId);
- $('#' + elements[j]["combined"] + 'Remove').prop('disabled', isDisabled);
-
- var itemScaffoldId = $('#' + elements[j]["combined"] + 'ScaffoldId').val();
- $('#' + elements[j]["combined"] + 'Scaffold').prop('disabled',
- !this.validateId(itemScaffoldId) ||
- !this.validateId(agentSelectionId));
-
-
- }
- if (elements[j]["location"] == "registered"){
- var itemSelectionId = $('#' + elements[j]["combined"] + 'SelectionId').html();
- var isDisabled = !this.validateId(itemSelectionId) || !this.validateId(agentSelectionId);
- $('#' + elements[j]["combined"] + 'Add').prop('disabled', isDisabled);
- }
- }
- // Search buttons
- var searchTerm = $('#searchInput').val();
- $('#searchInputButton').prop('disabled', !this.validateId(searchTerm));
- var searchItem = $('#searchItemsTableSelectionId').html();
- var itemType = $("#searchItemTypeSelected").html();
- var isDisabled = !this.validateId(searchItem) || !this.validateId(agentSelectionId) || (itemType == "agent");
- $('#searchItemsAdd').prop('disabled', isDisabled);
-
- var isDisabled = !this.validateId(searchItem) || (itemType != "agent");
- $('#searchAgentsFetch').prop('disabled', isDisabled);
- if (agentSelectionId != "NONE"){
- $('.localItemHeading').html(agentSelectionId);
- }
- else{
- $('.localItemHeading').html("Local");
-
- }
- }
-
- getAgentStatus(){
- var agentId = $('#localAgentsSelectionId').html()
- self = this
- if (self.validateId(agentId)){
- this.model.readAgentStatus(agentId)
- }
- else{
- self.view.setAgentStatus("Agent Status: NONE")
- self.view.setAgentTTY("
")
- self.view.setAgentError("
")
- }
- setTimeout(function() {
- self.getAgentStatus()
- }, 500)
-
- }
-
- // Update lists of protocols, connections and skills for the selected agent
- refreshAgentData(agentId){
- for (var j = 0; j < elements.length; j++) {
- if (elements[j]["location"] == "local" && elements[j]["type"] != "agent"){
- this.model.readLocalData(elements[j], agentId);
- }
- }
- }
-
-
- validateId(agentId){
- return agentId != "" && agentId != "NONE";
- }
-
-
-}
-
-$( document ).ready(function() {
- c = new Controller(new Model(), new View())
-});
diff --git a/aea/cli_gui/utils.py b/aea/cli_gui/utils.py
deleted file mode 100644
index 4003c97881..0000000000
--- a/aea/cli_gui/utils.py
+++ /dev/null
@@ -1,193 +0,0 @@
-# -*- coding: utf-8 -*-
-# ------------------------------------------------------------------------------
-#
-# Copyright 2018-2020 Fetch.AI Limited
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# ------------------------------------------------------------------------------
-"""Module with utils for CLI GUI."""
-
-import io
-import logging
-import os
-import subprocess # nosec
-import sys
-import threading
-from enum import Enum
-from typing import Any, Dict, List, Optional, Set, Tuple
-
-
-class AppContext: # pylint: disable=too-few-public-methods
- """Store useful global information about the app.
-
- Can't add it into the app object itself because mypy complains.
- """
-
- agent_processes: Dict[str, subprocess.Popen] = {}
- agent_tty: Dict[str, List[str]] = {}
- agent_error: Dict[str, List[str]] = {}
-
- ui_is_starting = False
- agents_dir = os.path.abspath(os.getcwd())
- module_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "../../")
-
- local = "--local" in sys.argv # a hack to get "local" option from cli args
-
-
-class ProcessState(Enum):
- """The state of execution of the agent."""
-
- NOT_STARTED = "Not started yet"
- RUNNING = "Running"
- STOPPING = "Stopping"
- FINISHED = "Finished"
- FAILED = "Failed"
-
-
-_processes = set() # type: Set[subprocess.Popen]
-lock = threading.Lock()
-
-
-def _call_subprocess(*args: Any, timeout: Optional[float] = None, **kwargs: Any) -> int:
- """
- Create a subprocess.Popen, but with error handling.
-
- :return the exit code, or -1 if the call raises exception.
- """
- process = subprocess.Popen(*args) # nosec
- ret = -1
- try:
- ret = process.wait(timeout=timeout)
- except subprocess.TimeoutExpired:
- logging.exception(
- "TimeoutError occurred when calling with args={} and kwargs={}".format(
- args, kwargs
- )
- )
- finally:
- _terminate_process(process)
- return ret
-
-
-def is_agent_dir(dir_name: str) -> bool:
- """Return true if this directory contains an AEA project (an agent)."""
- if not os.path.isdir(dir_name):
- return False
- return os.path.isfile(os.path.join(dir_name, "aea-config.yaml"))
-
-
-def call_aea_async(param_list: List[str], dir_arg: str) -> subprocess.Popen:
- """Call the aea in a subprocess."""
- # Should lock here to prevent multiple calls coming in at once and changing the current working directory weirdly
- with lock:
- old_cwd = os.getcwd()
-
- os.chdir(dir_arg)
- env = os.environ.copy()
- env["PYTHONUNBUFFERED"] = "1"
- ret = subprocess.Popen( # nosec
- param_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env
- )
- _processes.add(ret)
- os.chdir(old_cwd)
- return ret
-
-
-def read_tty(pid: subprocess.Popen, str_list: List[str]) -> None:
- """
- Read tty.
-
- :param pid: the process id
- :param str_list: the output list to append to.
- """
- for line in io.TextIOWrapper(pid.stdout, encoding="utf-8"):
- out = line.replace("\n", "")
- logging.info("stdout: {}".format(out))
- str_list.append(line)
-
- str_list.append("process terminated\n")
-
-
-def read_error(pid: subprocess.Popen, str_list: List[str]) -> None:
- """
- Read error.
-
- :param pid: the process id
- :param str_list: the output list to append to.
- """
- for line in io.TextIOWrapper(pid.stderr, encoding="utf-8"):
- out = line.replace("\n", "")
- logging.error("stderr: {}".format(out))
- str_list.append(line)
-
- str_list.append("process terminated\n")
-
-
-def stop_agent_process(agent_id: str, app_context: AppContext) -> Tuple[str, int]:
- """
- Stop an agent processs.
-
- :param agent_id: the agent id
- :param app_context: the app context
- """
- # Test if we have the process id
- if agent_id not in app_context.agent_processes:
- return (
- "detail: Agent {} is not running".format(agent_id),
- 400,
- ) # 400 Bad request
-
- app_context.agent_processes[agent_id].terminate()
- app_context.agent_processes[agent_id].wait()
- del app_context.agent_processes[agent_id]
-
- return "stop_agent: All fine {}".format(agent_id), 200 # 200 (OK)
-
-
-def _terminate_process(process: subprocess.Popen) -> None:
- """Try to process gracefully."""
- poll = process.poll()
- if poll is None:
- # send SIGTERM
- process.terminate()
- try:
- # wait for termination
- process.wait(3)
- except subprocess.TimeoutExpired:
- # send SIGKILL
- process.kill()
-
-
-def terminate_processes() -> None:
- """Terminate all the (async) processes instantiated by the GUI."""
- logging.info("Cleaning up...")
- for process in _processes: # pragma: no cover
- _terminate_process(process)
-
-
-def get_process_status(process_id: subprocess.Popen) -> ProcessState:
- """
- Return the state of the execution.
-
- :param process_id: the process id
- """
- if process_id is None: # pragma: nocover
- raise ValueError("Process id cannot be None!")
-
- return_code = process_id.poll()
- if return_code is None:
- return ProcessState.RUNNING
- if return_code <= 0:
- return ProcessState.FINISHED
- return ProcessState.FAILED
diff --git a/aea/components/base.py b/aea/components/base.py
index 404ce8ca23..eb21d67613 100644
--- a/aea/components/base.py
+++ b/aea/components/base.py
@@ -42,6 +42,8 @@
class Component(ABC, WithLogger):
"""Abstract class for an agent component."""
+ __slots__ = ("_configuration", "_directory", "_is_vendor")
+
def __init__(
self,
configuration: Optional[ComponentConfiguration] = None,
diff --git a/aea/configurations/base.py b/aea/configurations/base.py
index 9a68980ee0..5cc72acc38 100644
--- a/aea/configurations/base.py
+++ b/aea/configurations/base.py
@@ -148,6 +148,8 @@ class ProtocolSpecificationParseError(Exception):
class Configuration(JSONSerializable, ABC):
"""Configuration class."""
+ __slots__ = ("_key_order",)
+
def __init__(self) -> None:
"""Initialize a configuration object."""
# a list of keys that remembers the key order of the configuration file.
@@ -205,6 +207,19 @@ class PackageConfiguration(Configuration, ABC):
- contracts
"""
+ __slots__ = (
+ "_name",
+ "_author",
+ "version",
+ "license",
+ "fingerprint",
+ "fingerprint_ignore_patterns",
+ "build_entrypoint",
+ "_aea_version",
+ "_aea_version_specifiers",
+ "_directory",
+ )
+
default_configuration_filename: str
package_type: PackageType
FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset(["build_directory"])
@@ -289,6 +304,14 @@ def aea_version(self, new_aea_version: str) -> None:
)
self._aea_version = new_aea_version
+ def check_aea_version(self) -> None:
+ """
+ Check that the AEA version matches the specifier set.
+
+ :raises ValueError if the version of the aea framework falls within a specifier.
+ """
+ _check_aea_version(self)
+
@property
def directory(self) -> Optional[Path]:
"""Get the path to the configuration file associated to this file, if any."""
@@ -430,6 +453,8 @@ class ComponentConfiguration(PackageConfiguration, ABC):
package_type: PackageType
+ __slots__ = ("pypi_dependencies", "_build_directory")
+
def __init__(
self,
name: SimpleIdOrStr,
@@ -509,14 +534,6 @@ def check_fingerprint(self, directory: Path) -> None:
self, directory, False, self.component_type.to_package_type()
)
- def check_aea_version(self) -> None:
- """
- Check that the AEA version matches the specifier set.
-
- :raises ValueError if the version of the aea framework falls within a specifier.
- """
- _check_aea_version(self)
-
def check_public_id_consistency(self, directory: Path) -> None:
"""
Check that the public ids in the init file match the config.
@@ -541,6 +558,19 @@ class ConnectionConfig(ComponentConfiguration):
["config", "cert_requests", "is_abstract", "build_directory"]
)
+ __slots__ = (
+ "class_name",
+ "protocols",
+ "connections",
+ "restricted_to_protocols",
+ "excluded_protocols",
+ "dependencies",
+ "description",
+ "config",
+ "is_abstract",
+ "cert_requests",
+ )
+
def __init__(
self,
name: SimpleIdOrStr = "",
@@ -729,6 +759,8 @@ class ProtocolConfig(ComponentConfiguration):
schema = "protocol-config_schema.json"
FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset()
+ __slots__ = ("dependencies", "description", "protocol_specification_id")
+
def __init__(
self,
name: SimpleIdOrStr,
@@ -824,7 +856,11 @@ def _create_or_update_from_json(
class SkillComponentConfiguration:
"""This class represent a skill component configuration."""
- def __init__(self, class_name: str, **args: Any) -> None:
+ __slots__ = ("class_name", "file_path", "args")
+
+ def __init__(
+ self, class_name: str, file_path: Optional[str] = None, **args: Any
+ ) -> None:
"""
Initialize a skill component configuration.
@@ -833,12 +869,16 @@ def __init__(self, class_name: str, **args: Any) -> None:
:param args: keyword arguments.
"""
self.class_name = class_name
+ self.file_path: Optional[Path] = Path(file_path) if file_path else None
self.args = args
@property
def json(self) -> Dict:
"""Return the JSON representation."""
- return {"class_name": self.class_name, "args": self.args}
+ result = {"class_name": self.class_name, "args": self.args}
+ if self.file_path is not None:
+ result["file_path"] = str(self.file_path.as_posix())
+ return result
@classmethod
def from_json(cls, obj: Dict) -> "SkillComponentConfiguration":
@@ -852,7 +892,8 @@ def _create_or_update_from_json(
"""Initialize from a JSON object."""
obj = {**(instance.json if instance else {}), **copy(obj)}
class_name = cast(str, obj.get("class_name"))
- params = dict(class_name=class_name, **obj.get("args", {}))
+ file_path = cast(Optional[str], obj.get("file_path"))
+ params = dict(class_name=class_name, file_path=file_path, **obj.get("args", {}))
instance = cast(
SkillComponentConfiguration, cls._apply_params_to_instance(params, instance)
@@ -888,6 +929,19 @@ class SkillConfig(ComponentConfiguration):
)
NESTED_FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset(["args"])
+ __slots__ = (
+ "connections",
+ "protocols",
+ "contracts",
+ "skills",
+ "dependencies",
+ "description",
+ "handlers",
+ "behaviours",
+ "models",
+ "is_abstract",
+ )
+
def __init__(
self,
name: SimpleIdOrStr,
@@ -1103,10 +1157,42 @@ class AgentConfig(PackageConfiguration):
CHECK_EXCLUDES = [
("private_key_paths",),
("connection_private_key_paths",),
+ ("decision_maker_handler",),
("default_routing",),
+ ("dependencies",),
("logging_config",),
]
+ __slots__ = (
+ "agent_name",
+ "registry_path",
+ "description",
+ "private_key_paths",
+ "connection_private_key_paths",
+ "logging_config",
+ "default_ledger",
+ "currency_denominations",
+ "default_connection",
+ "connections",
+ "protocols",
+ "skills",
+ "contracts",
+ "period",
+ "execution_timeout",
+ "max_reactions",
+ "skill_exception_policy",
+ "connection_exception_policy",
+ "error_handler",
+ "decision_maker_handler",
+ "default_routing",
+ "loop_mode",
+ "runtime_mode",
+ "storage_uri",
+ "data_dir",
+ "_component_configurations",
+ "dependencies",
+ )
+
def __init__( # pylint: disable=too-many-arguments
self,
agent_name: SimpleIdOrStr,
@@ -1136,6 +1222,7 @@ def __init__( # pylint: disable=too-many-arguments
storage_uri: Optional[str] = None,
data_dir: Optional[str] = None,
component_configurations: Optional[Dict[ComponentId, Dict]] = None,
+ dependencies: Optional[Dependencies] = None,
) -> None:
"""Instantiate the agent configuration object."""
super().__init__(
@@ -1198,6 +1285,7 @@ def __init__( # pylint: disable=too-many-arguments
self.component_configurations = (
component_configurations if component_configurations is not None else {}
)
+ self.dependencies = dependencies or {}
@property
def component_configurations(self) -> Dict[ComponentId, Dict]:
@@ -1302,6 +1390,7 @@ def json(self) -> Dict:
"logging_config": self.logging_config,
"registry_path": self.registry_path,
"component_configurations": self.component_configurations_json(),
+ "dependencies": dependencies_to_json(self.dependencies),
}
) # type: Dict[str, Any]
@@ -1374,12 +1463,15 @@ def _create_or_update_from_json(
storage_uri=cast(str, obj.get("storage_uri")),
data_dir=cast(str, obj.get("data_dir")),
component_configurations=None,
+ dependencies=cast(
+ Dependencies, dependencies_from_json(obj.get("dependencies", {}))
+ ),
)
instance = cast(AgentConfig, cls._apply_params_to_instance(params, instance))
agent_config = instance
- # parse private keys
+ # Parse private keys
for crypto_id, path in obj.get("private_key_paths", {}).items():
agent_config.private_key_paths.create(crypto_id, path)
@@ -1463,6 +1555,8 @@ def update(self, data: Dict, env_vars_friendly: bool = False) -> None:
class SpeechActContentConfig(Configuration):
"""Handle a speech_act content configuration."""
+ __slots__ = ("args",)
+
def __init__(self, **args: Any) -> None:
"""Initialize a speech_act content configuration."""
super().__init__()
@@ -1482,6 +1576,8 @@ def from_json(cls, obj: Dict) -> "SpeechActContentConfig":
class ProtocolSpecification(ProtocolConfig):
"""Handle protocol specification."""
+ __slots__ = ("speech_acts", "_protobuf_snippets", "_dialogue_config")
+
def __init__(
self,
name: SimpleIdOrStr,
@@ -1586,6 +1682,13 @@ class ContractConfig(ComponentConfiguration):
FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset(["build_directory"])
+ __slots__ = (
+ "dependencies",
+ "description",
+ "contract_interface_paths",
+ "class_name",
+ )
+
def __init__(
self,
name: SimpleIdOrStr,
@@ -1758,17 +1861,29 @@ def _compare_fingerprints(
)
+class AEAVersionError(ValueError):
+ """Special Exception for version error."""
+
+ def __init__(
+ self, package_id: PublicId, aea_version_specifiers: SpecifierSet
+ ) -> None:
+ """Init exception."""
+ self.package_id = package_id
+ self.aea_version_specifiers = aea_version_specifiers
+ self.current_aea_version = Version(__aea_version__)
+ super().__init__(
+ f"The CLI version is {self.current_aea_version}, but package {self.package_id} requires version {self.aea_version_specifiers}"
+ )
+
+
def _check_aea_version(package_configuration: PackageConfiguration) -> None:
"""Check the package configuration version against the version of the framework."""
current_aea_version = Version(__aea_version__)
version_specifiers = package_configuration.aea_version_specifiers
if current_aea_version not in version_specifiers:
- raise ValueError(
- "The CLI version is {}, but package {} requires version {}".format(
- current_aea_version,
- package_configuration.public_id,
- package_configuration.aea_version_specifiers,
- )
+ raise AEAVersionError(
+ package_configuration.public_id,
+ package_configuration.aea_version_specifiers,
)
diff --git a/aea/configurations/constants.py b/aea/configurations/constants.py
index 0c876d7830..92d019ddfe 100644
--- a/aea/configurations/constants.py
+++ b/aea/configurations/constants.py
@@ -22,12 +22,14 @@
from typing import Dict, List
-FETCHAI = "fetchai"
+_FETCHAI_IDENTIFIER = "fetchai"
+_ETHEREUM_IDENTIFIER = "ethereum"
+_COSMOS_IDENTIFIER = "cosmos"
DEFAULT_PROTOCOL = "fetchai/default:latest"
SIGNING_PROTOCOL = "fetchai/signing:latest"
STATE_UPDATE_PROTOCOL = "fetchai/state_update:latest"
LEDGER_CONNECTION = "fetchai/ledger:latest"
-DEFAULT_LEDGER = FETCHAI
+DEFAULT_LEDGER = _FETCHAI_IDENTIFIER
PRIVATE_KEY_PATH_SCHEMA = "{}_private_key.txt"
DEFAULT_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(DEFAULT_LEDGER)
DEFAULT_LICENSE = "Apache-2.0"
@@ -87,6 +89,14 @@
DEFAULT_AEA_CONFIG_FILE: AGENT,
} # type: Dict[str, str]
+CRYPTO_PLUGIN_GROUP = "aea.cryptos"
+LEDGER_APIS_PLUGIN_GROUP = "aea.ledger_apis"
+FAUCET_APIS_PLUGIN_GROUP = "aea.faucet_apis"
+ALLOWED_GROUPS = {
+ CRYPTO_PLUGIN_GROUP,
+ LEDGER_APIS_PLUGIN_GROUP,
+ FAUCET_APIS_PLUGIN_GROUP,
+}
AEA_MANAGER_DATA_DIRNAME = "data"
LAUNCH_SUCCEED_MESSAGE = "Start processing messages..."
diff --git a/aea/configurations/data_types.py b/aea/configurations/data_types.py
index 46a399eec9..f9d23ed11f 100644
--- a/aea/configurations/data_types.py
+++ b/aea/configurations/data_types.py
@@ -223,6 +223,8 @@ class PublicId(JSONSerializable):
True
"""
+ __slots__ = ("_author", "_name", "_package_version")
+
AUTHOR_REGEX = fr"[a-zA-Z_][a-zA-Z0-9_]{{0,{STRING_LENGTH_LIMIT - 1}}}"
PACKAGE_NAME_REGEX = fr"[a-zA-Z_][a-zA-Z0-9_]{{0,{STRING_LENGTH_LIMIT - 1}}}"
VERSION_NUMBER_PART_REGEX = r"(0|[1-9]\d*)"
@@ -321,6 +323,21 @@ def from_str(cls, public_id_string: str) -> "PublicId":
version = match.group(3)[1:] if ":" in public_id_string else None
return PublicId(username, package_name, version)
+ @classmethod
+ def try_from_str(cls, public_id_string: str) -> Optional["PublicId"]:
+ """
+ Safely try to get public id from string.
+
+ :param public_id_string: the public id in string format.
+ :return: the public id object or None
+ """
+ result: Optional[PublicId] = None
+ try:
+ result = cls.from_str(public_id_string)
+ except ValueError:
+ pass
+ return result
+
@classmethod
def from_uri_path(cls, public_id_uri_path: str) -> "PublicId":
"""
@@ -437,6 +454,8 @@ class PackageId:
PACKAGE_TYPE_REGEX, PublicId.PUBLIC_ID_URI_REGEX[1:-1]
)
+ __slots__ = ("_package_type", "_public_id")
+
def __init__(
self, package_type: Union[PackageType, str], public_id: PublicId
) -> None:
@@ -482,7 +501,7 @@ def package_prefix(self) -> Tuple[PackageType, str, str]:
@classmethod
def from_uri_path(cls, package_id_uri_path: str) -> "PackageId":
"""
- Initialize the public id from the string.
+ Initialize the package id from the string.
>>> str(PackageId.from_uri_path("skill/author/package_name/0.1.0"))
'(skill, author/package_name:0.1.0)'
@@ -493,8 +512,8 @@ def from_uri_path(cls, package_id_uri_path: str) -> "PackageId":
...
ValueError: Input 'very/bad/formatted:input' is not well formatted.
- :param public_id_uri_path: the public id in uri path string format.
- :return: the public id object.
+ :param package_id_uri_path: the package id in uri path string format.
+ :return: the package id object.
:raises ValueError: if the string in input is not well formatted.
"""
if not re.match(cls.PACKAGE_ID_URI_REGEX, package_id_uri_path):
@@ -640,6 +659,8 @@ class Dependency:
These fields will be forwarded to the 'pip' command.
"""
+ __slots__ = ("_name", "_version", "_index", "_git", "_ref")
+
def __init__(
self,
name: Union[PyPIPackageName, str],
@@ -774,6 +795,8 @@ def __eq__(self, other: Any) -> bool:
class CRUDCollection(Generic[T]):
"""Interface of a CRUD collection."""
+ __slots__ = ("_items_by_id",)
+
def __init__(self) -> None:
"""Instantiate a CRUD collection."""
self._items_by_id = {} # type: Dict[str, T]
diff --git a/aea/configurations/schemas/aea-config_schema.json b/aea/configurations/schemas/aea-config_schema.json
index 43b1ad3a6a..bfe11333e1 100644
--- a/aea/configurations/schemas/aea-config_schema.json
+++ b/aea/configurations/schemas/aea-config_schema.json
@@ -151,6 +151,9 @@
},
"data_dir": {
"type": "string"
+ },
+ "dependencies": {
+ "$ref": "definitions.json#/definitions/dependencies"
}
}
}
diff --git a/aea/configurations/schemas/skill-config_schema.json b/aea/configurations/schemas/skill-config_schema.json
index 91d36a10be..47ff0d7c93 100644
--- a/aea/configurations/schemas/skill-config_schema.json
+++ b/aea/configurations/schemas/skill-config_schema.json
@@ -87,13 +87,13 @@
}
},
"handlers": {
- "$ref": "#/definitions/handlers"
+ "$ref": "#/definitions/skill_component_list"
},
"behaviours": {
- "$ref": "#/definitions/behaviours"
+ "$ref": "#/definitions/skill_component_list"
},
"models": {
- "$ref": "#/definitions/models"
+ "$ref": "#/definitions/skill_component_list"
},
"dependencies": {
"$ref": "definitions.json#/definitions/dependencies"
@@ -106,37 +106,16 @@
}
},
"definitions": {
- "handlers": {
- "type": "object",
- "additionalProperties": false,
- "uniqueItems": true,
- "patternProperties": {
- "^[^\\d\\W]\\w*\\Z": {
- "$ref": "#/definitions/handler"
- }
- }
- },
- "behaviours": {
+ "skill_component_list": {
"type": "object",
- "uniqueItems": true,
"patternProperties": {
"^[^\\d\\W]\\w*\\Z": {
- "$ref": "#/definitions/behaviour"
+ "$ref": "#/definitions/skill_component_configuration"
}
}
},
- "models": {
+ "skill_component_configuration": {
"type": "object",
- "uniqueItems": true,
- "patternProperties": {
- "^[^\\d\\W]\\w*\\Z": {
- "$ref": "#/definitions/model"
- }
- }
- },
- "behaviour": {
- "type": "object",
- "additionalProperties": false,
"required": [
"class_name"
],
@@ -146,36 +125,9 @@
},
"args": {
"type": "object"
- }
- }
- },
- "handler": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "class_name"
- ],
- "properties": {
- "class_name": {
- "type": "string"
},
- "args": {
- "type": "object"
- }
- }
- },
- "model": {
- "type": "object",
- "additionalProperties": false,
- "required": [
- "class_name"
- ],
- "properties": {
- "class_name": {
+ "file_path": {
"type": "string"
- },
- "args": {
- "type": "object"
}
}
}
diff --git a/aea/connections/base.py b/aea/connections/base.py
index f164938d86..2cd6d3996f 100644
--- a/aea/connections/base.py
+++ b/aea/connections/base.py
@@ -122,12 +122,8 @@ def _ensure_valid_envelope_for_external_comms(envelope: "Envelope") -> None:
:param envelope: the envelope
"""
enforce(
- not envelope.is_sender_public_id,
- f"Sender field of envelope is public id, needs to be address. Found={envelope.sender}",
- )
- enforce(
- not envelope.is_to_public_id,
- f"To field of envelope is public id, needs to be address. Found={envelope.to}",
+ not envelope.is_sender_public_id and not envelope.is_to_public_id,
+ f"Sender and to field of envelope is public id, needs to be address. Found: sender={envelope.sender}, to={envelope.to}",
)
@contextmanager
diff --git a/aea/connections/scaffold/connection.yaml b/aea/connections/scaffold/connection.yaml
index c4e07e9553..dad3bcb05d 100644
--- a/aea/connections/scaffold/connection.yaml
+++ b/aea/connections/scaffold/connection.yaml
@@ -5,7 +5,7 @@ type: connection
description: The scaffold connection provides a scaffold for a connection to be implemented
by the developer.
license: Apache-2.0
-aea_version: '>=0.10.0, <0.11.0'
+aea_version: '>=0.11.0, <0.12.0'
fingerprint:
__init__.py: QmZvYZ5ECcWwqiNGh8qNTg735wu51HqaLxTSifUxkQ4KGj
connection.py: QmRQASdBp3e7d7oDoEnxHcVfM7wvVQFWUE44DXbqpc3kDU
diff --git a/aea/context/base.py b/aea/context/base.py
index 3bff5e2155..7a3362917a 100644
--- a/aea/context/base.py
+++ b/aea/context/base.py
@@ -16,24 +16,44 @@
# limitations under the License.
#
# ------------------------------------------------------------------------------
-
-
"""This module contains the agent context class."""
from queue import Queue
from types import SimpleNamespace
-from typing import Any, Callable, Dict, Optional
+from typing import Any, Callable, Dict, Optional, Union
from aea.common import Address
from aea.configurations.base import PublicId
from aea.helpers.storage.generic_storage import Storage
from aea.identity.base import Identity
+from aea.mail.base import Envelope, EnvelopeContext
from aea.multiplexer import MultiplexerStatus, OutBox
+from aea.protocols.base import Message
from aea.skills.tasks import TaskManager
class AgentContext:
"""Provide read access to relevant objects of the agent for the skills."""
+ __slots__ = (
+ "_shared_state",
+ "_identity",
+ "_connection_status",
+ "_outbox",
+ "_decision_maker_message_queue",
+ "_decision_maker_handler_context",
+ "_task_manager",
+ "_search_service_address",
+ "_decision_maker_address",
+ "_default_ledger_id",
+ "_currency_denominations",
+ "_default_connection",
+ "_default_routing",
+ "_storage_callable",
+ "_data_dir",
+ "_namespace",
+ "_send_to_skill",
+ )
+
def __init__(
self,
identity: Identity,
@@ -50,6 +70,7 @@ def __init__(
decision_maker_address: Address,
data_dir: str,
storage_callable: Callable[[], Optional[Storage]] = lambda: None,
+ send_to_skill: Optional[Callable] = None,
**kwargs: Any
) -> None:
"""
@@ -87,6 +108,24 @@ def __init__(
self._storage_callable = storage_callable
self._data_dir = data_dir
self._namespace = SimpleNamespace(**kwargs)
+ self._send_to_skill = send_to_skill
+
+ def send_to_skill(
+ self,
+ message_or_envelope: Union[Message, Envelope],
+ context: Optional[EnvelopeContext] = None,
+ ) -> None:
+ """
+ Send message or envelope to another skill.
+
+ :param message_or_envelope: envelope to send to another skill.
+ if message passed it will be wrapped into envelope with optional envelope context.
+
+ :return: None
+ """
+ if self._send_to_skill is None: # pragma: nocover
+ raise ValueError("Send to skill feature is not supported")
+ return self._send_to_skill(message_or_envelope, context)
@property
def storage(self) -> Optional[Storage]:
diff --git a/aea/contracts/scaffold/contract.yaml b/aea/contracts/scaffold/contract.yaml
index aa7e1c68eb..06a6cc2dfb 100644
--- a/aea/contracts/scaffold/contract.yaml
+++ b/aea/contracts/scaffold/contract.yaml
@@ -4,7 +4,7 @@ version: 0.1.0
type: contract
description: The scaffold contract scaffolds a contract to be implemented by the developer.
license: Apache-2.0
-aea_version: '>=0.10.0, <0.11.0'
+aea_version: '>=0.11.0, <0.12.0'
fingerprint:
__init__.py: QmPBwWhEg3wcH1q9612srZYAYdANVdWLDFWKs7TviZmVj6
contract.py: QmbaG1cJbzb1oNAce78n8UuLKqquHFLvHKKBfHL5F5Ci7G
diff --git a/aea/crypto/__init__.py b/aea/crypto/__init__.py
index 3006b6a9a8..e82693d2c0 100644
--- a/aea/crypto/__init__.py
+++ b/aea/crypto/__init__.py
@@ -19,39 +19,8 @@
"""This module contains the crypto modules."""
-from aea.crypto.cosmos import CosmosCrypto
-from aea.crypto.ethereum import EthereumCrypto
-from aea.crypto.fetchai import FetchAICrypto
-from aea.crypto.registries import register_crypto # noqa
-from aea.crypto.registries import register_faucet_api, register_ledger_api
-
-
-register_crypto(
- id_=FetchAICrypto.identifier, entry_point="aea.crypto.fetchai:FetchAICrypto"
-)
-register_crypto(
- id_=EthereumCrypto.identifier, entry_point="aea.crypto.ethereum:EthereumCrypto"
-)
-register_crypto(
- id_=CosmosCrypto.identifier, entry_point="aea.crypto.cosmos:CosmosCrypto"
-)
-
-register_faucet_api(
- id_=FetchAICrypto.identifier, entry_point="aea.crypto.fetchai:FetchAIFaucetApi"
-)
-register_faucet_api(
- id_=EthereumCrypto.identifier, entry_point="aea.crypto.ethereum:EthereumFaucetApi"
-)
-register_faucet_api(
- id_=CosmosCrypto.identifier, entry_point="aea.crypto.cosmos:CosmosFaucetApi"
-)
-
-register_ledger_api(
- id_=FetchAICrypto.identifier, entry_point="aea.crypto.fetchai:FetchAIApi",
-)
-register_ledger_api(
- id_=EthereumCrypto.identifier, entry_point="aea.crypto.ethereum:EthereumApi"
-)
-register_ledger_api(
- id_=CosmosCrypto.identifier, entry_point="aea.crypto.cosmos:CosmosApi",
+from aea.crypto.registries import ( # noqa
+ register_crypto,
+ register_faucet_api,
+ register_ledger_api,
)
diff --git a/aea/crypto/ledger_apis.py b/aea/crypto/ledger_apis.py
index e6a0060eb1..0431675a07 100644
--- a/aea/crypto/ledger_apis.py
+++ b/aea/crypto/ledger_apis.py
@@ -21,22 +21,13 @@
from typing import Any, Dict, Optional, Tuple, Union
from aea.common import Address
-from aea.configurations.constants import DEFAULT_LEDGER
-from aea.crypto.base import LedgerApi
-from aea.crypto.cosmos import CosmosApi
-from aea.crypto.cosmos import DEFAULT_ADDRESS as COSMOS_DEFAULT_ADDRESS
-from aea.crypto.cosmos import DEFAULT_CHAIN_ID as COSMOS_DEFAULT_CHAIN_ID
-from aea.crypto.cosmos import DEFAULT_CURRENCY_DENOM as COSMOS_DEFAULT_CURRENCY_DENOM
-from aea.crypto.ethereum import DEFAULT_ADDRESS as ETHEREUM_DEFAULT_ADDRESS
-from aea.crypto.ethereum import DEFAULT_CHAIN_ID as ETHEREUM_DEFAULT_CHAIN_ID
-from aea.crypto.ethereum import (
- DEFAULT_CURRENCY_DENOM as ETHEREUM_DEFAULT_CURRENCY_DENOM,
+from aea.configurations.constants import (
+ DEFAULT_LEDGER,
+ _COSMOS_IDENTIFIER,
+ _ETHEREUM_IDENTIFIER,
+ _FETCHAI_IDENTIFIER,
)
-from aea.crypto.ethereum import EthereumApi
-from aea.crypto.fetchai import DEFAULT_ADDRESS as FETCHAI_DEFAULT_ADDRESS
-from aea.crypto.fetchai import DEFAULT_CHAIN_ID as FETCHAI_DEFAULT_CHAIN_ID
-from aea.crypto.fetchai import DEFAULT_CURRENCY_DENOM as FETCHAI_DEFAULT_CURRENCY_DENOM
-from aea.crypto.fetchai import FetchAIApi
+from aea.crypto.base import LedgerApi
from aea.crypto.registries import (
ledger_apis_registry,
make_ledger_api,
@@ -45,27 +36,38 @@
from aea.exceptions import enforce
-DEFAULT_LEDGER_CONFIGS = {
- CosmosApi.identifier: {
+COSMOS_DEFAULT_ADDRESS = "INVALID_URL"
+COSMOS_DEFAULT_CURRENCY_DENOM = "INVALID_CURRENCY_DENOM"
+COSMOS_DEFAULT_CHAIN_ID = "INVALID_CHAIN_ID"
+ETHEREUM_DEFAULT_ADDRESS = "http://127.0.0.1:8545"
+ETHEREUM_DEFAULT_CHAIN_ID = 1337
+ETHEREUM_DEFAULT_CURRENCY_DENOM = "wei"
+FETCHAI_DEFAULT_ADDRESS = "https://rest-agent-land.fetch.ai"
+FETCHAI_DEFAULT_CURRENCY_DENOM = "atestfet"
+FETCHAI_DEFAULT_CHAIN_ID = "agent-land"
+
+
+DEFAULT_LEDGER_CONFIGS: Dict[str, Dict[str, Union[str, int]]] = {
+ _COSMOS_IDENTIFIER: {
"address": COSMOS_DEFAULT_ADDRESS,
"chain_id": COSMOS_DEFAULT_CHAIN_ID,
"denom": COSMOS_DEFAULT_CURRENCY_DENOM,
},
- EthereumApi.identifier: {
+ _ETHEREUM_IDENTIFIER: {
"address": ETHEREUM_DEFAULT_ADDRESS,
"chain_id": ETHEREUM_DEFAULT_CHAIN_ID,
"denom": ETHEREUM_DEFAULT_CURRENCY_DENOM,
},
- FetchAIApi.identifier: {
+ _FETCHAI_IDENTIFIER: {
"address": FETCHAI_DEFAULT_ADDRESS,
"chain_id": FETCHAI_DEFAULT_CHAIN_ID,
"denom": FETCHAI_DEFAULT_CURRENCY_DENOM,
},
-} # type: Dict[str, Dict[str, Union[str, int]]]
+}
DEFAULT_CURRENCY_DENOMINATIONS = {
- CosmosApi.identifier: COSMOS_DEFAULT_CURRENCY_DENOM,
- EthereumApi.identifier: ETHEREUM_DEFAULT_CURRENCY_DENOM,
- FetchAIApi.identifier: FETCHAI_DEFAULT_CURRENCY_DENOM,
+ _COSMOS_IDENTIFIER: COSMOS_DEFAULT_CURRENCY_DENOM,
+ _ETHEREUM_IDENTIFIER: ETHEREUM_DEFAULT_CURRENCY_DENOM,
+ _FETCHAI_IDENTIFIER: FETCHAI_DEFAULT_CURRENCY_DENOM,
}
diff --git a/aea/crypto/plugin.py b/aea/crypto/plugin.py
new file mode 100644
index 0000000000..a3cd14b2c9
--- /dev/null
+++ b/aea/crypto/plugin.py
@@ -0,0 +1,169 @@
+# -*- coding: utf-8 -*-
+# ------------------------------------------------------------------------------
+#
+# Copyright 2018-2020 Fetch.AI Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------------
+
+"""Implementation of plug-in mechanism for cryptos."""
+import itertools
+import pprint
+from typing import Iterator, List, Set
+
+from pkg_resources import EntryPoint, iter_entry_points
+
+from aea.configurations.constants import (
+ ALLOWED_GROUPS,
+ CRYPTO_PLUGIN_GROUP,
+ DOTTED_PATH_MODULE_ELEMENT_SEPARATOR,
+ FAUCET_APIS_PLUGIN_GROUP,
+ LEDGER_APIS_PLUGIN_GROUP,
+)
+from aea.crypto import register_crypto, register_faucet_api, register_ledger_api
+from aea.crypto.registries.base import EntryPoint as EntryPointString
+from aea.crypto.registries.base import ItemId
+from aea.exceptions import AEAPluginError, enforce
+
+
+_from_group_to_register_callable = {
+ CRYPTO_PLUGIN_GROUP: register_crypto,
+ LEDGER_APIS_PLUGIN_GROUP: register_ledger_api,
+ FAUCET_APIS_PLUGIN_GROUP: register_faucet_api,
+}
+
+
+class Plugin:
+ """Class that implements an AEA plugin."""
+
+ __slots__ = ("_group", "_entry_point")
+
+ def __init__(self, group: str, entry_point: EntryPoint):
+ """
+ Initialize the plugin.
+
+ :param group: the group the plugin belongs to.
+ :param entry_point: the entrypoint.
+ """
+ self._group = group
+ self._entry_point = entry_point
+ self._check_consistency()
+
+ def _check_consistency(self) -> None:
+ """
+ Check consistency of input.
+
+ :raises AEAPluginError: if some input is not correct.
+ """
+ _error_message_prefix = f"Error with plugin '{self._entry_point.name}':"
+ enforce(
+ self.group in ALLOWED_GROUPS,
+ f"{_error_message_prefix} '{self.group}' is not in the allowed groups: {pprint.pformat(ALLOWED_GROUPS)}",
+ AEAPluginError,
+ )
+ enforce(
+ ItemId.REGEX.match(self._entry_point.name) is not None,
+ f"{_error_message_prefix} '{self._entry_point.name}' is not a valid identifier for a plugin.",
+ AEAPluginError,
+ )
+ enforce(
+ len(self._entry_point.attrs) == 1,
+ f"{_error_message_prefix} Nested attributes currently not supported.",
+ AEAPluginError,
+ )
+ enforce(
+ len(self._entry_point.extras) == 0,
+ f"{_error_message_prefix} Extras currently not supported.",
+ AEAPluginError,
+ )
+ enforce(
+ EntryPointString.REGEX.match(self.entry_point_path) is not None,
+ f"{_error_message_prefix} Entry point path '{self.entry_point_path}' is not valid.",
+ )
+
+ @property
+ def name(self) -> str:
+ """Get the plugin identifier."""
+ return self._entry_point.name
+
+ @property
+ def group(self) -> str:
+ """Get the group."""
+ return self._group
+
+ @property
+ def attr(self) -> str:
+ """Get the class name."""
+ return self._entry_point.attrs[0]
+
+ @property
+ def entry_point_path(self) -> str:
+ """Get the entry point path."""
+ class_name = self.attr
+ return f"{self._entry_point.module_name}{DOTTED_PATH_MODULE_ELEMENT_SEPARATOR}{class_name}"
+
+
+def _check_no_duplicates(plugins: List[EntryPoint]) -> None:
+ """Check there are no two plugins with the same id."""
+ seen: Set[str] = set()
+ duplicate_plugins = [p for p in plugins if p.name in seen or seen.add(p.name)] # type: ignore
+ error_msg = f"Found plugins with the same id: {pprint.pformat(duplicate_plugins)}"
+ enforce(len(duplicate_plugins) == 0, error_msg, AEAPluginError)
+
+
+def _get_plugins(group: str) -> List[Plugin]:
+ """
+ Return a dict of all installed plugins, by name.
+
+ :param group: the plugin group.
+ :return: a mapping from plugin name to Plugin objects.
+ """
+ entry_points: List[EntryPoint] = list(iter_entry_points(group=group))
+ _check_no_duplicates(entry_points)
+ return [Plugin(group, entry_point) for entry_point in entry_points]
+
+
+def _get_cryptos() -> List[Plugin]:
+ """Get cryptos plugins."""
+ return _get_plugins(CRYPTO_PLUGIN_GROUP)
+
+
+def _get_ledger_apis() -> List[Plugin]:
+ """Get ledgers plugins."""
+ return _get_plugins(LEDGER_APIS_PLUGIN_GROUP)
+
+
+def _get_faucet_apis() -> List[Plugin]:
+ """Get faucets plugins."""
+ return _get_plugins(FAUCET_APIS_PLUGIN_GROUP)
+
+
+def _iter_plugins() -> Iterator[Plugin]:
+ """Iterate over all the plugins."""
+ for plugin in itertools.chain(
+ _get_cryptos(), _get_ledger_apis(), _get_faucet_apis()
+ ):
+ yield plugin
+
+
+def _register_plugin(plugin: Plugin) -> None:
+ """Register a plugin to the right registry."""
+ register_function = _from_group_to_register_callable[plugin.group]
+ register_function(plugin.name, entry_point=plugin.entry_point_path)
+
+
+def load_all_plugins() -> None:
+ """Load all plugins."""
+ for plugin in _iter_plugins():
+ _register_plugin(plugin)
diff --git a/aea/crypto/wallet.py b/aea/crypto/wallet.py
index 0a1ae77ca6..ec1967748a 100644
--- a/aea/crypto/wallet.py
+++ b/aea/crypto/wallet.py
@@ -33,6 +33,8 @@
class CryptoStore:
"""Utility class to store and retrieve crypto objects."""
+ __slots__ = ("_crypto_objects", "_public_keys", "_addresses", "_private_keys")
+
def __init__(
self, crypto_id_to_path: Optional[Dict[str, Optional[str]]] = None
) -> None:
diff --git a/aea/decision_maker/gop.py b/aea/decision_maker/gop.py
index da746391af..1b82e03e1d 100644
--- a/aea/decision_maker/gop.py
+++ b/aea/decision_maker/gop.py
@@ -45,8 +45,6 @@
UtilityParams = Dict[str, float] # a map from identifier to quantity
ExchangeParams = Dict[str, float] # a map from identifier to quantity
-QUANTITY_SHIFT = 100
-
_default_logger = logging.getLogger(__name__)
@@ -288,7 +286,6 @@ def __init__(self) -> None:
"""Instantiate an agent preference object."""
self._exchange_params_by_currency_id = None # type: Optional[ExchangeParams]
self._utility_params_by_good_id = None # type: Optional[UtilityParams]
- self._quantity_shift = QUANTITY_SHIFT
def set( # pylint: disable=arguments-differ
self,
@@ -348,7 +345,7 @@ def logarithmic_utility(self, quantities_by_good_id: GoodHoldings) -> float:
"""
enforce(self.is_initialized, "Preferences params not set!")
result = logarithmic_utility(
- self.utility_params_by_good_id, quantities_by_good_id, self._quantity_shift
+ self.utility_params_by_good_id, quantities_by_good_id,
)
return result
diff --git a/aea/error_handler/base.py b/aea/error_handler/base.py
index 15ff4a67ec..7d868d3e0a 100644
--- a/aea/error_handler/base.py
+++ b/aea/error_handler/base.py
@@ -39,20 +39,28 @@ def send_unsupported_protocol(cls, envelope: Envelope, logger: Logger) -> None:
@classmethod
@abstractmethod
- def send_decoding_error(cls, envelope: Envelope, logger: Logger) -> None:
+ def send_decoding_error(
+ cls, envelope: Envelope, exception: Exception, logger: Logger
+ ) -> None:
"""
Handle a decoding error.
:param envelope: the envelope
+ :param exception: the exception raised during decoding
+ :param logger: the logger
:return: None
"""
@classmethod
@abstractmethod
- def send_unsupported_skill(cls, envelope: Envelope, logger: Logger) -> None:
+ def send_no_active_handler(
+ cls, envelope: Envelope, reason: str, logger: Logger
+ ) -> None:
"""
- Handle the received envelope in case the skill is not supported.
+ Handle the received envelope in case the handler is not supported.
:param envelope: the envelope
+ :param reason: the reason for the failure
+ :param logger: the logger
:return: None
"""
diff --git a/aea/error_handler/default.py b/aea/error_handler/default.py
index cd08759ccd..78c51a52b0 100644
--- a/aea/error_handler/default.py
+++ b/aea/error_handler/default.py
@@ -29,7 +29,7 @@ class ErrorHandler(AbstractErrorHandler):
"""Error handler class for handling problematic envelopes."""
unsupported_protocol_count = 0
- unsupported_skill_count = 0
+ no_active_handler_count = 0
decoding_error_count = 0
@classmethod
@@ -46,32 +46,34 @@ def send_unsupported_protocol(cls, envelope: Envelope, logger: Logger) -> None:
)
@classmethod
- def send_decoding_error(cls, envelope: Envelope, logger: Logger) -> None:
+ def send_decoding_error(
+ cls, envelope: Envelope, exception: Exception, logger: Logger
+ ) -> None:
"""
Handle a decoding error.
:param envelope: the envelope
+ :param exception: the exception raised during decoding
+ :param logger: the logger
:return: None
"""
cls.decoding_error_count += 1
logger.warning(
- f"Decoding error for envelope: {envelope}. Protocol_specification_id='{envelope.protocol_specification_id}' and message are inconsistent. Sender={envelope.sender}, to={envelope.sender}."
+ f"Decoding error for envelope: {envelope}. Protocol_specification_id='{envelope.protocol_specification_id}' and message are inconsistent. Sender={envelope.sender}, to={envelope.sender}. Exception={exception}."
)
@classmethod
- def send_unsupported_skill(cls, envelope: Envelope, logger: Logger) -> None:
+ def send_no_active_handler(
+ cls, envelope: Envelope, reason: str, logger: Logger
+ ) -> None:
"""
- Handle the received envelope in case the skill is not supported.
+ Handle the received envelope in case the handler is not supported.
:param envelope: the envelope
+ :param reason: the reason for the failure
:return: None
"""
- cls.unsupported_skill_count += 1
- if envelope.skill_id is None:
- logger.warning(
- f"Cannot handle envelope: no active handler registered for the protocol_specification_id='{envelope.protocol_specification_id}'. Sender={envelope.sender}, to={envelope.sender}."
- )
- else:
- logger.warning(
- f"Cannot handle envelope: no active handler registered for the protocol_specification_id='{envelope.protocol_specification_id}' and skill_id='{envelope.skill_id}'. Sender={envelope.sender}, to={envelope.sender}."
- )
+ cls.no_active_handler_count += 1
+ logger.warning(
+ f"Cannot handle envelope: {reason}. Sender={envelope.sender}, to={envelope.sender}."
+ )
diff --git a/aea/error_handler/scaffold.py b/aea/error_handler/scaffold.py
index 03e451dd95..07720941f9 100644
--- a/aea/error_handler/scaffold.py
+++ b/aea/error_handler/scaffold.py
@@ -39,21 +39,29 @@ def send_unsupported_protocol(cls, envelope: Envelope, logger: Logger) -> None:
raise NotImplementedError
@classmethod
- def send_decoding_error(cls, envelope: Envelope, logger: Logger) -> None:
+ def send_decoding_error(
+ cls, envelope: Envelope, exception: Exception, logger: Logger
+ ) -> None:
"""
Handle a decoding error.
:param envelope: the envelope
+ :param exception: the exception raised during decoding
+ :param logger: the logger
:return: None
"""
raise NotImplementedError
@classmethod
- def send_unsupported_skill(cls, envelope: Envelope, logger: Logger) -> None:
+ def send_no_active_handler(
+ cls, envelope: Envelope, reason: str, logger: Logger
+ ) -> None:
"""
- Handle the received envelope in case the skill is not supported.
+ Handle the received envelope in case the handler is not supported.
:param envelope: the envelope
+ :param reason: the reason for the failure
+ :param logger: the logger
:return: None
"""
raise NotImplementedError
diff --git a/aea/exceptions.py b/aea/exceptions.py
index 22cbd5090f..e75b4769c0 100644
--- a/aea/exceptions.py
+++ b/aea/exceptions.py
@@ -51,6 +51,10 @@ class AEAInstantiationException(AEAException):
"""Class for exceptions that are raised for instantiation errors of AEA packages."""
+class AEAPluginError(AEAException):
+ """Class for exceptions that are raised for wrong plugin setup of the working set."""
+
+
class AEAEnforceError(AEAException):
"""Class for enforcement errors."""
diff --git a/aea/helpers/acn/agent_record.py b/aea/helpers/acn/agent_record.py
index af0c1b6cd5..e2f6eda8cb 100644
--- a/aea/helpers/acn/agent_record.py
+++ b/aea/helpers/acn/agent_record.py
@@ -28,6 +28,15 @@
class AgentRecord:
"""Agent Proof-of-Representation to representative."""
+ __slots__ = (
+ "_address",
+ "_representative_public_key",
+ "_message",
+ "_signature",
+ "_ledger_id",
+ "_public_key",
+ )
+
def __init__(
self,
address: str,
diff --git a/aea/helpers/acn/uri.py b/aea/helpers/acn/uri.py
index 606dd8ce1e..58ffc93e40 100644
--- a/aea/helpers/acn/uri.py
+++ b/aea/helpers/acn/uri.py
@@ -26,6 +26,8 @@
class Uri:
"""Holds a node address in format "host:port"."""
+ __slots__ = ("_host", "_port")
+
def __init__(
self,
uri: Optional[str] = None,
diff --git a/aea/helpers/async_utils.py b/aea/helpers/async_utils.py
index 3cded28691..9e38a0ac65 100644
--- a/aea/helpers/async_utils.py
+++ b/aea/helpers/async_utils.py
@@ -153,6 +153,7 @@ async def wait(self, state_or_states: Union[Any, Sequence[Any]]) -> Tuple[Any, A
"""Wait state to be set.
:param state_or_states: state or list of states.
+
:return: tuple of previous state and new state.
"""
states = ensure_list(state_or_states)
@@ -453,7 +454,7 @@ def __init__(
"""
if loop and threaded:
raise ValueError(
- "You can not set a loop in threaded mode. A separate loop will be created in each thread."
+ "You can not set a loop in threaded mode. A dedicated loop will be created for each thread."
)
self._loop = loop
self._threaded = threaded
diff --git a/aea/helpers/logging.py b/aea/helpers/logging.py
index f0bcd6692b..7e4c4d6c1d 100644
--- a/aea/helpers/logging.py
+++ b/aea/helpers/logging.py
@@ -51,6 +51,8 @@ def process(
class WithLogger:
"""Interface to endow subclasses with a logger."""
+ __slots__ = ("_logger", "_default_logger_name")
+
def __init__(
self, logger: Optional[Logger] = None, default_logger_name: str = "aea",
) -> None:
diff --git a/aea/helpers/preference_representations/base.py b/aea/helpers/preference_representations/base.py
index 357df7af28..f15267b16b 100644
--- a/aea/helpers/preference_representations/base.py
+++ b/aea/helpers/preference_representations/base.py
@@ -28,20 +28,22 @@
def logarithmic_utility(
utility_params_by_good_id: Dict[str, float],
quantities_by_good_id: Dict[str, int],
- quantity_shift: int = 1,
+ quantity_shift: int = 100,
) -> float:
"""
Compute agent's utility given her utility function params and a good bundle.
:param utility_params_by_good_id: utility params by good identifier
:param quantities_by_good_id: quantities by good identifier
- :param quantity_shift: a non-negative factor to shift the quantities in the utility function (to ensure the natural logarithm can be used on the entire range of quantities)
+ :param quantity_shift: a non-negative factor to shift the quantities in the utility function (to
+ ensure the natural logarithm can be used on the entire range of quantities)
:return: utility value
"""
enforce(
quantity_shift >= 0,
"The quantity_shift argument must be a non-negative integer.",
)
+
goodwise_utility = [
utility_params_by_good_id[good_id] * math.log(quantity + quantity_shift)
if quantity + quantity_shift > 0
diff --git a/aea/helpers/search/generic.py b/aea/helpers/search/generic.py
index a69ee6644a..9aa341db71 100644
--- a/aea/helpers/search/generic.py
+++ b/aea/helpers/search/generic.py
@@ -35,7 +35,7 @@ def __init__(
self, data_model_name: str, data_model_attributes: Dict[str, Any]
) -> None:
"""Initialise the dataModel."""
- self.attributes = [] # type: List[Attribute]
+ attributes = [] # type: List[Attribute]
for values in data_model_attributes.values():
enforce(
values["type"] in SUPPORTED_TYPES.keys(),
@@ -46,7 +46,7 @@ def __init__(
isinstance(values["is_required"], bool),
"Wrong type for is_required. Must be bool!",
)
- self.attributes.append(
+ attributes.append(
Attribute(
name=values["name"], # type: ignore
type_=SUPPORTED_TYPES[values["type"]],
@@ -54,7 +54,7 @@ def __init__(
)
)
- super().__init__(data_model_name, self.attributes)
+ super().__init__(data_model_name, attributes)
AGENT_LOCATION_MODEL = DataModel(
diff --git a/aea/helpers/search/models.py b/aea/helpers/search/models.py
index 8b7ff00dcf..f0b847c1e9 100644
--- a/aea/helpers/search/models.py
+++ b/aea/helpers/search/models.py
@@ -96,6 +96,8 @@
class Location:
"""Data structure to represent locations (i.e. a pair of latitude and longitude)."""
+ __slots__ = ("latitude", "longitude")
+
def __init__(self, latitude: float, longitude: float) -> None:
"""
Initialize a location.
@@ -185,6 +187,8 @@ class Attribute:
Location: models_pb2.Query.Attribute.LOCATION, # type: ignore
}
+ __slots__ = ("name", "type", "is_required", "description")
+
def __init__(
self,
name: str,
@@ -254,6 +258,8 @@ def decode(cls, attribute_pb: models_pb2.Query.Attribute) -> "Attribute": # typ
class DataModel:
"""Implements an OEF data model."""
+ __slots__ = ("name", "attributes", "description")
+
def __init__(
self, name: str, attributes: List[Attribute], description: str = ""
) -> None:
@@ -268,9 +274,13 @@ def __init__(
attributes, key=lambda x: x.name
) # type: List[Attribute]
self._check_validity()
- self.attributes_by_name = {a.name: a for a in self.attributes}
self.description = description
+ @property
+ def attributes_by_name(self) -> Dict[str, Attribute]:
+ """Get the attributes by name."""
+ return {a.name: a for a in self.attributes}
+
def _check_validity(self) -> None:
# check if there are duplicated attribute names
attribute_names = [attribute.name for attribute in self.attributes]
@@ -344,6 +354,8 @@ def generate_data_model(
class Description:
"""Implements an OEF description."""
+ __slots__ = ("_values", "data_model")
+
def __init__(
self,
values: Mapping[str, ATTRIBUTE_TYPES],
@@ -597,6 +609,8 @@ class ConstraintType:
"""
+ __slots__ = ("type", "value")
+
def __init__(self, type_: Union[ConstraintTypes, str], value: Any) -> None:
"""
Initialize a constraint type.
@@ -1115,6 +1129,8 @@ def _decode(constraint_expression_pb: Any) -> "ConstraintExpr":
class And(ConstraintExpr):
"""Implementation of the 'And' constraint expression."""
+ __slots__ = ("constraints",)
+
def __init__(self, constraints: List[ConstraintExpr]) -> None:
"""
Initialize an 'And' expression.
@@ -1189,6 +1205,8 @@ def decode(cls, and_pb: Any) -> "And":
class Or(ConstraintExpr):
"""Implementation of the 'Or' constraint expression."""
+ __slots__ = ("constraints",)
+
def __init__(self, constraints: List[ConstraintExpr]) -> None:
"""
Initialize an 'Or' expression.
@@ -1263,6 +1281,8 @@ def decode(cls, or_pb: Any) -> "Or":
class Not(ConstraintExpr):
"""Implementation of the 'Not' constraint expression."""
+ __slots__ = ("constraint",)
+
def __init__(self, constraint: ConstraintExpr) -> None:
"""
Initialize a 'Not' expression.
@@ -1319,6 +1339,8 @@ def decode(cls, not_pb: Any) -> "Not":
class Constraint(ConstraintExpr):
"""The atomic component of a constraint expression."""
+ __slots__ = ("attribute_name", "constraint_type")
+
def __init__(self, attribute_name: str, constraint_type: ConstraintType) -> None:
"""
Initialize a constraint.
@@ -1482,6 +1504,8 @@ def decode(cls, constraint_pb: Any) -> "Constraint":
class Query:
"""This class lets you build a query for the OEF."""
+ __slots__ = ("constraints", "model")
+
def __init__(
self, constraints: List[ConstraintExpr], model: Optional[DataModel] = None
) -> None:
diff --git a/aea/helpers/transaction/base.py b/aea/helpers/transaction/base.py
index 4bee9e3241..e03e6554c1 100644
--- a/aea/helpers/transaction/base.py
+++ b/aea/helpers/transaction/base.py
@@ -35,6 +35,8 @@
class RawTransaction:
"""This class represents an instance of RawTransaction."""
+ __slots__ = ("_ledger_id", "_body")
+
def __init__(self, ledger_id: str, body: JSONLike,) -> None:
"""Initialise an instance of RawTransaction."""
self._ledger_id = ledger_id
@@ -112,6 +114,8 @@ def __str__(self) -> str:
class RawMessage:
"""This class represents an instance of RawMessage."""
+ __slots__ = ("_ledger_id", "_body", "_is_deprecated_mode")
+
def __init__(
self, ledger_id: str, body: bytes, is_deprecated_mode: bool = False,
) -> None:
@@ -206,6 +210,8 @@ def __str__(self) -> str:
class SignedTransaction:
"""This class represents an instance of SignedTransaction."""
+ __slots__ = ("_ledger_id", "_body")
+
def __init__(self, ledger_id: str, body: JSONLike,) -> None:
"""Initialise an instance of SignedTransaction."""
self._ledger_id = ledger_id
@@ -285,6 +291,8 @@ def __str__(self) -> str:
class SignedMessage:
"""This class represents an instance of RawMessage."""
+ __slots__ = ("_ledger_id", "_body", "_is_deprecated_mode")
+
def __init__(
self, ledger_id: str, body: str, is_deprecated_mode: bool = False,
) -> None:
@@ -379,6 +387,8 @@ def __str__(self) -> str:
class State:
"""This class represents an instance of State."""
+ __slots__ = ("_ledger_id", "_body")
+
def __init__(self, ledger_id: str, body: JSONLike) -> None:
"""Initialise an instance of State."""
self._ledger_id = ledger_id
@@ -447,6 +457,24 @@ def __str__(self) -> str:
class Terms:
"""Class to represent the terms of a multi-currency & multi-token ledger transaction."""
+ __slots__ = (
+ "_ledger_id",
+ "_sender_address",
+ "_counterparty_address",
+ "_amount_by_currency_id",
+ "_quantities_by_good_id",
+ "_is_sender_payable_tx_fee",
+ "_nonce",
+ "_fee_by_currency_id",
+ "_is_strict",
+ "_kwargs",
+ "_good_ids",
+ "_sender_supplied_quantities",
+ "_counterparty_supplied_quantities",
+ "_sender_hash",
+ "_counterparty_hash",
+ )
+
def __init__(
self,
ledger_id: str,
@@ -942,6 +970,8 @@ def __str__(self) -> str:
class TransactionDigest:
"""This class represents an instance of TransactionDigest."""
+ __slots__ = ("_ledger_id", "_body")
+
def __init__(self, ledger_id: str, body: str) -> None:
"""Initialise an instance of TransactionDigest."""
self._ledger_id = ledger_id
@@ -1022,6 +1052,8 @@ def __str__(self) -> str:
class TransactionReceipt:
"""This class represents an instance of TransactionReceipt."""
+ __slots__ = ("_ledger_id", "_receipt", "_transaction")
+
def __init__(
self, ledger_id: str, receipt: JSONLike, transaction: JSONLike
) -> None:
diff --git a/aea/identity/base.py b/aea/identity/base.py
index 63f8db570d..27b24d322f 100644
--- a/aea/identity/base.py
+++ b/aea/identity/base.py
@@ -35,6 +35,8 @@ class Identity:
- the addresses, a map from address identifier to address (can be a single key-value pair)
"""
+ __slots__ = ("_name", "_address", "_addresses", "_default_address_key")
+
def __init__(
self,
name: str,
diff --git a/aea/mail/base.proto b/aea/mail/base.proto
index 5b6d97ee4f..5958ddb2a9 100644
--- a/aea/mail/base.proto
+++ b/aea/mail/base.proto
@@ -1,6 +1,6 @@
syntax = "proto3";
-package aea;
+package aea.base.v0_1_0;
import "google/protobuf/struct.proto";
diff --git a/aea/mail/base.py b/aea/mail/base.py
index eb86672ade..7782dbaad7 100644
--- a/aea/mail/base.py
+++ b/aea/mail/base.py
@@ -20,12 +20,11 @@
import logging
from abc import ABC, abstractmethod
-from typing import Any, Optional, Tuple, Union
+from typing import Any, Optional, Union
from urllib.parse import urlparse
from aea.common import Address
-from aea.configurations.base import PackageId, PublicId
-from aea.configurations.constants import CONNECTION, SKILL
+from aea.configurations.base import PublicId
from aea.exceptions import enforce
from aea.mail import base_pb2
from aea.protocols.base import Message
@@ -34,17 +33,11 @@
_default_logger = logging.getLogger(__name__)
-class AEAConnectionError(Exception):
- """Exception class for connection errors."""
-
-
-class Empty(Exception):
- """Exception for when the inbox is empty."""
-
-
class URI:
"""URI following RFC3986."""
+ __slots__ = ("_uri_raw",)
+
def __init__(self, uri_raw: str) -> None:
"""
Initialize the URI.
@@ -54,175 +47,132 @@ def __init__(self, uri_raw: str) -> None:
:param uri_raw: the raw form uri
:raises ValueError: if uri_raw is not RFC3986 compliant
"""
- self.uri_raw = uri_raw
- parsed = urlparse(uri_raw)
- self._scheme = parsed.scheme
- self._netloc = parsed.netloc
- self._path = parsed.path
- self._params = parsed.params
- self._query = parsed.query
- self._fragment = parsed.fragment
- self._username = parsed.username
- self._password = parsed.password
- self._host = parsed.hostname
- self._port = parsed.port
+ self._uri_raw = uri_raw
@property
def scheme(self) -> str:
"""Get the scheme."""
- return self._scheme
+ parsed = urlparse(self._uri_raw)
+ return parsed.scheme
@property
def netloc(self) -> str:
"""Get the netloc."""
- return self._netloc
+ parsed = urlparse(self._uri_raw)
+ return parsed.netloc
@property
def path(self) -> str:
"""Get the path."""
- return self._path
+ parsed = urlparse(self._uri_raw)
+ return parsed.path
@property
def params(self) -> str:
"""Get the params."""
- return self._params
+ parsed = urlparse(self._uri_raw)
+ return parsed.params
@property
def query(self) -> str:
"""Get the query."""
- return self._query
+ parsed = urlparse(self._uri_raw)
+ return parsed.query
@property
def fragment(self) -> str:
"""Get the fragment."""
- return self._fragment
+ parsed = urlparse(self._uri_raw)
+ return parsed.fragment
@property
def username(self) -> Optional[str]:
"""Get the username."""
- return self._username
+ parsed = urlparse(self._uri_raw)
+ return parsed.username
@property
def password(self) -> Optional[str]:
"""Get the password."""
- return self._password
+ parsed = urlparse(self._uri_raw)
+ return parsed.password
@property
def host(self) -> Optional[str]:
"""Get the host."""
- return self._host
+ parsed = urlparse(self._uri_raw)
+ return parsed.hostname
@property
def port(self) -> Optional[int]:
"""Get the port."""
- return self._port
+ parsed = urlparse(self._uri_raw)
+ return parsed.port
def __str__(self) -> str:
"""Get string representation."""
- return self.uri_raw
+ return self._uri_raw
def __eq__(self, other: Any) -> bool:
"""Compare with another object."""
- return (
- isinstance(other, URI)
- and self.scheme == other.scheme
- and self.netloc == other.netloc
- and self.path == other.path
- and self.params == other.params
- and self.query == other.query
- and self.fragment == other.fragment
- and self.username == other.username
- and self.password == other.password
- and self.host == other.host
- and self.port == other.port
- )
+ return isinstance(other, URI) and str(self) == str(other)
class EnvelopeContext:
- """Extra information for the handling of an envelope."""
+ """Contains context information of an envelope."""
+
+ __slots__ = ("_connection_id", "_uri")
def __init__(
- self,
- connection_id: Optional[PublicId] = None,
- skill_id: Optional[PublicId] = None,
- uri: Optional[URI] = None,
+ self, connection_id: Optional[PublicId] = None, uri: Optional[URI] = None,
) -> None:
"""
Initialize the envelope context.
:param connection_id: the connection id used for routing the outgoing envelope in the multiplexer.
- :param skill_id: the skill id used for routing the incoming envelope in the AEA.
:param uri: the URI sent with the envelope.
"""
- skill_id_from_uri, connection_id_from_uri = (
- self._get_public_ids_from_uri(uri) if uri is not None else (None, None)
- )
- if connection_id_from_uri and connection_id:
- raise ValueError("Cannot define connection_id explicitly and in URI.")
- self._connection_id = connection_id or connection_id_from_uri
- if skill_id_from_uri and skill_id:
- raise ValueError("Cannot define skill_id explicitly and in URI.")
- self._skill_id = skill_id or skill_id_from_uri
- self.uri = uri
+ self._connection_id = connection_id
+ self._uri = uri
@property
- def connection_id(self) -> Optional[PublicId]:
- """Get the connection id."""
- return self._connection_id
+ def uri(self) -> Optional[URI]:
+ """Get the URI."""
+ return self._uri
@property
- def skill_id(self) -> Optional[PublicId]:
- """Get the skill id."""
- return self._skill_id
-
- @property
- def uri_raw(self) -> str:
- """Get uri in string format."""
- return str(self.uri) if self.uri is not None else ""
-
- @staticmethod
- def _get_public_ids_from_uri(
- uri: URI,
- ) -> Tuple[Optional[PublicId], Optional[PublicId]]:
- """
- Try get skill and connection id from uri.
+ def connection_id(self) -> Optional[PublicId]:
+ """Get the connection id to route the envelope."""
+ return self._connection_id
- :param uri: the uri
- :return: (skill_id if present in uri, connection if present in uri)
- """
- skill_id = None
- connection_id = None
- try:
- package_id = PackageId.from_uri_path(uri.path)
- package_type = str(package_id.package_type)
- if package_type == SKILL:
- skill_id = package_id.public_id
- elif package_type == CONNECTION:
- connection_id = package_id.public_id
- else:
- raise ValueError(
- f"Invalid package type {package_type} in uri for envelope context."
- )
- except ValueError as e:
- _default_logger.debug(
- f"URI - {uri.path} - not a valid package_id id. Error: {e}"
- )
- return (skill_id, connection_id)
+ @connection_id.setter
+ def connection_id(self, connection_id: PublicId) -> None:
+ """Set the 'via' connection id."""
+ if self._connection_id is not None:
+ raise ValueError("connection_id already set!") # pragma: nocover
+ self._connection_id = connection_id
def __str__(self) -> str:
"""Get the string representation."""
- return f"EnvelopeContext(connection_id={self.connection_id}, skill_id={self.skill_id}, uri_raw={self.uri_raw})"
+ return f"EnvelopeContext(connection_id={self.connection_id}, uri={self.uri})"
def __eq__(self, other: Any) -> bool:
"""Compare with another object."""
return (
isinstance(other, EnvelopeContext)
and self.connection_id == other.connection_id
- and self.skill_id == other.skill_id
and self.uri == other.uri
)
+class AEAConnectionError(Exception):
+ """Exception class for connection errors."""
+
+
+class Empty(Exception):
+ """Exception for when the inbox is empty."""
+
+
class EnvelopeSerializer(ABC):
"""Abstract class to specify the serialization layer for the envelope."""
@@ -260,8 +210,8 @@ def encode(self, envelope: "Envelope") -> bytes:
envelope_pb.sender = envelope.sender
envelope_pb.protocol_id = str(envelope.protocol_specification_id)
envelope_pb.message = envelope.message_bytes
- if envelope.context is not None and envelope.context.uri_raw != "":
- envelope_pb.uri = envelope.context.uri_raw
+ if envelope.context is not None and envelope.context.uri is not None:
+ envelope_pb.uri = str(envelope.context.uri)
envelope_bytes = envelope_pb.SerializeToString()
return envelope_bytes
@@ -314,6 +264,8 @@ class Envelope:
default_serializer = DefaultEnvelopeSerializer()
+ __slots__ = ("_to", "_sender", "_protocol_specification_id", "_message", "_context")
+
def __init__(
self,
to: Address,
@@ -346,6 +298,11 @@ def __init__(
self._to = to
self._sender = sender
+ enforce(
+ self.is_to_public_id == self.is_sender_public_id,
+ "To and sender must either both be agent addresses or both be public ids of AEA components.",
+ )
+
if isinstance(message, bytes):
if protocol_specification_id is None:
raise ValueError(
@@ -368,7 +325,12 @@ def __init__(
self._protocol_specification_id: PublicId = protocol_specification_id
self._message = message
- self._context = context if context is not None else EnvelopeContext()
+ if self.is_component_to_component_message:
+ enforce(
+ context is None,
+ "EnvelopeContext must be None for component to component messages.",
+ )
+ self._context = context
@property
def to(self) -> Address:
@@ -417,33 +379,14 @@ def message_bytes(self) -> bytes:
return self._message
@property
- def context(self) -> EnvelopeContext:
+ def context(self) -> Optional[EnvelopeContext]:
"""Get the envelope context."""
return self._context
@property
- def skill_id(self) -> Optional[PublicId]:
- """
- Get the skill id from an envelope context, if set.
-
- :return: skill id
- """
- skill_id = None # Optional[PublicId]
- if self.context is not None:
- skill_id = self.context.skill_id
- return skill_id
-
- @property
- def connection_id(self) -> Optional[PublicId]:
- """
- Get the connection id from an envelope context, if set.
-
- :return: connection id
- """
- connection_id = None # Optional[PublicId]
- if self.context is not None:
- connection_id = self.context.connection_id
- return connection_id
+ def to_as_public_id(self) -> Optional[PublicId]:
+ """Get to as public id."""
+ return PublicId.try_from_str(self.to)
@property
def is_sender_public_id(self) -> bool:
@@ -455,6 +398,11 @@ def is_to_public_id(self) -> bool:
"""Check if to is a public id."""
return PublicId.is_valid_str(self.to)
+ @property
+ def is_component_to_component_message(self) -> bool:
+ """Whether or not the message contained is component to component."""
+ return self.is_to_public_id and self.is_sender_public_id
+
@staticmethod
def _check_consistency(message: Message, to: str, sender: str) -> Message:
"""Check consistency of sender and to."""
diff --git a/aea/mail/base_pb2.py b/aea/mail/base_pb2.py
index 9c36ba2092..281d3b9f77 100644
--- a/aea/mail/base_pb2.py
+++ b/aea/mail/base_pb2.py
@@ -1,9 +1,7 @@
+# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: base.proto
-import sys
-
-_b = sys.version_info[0] < 3 and (lambda x: x) or (lambda x: x.encode("latin1"))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
@@ -16,28 +14,27 @@
from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2
+
DESCRIPTOR = _descriptor.FileDescriptor(
name="base.proto",
- package="aea",
+ package="aea.base.v0_1_0",
syntax="proto3",
serialized_options=None,
- serialized_pb=_b(
- '\n\nbase.proto\x12\x03\x61\x65\x61\x1a\x1cgoogle/protobuf/struct.proto"\x90\x01\n\x0f\x44ialogueMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12"\n\x1a\x64ialogue_starter_reference\x18\x02 \x01(\t\x12$\n\x1c\x64ialogue_responder_reference\x18\x03 \x01(\t\x12\x0e\n\x06target\x18\x04 \x01(\x05\x12\x0f\n\x07\x63ontent\x18\x05 \x01(\x0c"o\n\x07Message\x12\'\n\x04\x62ody\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x12\x30\n\x10\x64ialogue_message\x18\x02 \x01(\x0b\x32\x14.aea.DialogueMessageH\x00\x42\t\n\x07message"Y\n\x08\x45nvelope\x12\n\n\x02to\x18\x01 \x01(\t\x12\x0e\n\x06sender\x18\x02 \x01(\t\x12\x13\n\x0bprotocol_id\x18\x03 \x01(\t\x12\x0f\n\x07message\x18\x04 \x01(\x0c\x12\x0b\n\x03uri\x18\x05 \x01(\tb\x06proto3'
- ),
+ serialized_pb=b'\n\nbase.proto\x12\x0f\x61\x65\x61.base.v0_1_0\x1a\x1cgoogle/protobuf/struct.proto"\x90\x01\n\x0f\x44ialogueMessage\x12\x12\n\nmessage_id\x18\x01 \x01(\x05\x12"\n\x1a\x64ialogue_starter_reference\x18\x02 \x01(\t\x12$\n\x1c\x64ialogue_responder_reference\x18\x03 \x01(\t\x12\x0e\n\x06target\x18\x04 \x01(\x05\x12\x0f\n\x07\x63ontent\x18\x05 \x01(\x0c"{\n\x07Message\x12\'\n\x04\x62ody\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructH\x00\x12<\n\x10\x64ialogue_message\x18\x02 \x01(\x0b\x32 .aea.base.v0_1_0.DialogueMessageH\x00\x42\t\n\x07message"Y\n\x08\x45nvelope\x12\n\n\x02to\x18\x01 \x01(\t\x12\x0e\n\x06sender\x18\x02 \x01(\t\x12\x13\n\x0bprotocol_id\x18\x03 \x01(\t\x12\x0f\n\x07message\x18\x04 \x01(\x0c\x12\x0b\n\x03uri\x18\x05 \x01(\tb\x06proto3',
dependencies=[google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,],
)
_DIALOGUEMESSAGE = _descriptor.Descriptor(
name="DialogueMessage",
- full_name="aea.DialogueMessage",
+ full_name="aea.base.v0_1_0.DialogueMessage",
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name="message_id",
- full_name="aea.DialogueMessage.message_id",
+ full_name="aea.base.v0_1_0.DialogueMessage.message_id",
index=0,
number=1,
type=5,
@@ -55,14 +52,14 @@
),
_descriptor.FieldDescriptor(
name="dialogue_starter_reference",
- full_name="aea.DialogueMessage.dialogue_starter_reference",
+ full_name="aea.base.v0_1_0.DialogueMessage.dialogue_starter_reference",
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
- default_value=_b("").decode("utf-8"),
+ default_value=b"".decode("utf-8"),
message_type=None,
enum_type=None,
containing_type=None,
@@ -73,14 +70,14 @@
),
_descriptor.FieldDescriptor(
name="dialogue_responder_reference",
- full_name="aea.DialogueMessage.dialogue_responder_reference",
+ full_name="aea.base.v0_1_0.DialogueMessage.dialogue_responder_reference",
index=2,
number=3,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
- default_value=_b("").decode("utf-8"),
+ default_value=b"".decode("utf-8"),
message_type=None,
enum_type=None,
containing_type=None,
@@ -91,7 +88,7 @@
),
_descriptor.FieldDescriptor(
name="target",
- full_name="aea.DialogueMessage.target",
+ full_name="aea.base.v0_1_0.DialogueMessage.target",
index=3,
number=4,
type=5,
@@ -109,14 +106,14 @@
),
_descriptor.FieldDescriptor(
name="content",
- full_name="aea.DialogueMessage.content",
+ full_name="aea.base.v0_1_0.DialogueMessage.content",
index=4,
number=5,
type=12,
cpp_type=9,
label=1,
has_default_value=False,
- default_value=_b(""),
+ default_value=b"",
message_type=None,
enum_type=None,
containing_type=None,
@@ -134,21 +131,21 @@
syntax="proto3",
extension_ranges=[],
oneofs=[],
- serialized_start=50,
- serialized_end=194,
+ serialized_start=62,
+ serialized_end=206,
)
_MESSAGE = _descriptor.Descriptor(
name="Message",
- full_name="aea.Message",
+ full_name="aea.base.v0_1_0.Message",
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name="body",
- full_name="aea.Message.body",
+ full_name="aea.base.v0_1_0.Message.body",
index=0,
number=1,
type=11,
@@ -166,7 +163,7 @@
),
_descriptor.FieldDescriptor(
name="dialogue_message",
- full_name="aea.Message.dialogue_message",
+ full_name="aea.base.v0_1_0.Message.dialogue_message",
index=1,
number=2,
type=11,
@@ -193,34 +190,34 @@
oneofs=[
_descriptor.OneofDescriptor(
name="message",
- full_name="aea.Message.message",
+ full_name="aea.base.v0_1_0.Message.message",
index=0,
containing_type=None,
fields=[],
),
],
- serialized_start=196,
- serialized_end=307,
+ serialized_start=208,
+ serialized_end=331,
)
_ENVELOPE = _descriptor.Descriptor(
name="Envelope",
- full_name="aea.Envelope",
+ full_name="aea.base.v0_1_0.Envelope",
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name="to",
- full_name="aea.Envelope.to",
+ full_name="aea.base.v0_1_0.Envelope.to",
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
- default_value=_b("").decode("utf-8"),
+ default_value=b"".decode("utf-8"),
message_type=None,
enum_type=None,
containing_type=None,
@@ -231,14 +228,14 @@
),
_descriptor.FieldDescriptor(
name="sender",
- full_name="aea.Envelope.sender",
+ full_name="aea.base.v0_1_0.Envelope.sender",
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
- default_value=_b("").decode("utf-8"),
+ default_value=b"".decode("utf-8"),
message_type=None,
enum_type=None,
containing_type=None,
@@ -249,14 +246,14 @@
),
_descriptor.FieldDescriptor(
name="protocol_id",
- full_name="aea.Envelope.protocol_id",
+ full_name="aea.base.v0_1_0.Envelope.protocol_id",
index=2,
number=3,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
- default_value=_b("").decode("utf-8"),
+ default_value=b"".decode("utf-8"),
message_type=None,
enum_type=None,
containing_type=None,
@@ -267,14 +264,14 @@
),
_descriptor.FieldDescriptor(
name="message",
- full_name="aea.Envelope.message",
+ full_name="aea.base.v0_1_0.Envelope.message",
index=3,
number=4,
type=12,
cpp_type=9,
label=1,
has_default_value=False,
- default_value=_b(""),
+ default_value=b"",
message_type=None,
enum_type=None,
containing_type=None,
@@ -285,14 +282,14 @@
),
_descriptor.FieldDescriptor(
name="uri",
- full_name="aea.Envelope.uri",
+ full_name="aea.base.v0_1_0.Envelope.uri",
index=4,
number=5,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
- default_value=_b("").decode("utf-8"),
+ default_value=b"".decode("utf-8"),
message_type=None,
enum_type=None,
containing_type=None,
@@ -310,8 +307,8 @@
syntax="proto3",
extension_ranges=[],
oneofs=[],
- serialized_start=309,
- serialized_end=398,
+ serialized_start=333,
+ serialized_end=422,
)
_MESSAGE.fields_by_name[
@@ -334,33 +331,33 @@
DialogueMessage = _reflection.GeneratedProtocolMessageType(
"DialogueMessage",
(_message.Message,),
- dict(
- DESCRIPTOR=_DIALOGUEMESSAGE,
- __module__="base_pb2"
- # @@protoc_insertion_point(class_scope:aea.DialogueMessage)
- ),
+ {
+ "DESCRIPTOR": _DIALOGUEMESSAGE,
+ "__module__": "base_pb2"
+ # @@protoc_insertion_point(class_scope:aea.base.v0_1_0.DialogueMessage)
+ },
)
_sym_db.RegisterMessage(DialogueMessage)
Message = _reflection.GeneratedProtocolMessageType(
"Message",
(_message.Message,),
- dict(
- DESCRIPTOR=_MESSAGE,
- __module__="base_pb2"
- # @@protoc_insertion_point(class_scope:aea.Message)
- ),
+ {
+ "DESCRIPTOR": _MESSAGE,
+ "__module__": "base_pb2"
+ # @@protoc_insertion_point(class_scope:aea.base.v0_1_0.Message)
+ },
)
_sym_db.RegisterMessage(Message)
Envelope = _reflection.GeneratedProtocolMessageType(
"Envelope",
(_message.Message,),
- dict(
- DESCRIPTOR=_ENVELOPE,
- __module__="base_pb2"
- # @@protoc_insertion_point(class_scope:aea.Envelope)
- ),
+ {
+ "DESCRIPTOR": _ENVELOPE,
+ "__module__": "base_pb2"
+ # @@protoc_insertion_point(class_scope:aea.base.v0_1_0.Envelope)
+ },
)
_sym_db.RegisterMessage(Envelope)
diff --git a/aea/manager/manager.py b/aea/manager/manager.py
index 2879a3c1f4..16f55e4a3f 100644
--- a/aea/manager/manager.py
+++ b/aea/manager/manager.py
@@ -22,6 +22,7 @@
import os
import threading
from asyncio.tasks import FIRST_COMPLETED
+from collections import defaultdict
from shutil import rmtree
from threading import Thread
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
@@ -33,6 +34,19 @@
from aea.manager.project import AgentAlias, Project
+class ProjectNotFoundError(ValueError):
+ """Project not found exception."""
+
+
+class ProjectCheckError(ValueError):
+ """Project check error exception."""
+
+ def __init__(self, msg: str, source_exception: Exception):
+ """Init exception."""
+ super().__init__(msg)
+ self.source_exception = source_exception
+
+
class AgentRunAsyncTask:
"""Async task wrapper for agent."""
@@ -138,13 +152,20 @@ def __init__(
working_dir: str,
mode: str = "async",
registry_path: str = DEFAULT_REGISTRY_NAME,
+ auto_add_remove_project: bool = False,
) -> None:
"""
Initialize manager.
:param working_dir: directory to store base agents.
+ :param mode: str. async or threaded
+ :param registry_path: str. path to the local packages registry
+ :param auto_add_remove_project: bool. add/remove project on the first agent add/last agent remove
+
+ :return: None
"""
self.working_dir = working_dir
+ self._auto_add_remove_project = auto_add_remove_project
self._save_path = os.path.join(self.working_dir, self.SAVE_FILENAME)
self.registry_path = registry_path
@@ -163,6 +184,13 @@ def __init__(
self._event: Optional[asyncio.Event] = None
self._error_callbacks: List[Callable[[str, BaseException], None]] = []
+ self._last_start_status: Optional[
+ Tuple[
+ bool,
+ Dict[PublicId, List[Dict]],
+ List[Tuple[PublicId, List[Dict], Exception]],
+ ]
+ ] = None
if mode not in self.MODES:
raise ValueError(
@@ -249,13 +277,14 @@ def start_manager(
:param local: whether or not to fetch from local registry.
:param remote: whether or not to fetch from remote registry.
+
:return: the MultiAgentManager instance.
"""
if self._is_running:
return self
self._ensure_working_dir()
- self._load_state(local=local, remote=remote)
+ self._last_start_status = self._load_state(local=local, remote=remote)
self._started_event.clear()
self._is_running = True
@@ -264,6 +293,17 @@ def start_manager(
self._started_event.wait(self.DEFAULT_TIMEOUT_FOR_BLOCKING_OPERATIONS)
return self
+ @property
+ def last_start_status(
+ self,
+ ) -> Tuple[
+ bool, Dict[PublicId, List[Dict]], List[Tuple[PublicId, List[Dict], Exception]],
+ ]:
+ """Get status of the last agents start loading state."""
+ if self._last_start_status is None:
+ raise ValueError("Manager was not started")
+ return self._last_start_status
+
def stop_manager(
self, cleanup: bool = True, save: bool = False
) -> "MultiAgentManager":
@@ -292,7 +332,7 @@ def stop_manager(
self._save_state()
for agent_name in self.list_agents():
- self.remove_agent(agent_name)
+ self.remove_agent(agent_name, skip_project_auto_remove=True)
if cleanup:
for project in list(self._projects.keys()):
@@ -332,6 +372,7 @@ def add_project(
registry, and then from remote registry in case of failure).
:param public_id: the public if of the agent project.
+
:param local: whether or not to fetch from local registry.
:param remote: whether or not to fetch from remote registry.
:param restore: bool flag for restoring already fetched agent.
@@ -356,6 +397,14 @@ def add_project(
project.install_pypi_dependencies()
project.build()
+ try:
+ project.check()
+ except Exception as e:
+ project.remove()
+ raise ProjectCheckError(
+ f"Failed to load project: {public_id} Error: {str(e)}", e
+ )
+
self._projects[public_id] = project
return self
@@ -392,6 +441,9 @@ def add_agent(
agent_name: Optional[str] = None,
agent_overrides: Optional[dict] = None,
component_overrides: Optional[List[dict]] = None,
+ local: bool = False,
+ remote: bool = False,
+ restore: bool = False,
) -> "MultiAgentManager":
"""
Create new agent configuration based on project with config overrides applied.
@@ -404,6 +456,10 @@ def add_agent(
:param component_overrides: overrides for component section.
:param config: agent config (used for agent re-creation).
+ :param local: whether or not to fetch from local registry.
+ :param remote: whether or not to fetch from remote registry.
+ :param restore: bool flag for restoring already fetched agent.
+
:return: manager
"""
agent_name = agent_name or public_id.name
@@ -411,10 +467,14 @@ def add_agent(
if agent_name in self._agents:
raise ValueError(f"Agent with name {agent_name} already exists!")
- if public_id not in self._projects:
- raise ValueError(f"{public_id} project is not added!")
+ project = self._projects.get(public_id, None)
- project = self._projects[public_id]
+ if project is None and self._auto_add_remove_project:
+ self.add_project(public_id, local, remote, restore)
+ project = self._projects.get(public_id, None)
+
+ if project is None:
+ raise ProjectNotFoundError(f"{public_id} project is not added!")
agent_alias = AgentAlias(
project=project,
@@ -524,11 +584,14 @@ def list_agents(self, running_only: bool = False) -> List[str]:
return [i for i in self._agents.keys() if self._is_agent_running(i)]
return list(self._agents.keys())
- def remove_agent(self, agent_name: str) -> "MultiAgentManager":
+ def remove_agent(
+ self, agent_name: str, skip_project_auto_remove: bool = False
+ ) -> "MultiAgentManager":
"""
Remove agent alias definition from registry.
:param agent_name: agent name to remove
+ :param skip_project_auto_remove: disable auto project remove on last agent removed.
:return: None
"""
@@ -540,6 +603,15 @@ def remove_agent(self, agent_name: str) -> "MultiAgentManager":
agent_alias = self._agents.pop(agent_name)
agent_alias.remove_from_project()
+ project: Project = agent_alias.project
+
+ if (
+ not project.agents
+ and self._auto_add_remove_project
+ and not skip_project_auto_remove
+ ):
+ self.remove_project(project.public_id, keep_files=False)
+
return self
def start_agent(self, agent_name: str) -> "MultiAgentManager":
@@ -692,7 +764,11 @@ def _ensure_working_dir(self) -> None:
if not os.path.exists(self.data_dir):
os.makedirs(self.data_dir)
- def _load_state(self, local: bool, remote: bool) -> None:
+ def _load_state(
+ self, local: bool, remote: bool
+ ) -> Tuple[
+ bool, Dict[PublicId, List[Dict]], List[Tuple[PublicId, List[Dict], Exception]],
+ ]:
"""
Load saved state from file.
@@ -709,32 +785,44 @@ def _load_state(self, local: bool, remote: bool) -> None:
:raises: ValueError if failed to load state.
"""
if not os.path.exists(self._save_path):
- return
+ return False, {}, []
save_json = {}
with open_file(self._save_path) as f:
save_json = json.load(f)
if not save_json:
- return # pragma: nocover
+ return False, {}, [] # pragma: nocover
- try:
- for public_id in save_json["projects"]:
+ projects_agents: Dict[PublicId, List] = defaultdict(list)
+
+ for agent_settings in save_json["agents"]:
+ projects_agents[PublicId.from_str(agent_settings["public_id"])].append(
+ agent_settings
+ )
+
+ failed_to_load: List[Tuple[PublicId, List[Dict], Exception]] = []
+ loaded_ok: Dict[PublicId, List[Dict]] = {}
+ for project_public_id, agents_settings in projects_agents.items():
+ try:
self.add_project(
- PublicId.from_str(public_id),
- local=local,
- remote=remote,
- restore=True,
+ project_public_id, local=local, remote=remote, restore=True,
)
+ except ProjectCheckError as e:
+ failed_to_load.append((project_public_id, agents_settings, e))
+ break
+
+ for agent_settings in agents_settings:
- for agent_settings in save_json["agents"]:
self.add_agent_with_config(
public_id=PublicId.from_str(agent_settings["public_id"]),
agent_name=agent_settings["agent_name"],
config=agent_settings["config"],
)
- except ValueError as e: # pragma: nocover
- raise ValueError(f"Failed to load state. {e}")
+
+ loaded_ok[project_public_id] = agents_settings
+
+ return True, loaded_ok, failed_to_load
def _save_state(self) -> None:
"""
diff --git a/aea/manager/project.py b/aea/manager/project.py
index 8bab91ff5c..8294d74d03 100644
--- a/aea/manager/project.py
+++ b/aea/manager/project.py
@@ -41,7 +41,9 @@ class _Base:
@classmethod
def _get_agent_config(cls, path: Union[Path, str]) -> AgentConfig:
"""Get agent config instance."""
- return AEABuilder.try_to_load_agent_configuration_file(path)
+ agent_config = AEABuilder.try_to_load_agent_configuration_file(path)
+ agent_config.check_aea_version()
+ return agent_config
@classmethod
def _get_builder(
@@ -72,6 +74,8 @@ def install_pypi_dependencies(self) -> None:
class Project(_Base):
"""Agent project representation."""
+ __slots__ = ("public_id", "path", "agents")
+
def __init__(self, public_id: PublicId, path: str) -> None:
"""Init project with public_id and project's path."""
self.public_id: PublicId = public_id
@@ -126,10 +130,16 @@ def builder(self) -> AEABuilder:
"""Get builder instance."""
return self._get_builder(self._get_agent_config(self.path), self.path)
+ def check(self) -> None:
+ """Check we can still construct an AEA from the project with builder.build."""
+ _ = self.builder
+
class AgentAlias(_Base):
"""Agent alias representation."""
+ __slots__ = ("project", "agent_name", "_data_dir", "_agent_config")
+
def __init__(
self, project: Project, agent_name: str, data_dir: str,
):
diff --git a/aea/multiplexer.py b/aea/multiplexer.py
index c336af4d96..1817778b95 100644
--- a/aea/multiplexer.py
+++ b/aea/multiplexer.py
@@ -37,6 +37,7 @@
cast,
)
+from aea.common import Address
from aea.configurations.base import PublicId
from aea.connections.base import Connection, ConnectionStates
from aea.exceptions import enforce
@@ -141,6 +142,7 @@ def __init__(
self._specification_id_to_protocol_id = {
p.protocol_specification_id: p.protocol_id for p in protocols or []
}
+ self._routing_helper: Dict[Address, PublicId] = {}
self._in_queue = AsyncFriendlyQueue() # type: AsyncFriendlyQueue
self._out_queue = None # type: Optional[asyncio.Queue]
@@ -482,10 +484,7 @@ async def _send_loop(self) -> None:
)
return None
self.logger.debug("Sending envelope {}".format(str(envelope)))
- try:
- await self._send(envelope)
- except AEAConnectionError as e:
- self.logger.error(str(e))
+ await self._send(envelope)
except asyncio.CancelledError:
self.logger.debug("Sending loop cancelled.")
@@ -509,12 +508,13 @@ async def _receiving_loop(self) -> None:
# process completed receiving tasks.
for task in done:
+ connection = task_to_connection.pop(task)
envelope = task.result()
if envelope is not None:
+ self._update_routing_helper(envelope, connection)
self.in_queue.put_nowait(envelope)
# reinstantiate receiving task, but only if the connection is still up.
- connection = task_to_connection.pop(task)
if connection.is_connected:
new_task = asyncio.ensure_future(connection.receive())
task_to_connection[new_task] = connection
@@ -538,36 +538,18 @@ async def _send(self, envelope: Envelope) -> None:
:param envelope: the envelope to send.
:return: None
:raises ValueError: if the connection id provided is not valid.
- :raises AEAConnectionError: if the connection id provided is not valid.
"""
- connection_id = None # type: Optional[PublicId]
- envelope_context = envelope.context
- # first, try to route by context
- if envelope_context is not None:
- connection_id = envelope_context.connection_id
-
envelope_protocol_id = self._get_protocol_id_for_envelope(envelope)
+ connection_id = self._get_connection_id_from_envelope(
+ envelope, envelope_protocol_id
+ )
- # second, try to route by default routing
- if connection_id is None and envelope_protocol_id in self.default_routing:
- connection_id = self.default_routing[envelope_protocol_id]
- self.logger.debug("Using default routing: {}".format(connection_id))
-
- if connection_id is not None and connection_id not in self._id_to_connection:
- raise AEAConnectionError(
- "No connection registered with id: {}.".format(connection_id)
- )
-
- # third, if no other option route by default connection
- if connection_id is None:
- self.logger.debug(
- "Using default connection: {}".format(self.default_connection)
- )
- connection = self.default_connection
- else:
- connection = self._id_to_connection[connection_id]
+ connection = (
+ self._get_connection(connection_id) if connection_id is not None else None
+ )
if connection is None:
+ # we don't raise on dropping envelope as this can be a configuration issue only!
self.logger.warning(
f"Dropping envelope, no connection available for sending: {envelope}"
)
@@ -581,6 +563,79 @@ async def _send(self, envelope: Envelope) -> None:
except Exception as e: # pylint: disable=broad-except
self._handle_exception(self._send, e)
+ def _get_connection_id_from_envelope(
+ self, envelope: Envelope, envelope_protocol_id: PublicId
+ ) -> Optional[PublicId]:
+ """
+ Get the connection id from an envelope.
+
+ Applies the following rules:
+ - component to component messages are routed by their component id
+ - agent to agent messages, are routed following four rules:
+ * first, try to route by envelope context connection id
+ * second, try to route by routing helper
+ * third, try to route by default routing
+ * forth, using default connection
+
+ :param envelope: the Envelope
+ :param envelope_protocol_id: the protocol id of the message contained in the envelope
+ :return: public id if found
+ """
+ # component to component messages are routed by their component id
+ if envelope.is_component_to_component_message:
+ connection_id = envelope.to_as_public_id
+ self.logger.debug(
+ "Using envelope `to` field as connection_id: {}".format(connection_id)
+ )
+ enforce(
+ connection_id is not None,
+ "Connection id cannot be None by envelope construction.",
+ )
+ return connection_id
+
+ # agent to agent messages, are routed following four rules:
+ # first, try to route by envelope context connection id
+ if envelope.context is not None and envelope.context.connection_id is not None:
+ connection_id = envelope.context.connection_id
+ self.logger.debug(
+ "Using envelope context connection_id: {}".format(connection_id)
+ )
+ return connection_id
+
+ # second, try to route by routing helper
+ if envelope.to in self._routing_helper:
+ connection_id = self._routing_helper[envelope.to]
+ self.logger.debug(
+ "Using routing helper with connection_id: {}".format(connection_id)
+ )
+ return connection_id
+
+ # third, try to route by default routing
+ if envelope_protocol_id in self.default_routing:
+ connection_id = self.default_routing[envelope_protocol_id]
+ self.logger.debug("Using default routing: {}".format(connection_id))
+ return connection_id
+
+ # forth, using default connection
+ connection_id = (
+ self.default_connection.connection_id
+ if self.default_connection is not None
+ else None
+ )
+ self.logger.debug("Using default connection: {}".format(connection_id))
+ return connection_id
+
+ def _get_connection(self, connection_id: PublicId) -> Optional[Connection]:
+ """Check if the connection id is registered."""
+ conn_ = self._id_to_connection.get(connection_id, None)
+ if conn_ is not None:
+ return conn_
+ for id_, conn_ in self._id_to_connection.items():
+ if id_.same_prefix(connection_id):
+ return conn_
+ self.logger.error(f"No connection registered with id: {connection_id}")
+ return None
+
def _is_connection_supported_protocol(
self, connection: Connection, protocol_id: PublicId
) -> bool:
@@ -686,10 +741,28 @@ def _setup(
for c in connections:
self.add_connection(c, c.public_id == default_connection)
+ def _update_routing_helper(
+ self, envelope: Envelope, connection: Connection
+ ) -> None:
+ """
+ Update the routing helper.
+
+ Saves the source (connection) of an agent-to-agent envelope.
+
+ :param envelope: the envelope to be updated
+ :param connection: the connection
+ """
+ if envelope.is_component_to_component_message:
+ return
+ self._routing_helper[envelope.sender] = connection.public_id
+
class Multiplexer(AsyncMultiplexer):
"""Transit sync multiplexer for compatibility."""
+ _thread_was_started: bool
+ _is_connected: bool
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""
Initialize the connection multiplexer.
@@ -702,6 +775,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
"""
super().__init__(*args, **kwargs)
self._sync_lock = threading.Lock()
+ self._init()
+
+ def _init(self) -> None:
+ """Set initial variables."""
self._thread_was_started = False
self._is_connected = False
@@ -748,8 +825,13 @@ def disconnect(self) -> None: # type: ignore # cause overrides coroutine # pyli
if self._thread_runner.is_alive() and self._thread_was_started:
self._thread_runner.stop()
self.logger.debug("Thread stopped")
+
self.logger.debug("Disconnected")
+ # reset thread runner and init variables
+ self._init()
+ self.set_loop(self._loop)
+
def put(self, envelope: Envelope) -> None: # type: ignore # cause overrides coroutine
"""
Schedule an envelope for sending it.
diff --git a/aea/protocols/base.py b/aea/protocols/base.py
index 23960d485d..c43d1b7925 100644
--- a/aea/protocols/base.py
+++ b/aea/protocols/base.py
@@ -334,6 +334,8 @@ class Protocol(Component):
It includes a serializer to encode/decode a message.
"""
+ __slots__ = ("_message_class",)
+
def __init__(
self, configuration: ProtocolConfig, message_class: Type[Message], **kwargs: Any
) -> None:
diff --git a/aea/protocols/dialogue/base.py b/aea/protocols/dialogue/base.py
index 9d79740e0d..a7563165b8 100644
--- a/aea/protocols/dialogue/base.py
+++ b/aea/protocols/dialogue/base.py
@@ -16,7 +16,6 @@
# limitations under the License.
#
# ------------------------------------------------------------------------------
-
"""
This module contains the classes required for dialogue management.
@@ -132,11 +131,7 @@ def dialogue_starter_addr(self) -> str:
def __eq__(self, other: Any) -> bool:
"""Check for equality between two DialogueLabel objects."""
if isinstance(other, DialogueLabel):
- return (
- self.dialogue_reference == other.dialogue_reference
- and self.dialogue_starter_addr == other.dialogue_starter_addr
- and self.dialogue_opponent_addr == other.dialogue_opponent_addr
- )
+ return hash(self) == hash(other)
return False
def __hash__(self) -> int:
@@ -211,8 +206,7 @@ class _DialogueMeta(type):
"""
Metaclass for Dialogue.
- Adds slot support forevery subclass
- Creates classlevvel Rules instance
+ Creates class level Rules instance to share among instances
"""
def __new__(cls, name: str, bases: Tuple[Type], dct: Dict) -> "_DialogueMeta":
@@ -241,6 +235,18 @@ class Dialogue(metaclass=_DialogueMeta):
dict()
) # type: Dict[Message.Performative, FrozenSet[Message.Performative]]
+ __slots__ = (
+ "_self_address",
+ "_dialogue_label",
+ "_role",
+ "_message_class",
+ "_outgoing_messages",
+ "_incoming_messages",
+ "_terminal_state_callbacks",
+ "_last_message_id",
+ "_ordered_message_ids",
+ )
+
class Rules:
"""This class defines the rules for the dialogue."""
@@ -443,11 +449,11 @@ def incomplete_dialogue_label(self) -> DialogueLabel:
@property
def dialogue_labels(self) -> Set[DialogueLabel]:
"""
- Get the dialogue labels (incomplete and complete, if it exists)
+ Get the dialogue labels (incomplete and complete, if it exists).
:return: the dialogue labels
"""
- return {self._dialogue_label, self.incomplete_dialogue_label}
+ return {self.dialogue_label, self.incomplete_dialogue_label}
@property
def self_address(self) -> Address:
@@ -689,7 +695,10 @@ def reply(
if last_message is None:
raise ValueError("Cannot reply in an empty dialogue!")
- if target_message is None and target is None:
+ if target_message is None and target is not None:
+ target_message = self.get_message_by_id(target)
+ elif target_message is None and target is None:
+ target_message = last_message
target = last_message.message_id
elif target_message is not None and target is None:
target = target_message.message_id
@@ -699,6 +708,8 @@ def reply(
"The provided target and target_message do not match."
)
+ if target_message is None:
+ raise ValueError("No target message found!")
enforce(
self._has_message_id(target), # type: ignore
"The target message does not exist in this dialogue.",
diff --git a/aea/protocols/generator/base.py b/aea/protocols/generator/base.py
index 7d3b87c1d9..6bd39273f1 100644
--- a/aea/protocols/generator/base.py
+++ b/aea/protocols/generator/base.py
@@ -1981,9 +1981,7 @@ def generate_protobuf_only_mode(
self.protocol_specification.name,
)
if not is_correctly_formatted and protolint_output != "":
- protobuf_output = (
- "Protolint warnings:\n" + protolint_output
- ) # pragma: no cover
+ protobuf_output = "Protolint warnings:\n" + protolint_output
# Run black and isort formatting for python
if language == PROTOCOL_LANGUAGE_PYTHON:
@@ -2064,9 +2062,7 @@ def generate_full_mode(self, language: str) -> Optional[str]:
self.spec.all_custom_types, CUSTOM_TYPES_DOT_PY_FILE_NAME
)
if full_mode_output is not None:
- full_mode_output += (
- incomplete_generation_warning_msg # pragma: no cover
- )
+ full_mode_output += incomplete_generation_warning_msg
else:
full_mode_output = incomplete_generation_warning_msg
return full_mode_output
diff --git a/aea/protocols/scaffold/protocol.yaml b/aea/protocols/scaffold/protocol.yaml
index cf12156762..729ea57dd5 100644
--- a/aea/protocols/scaffold/protocol.yaml
+++ b/aea/protocols/scaffold/protocol.yaml
@@ -4,7 +4,7 @@ version: 0.1.0
type: protocol
description: The scaffold protocol scaffolds a protocol to be implemented by the developer.
license: Apache-2.0
-aea_version: '>=0.10.0, <0.11.0'
+aea_version: '>=0.11.0, <0.12.0'
protocol_specification_id: fetchai/scaffold:0.1.0
fingerprint:
__init__.py: Qmc9Ln8THrWmwou4nr3Acag7vcZ1fv8v5oRSkCWtv1aH6t
diff --git a/aea/registries/base.py b/aea/registries/base.py
index 12f2d31793..1c79fa6bb2 100644
--- a/aea/registries/base.py
+++ b/aea/registries/base.py
@@ -122,6 +122,8 @@ class PublicIdRegistry(Generic[Item], Registry[PublicId, Item]):
points to the 'latest' version of a package.
"""
+ __slots__ = ("_public_id_to_item",)
+
def __init__(self) -> None:
"""Initialize the registry."""
super().__init__()
@@ -187,6 +189,8 @@ def teardown(self) -> None:
class AgentComponentRegistry(Registry[ComponentId, Component]):
"""This class implements a simple dictionary-based registry for agent components."""
+ __slots__ = ("_components_by_type", "_registered_keys")
+
def __init__(self, **kwargs: Any) -> None:
"""
Instantiate the registry.
@@ -329,6 +333,8 @@ class ComponentRegistry(
):
"""This class implements a generic registry for skill components."""
+ __slots__ = ("_items", "_dynamically_added")
+
def __init__(self, **kwargs: Any) -> None:
"""
Instantiate the registry.
@@ -517,6 +523,8 @@ def teardown(self) -> None:
class HandlerRegistry(ComponentRegistry[Handler]):
"""This class implements the handlers registry."""
+ __slots__ = ("_items_by_protocol_and_skill",)
+
def __init__(self, **kwargs: Any) -> None:
"""
Instantiate the registry.
diff --git a/aea/registries/resources.py b/aea/registries/resources.py
index 06aabf8c3f..5d7b1195e9 100644
--- a/aea/registries/resources.py
+++ b/aea/registries/resources.py
@@ -38,6 +38,16 @@
class Resources:
"""This class implements the object that holds the resources of an AEA."""
+ __slots__ = (
+ "_agent_name",
+ "_component_registry",
+ "_specification_to_protocol_id",
+ "_handler_registry",
+ "_behaviour_registry",
+ "_model_registry",
+ "_registries",
+ )
+
def __init__(self, agent_name: str = "standalone") -> None:
"""
Instantiate the resources.
diff --git a/aea/runtime.py b/aea/runtime.py
index 00614b353a..b065496550 100644
--- a/aea/runtime.py
+++ b/aea/runtime.py
@@ -25,7 +25,13 @@
from typing import Dict, Optional, Type, cast
from aea.abstract_agent import AbstractAgent
-from aea.agent_loop import AsyncAgentLoop, AsyncState, BaseAgentLoop, SyncAgentLoop
+from aea.agent_loop import (
+ AgentLoopStates,
+ AsyncAgentLoop,
+ AsyncState,
+ BaseAgentLoop,
+ SyncAgentLoop,
+)
from aea.connections.base import ConnectionStates
from aea.decision_maker.base import DecisionMaker, DecisionMakerHandler
from aea.exceptions import _StopRuntime
@@ -238,6 +244,8 @@ def set_loop(self, loop: AbstractEventLoop) -> None:
class AsyncRuntime(BaseRuntime):
"""Asynchronous runtime: uses asyncio loop for multiplexer and async agent main loop."""
+ AGENT_LOOP_STARTED_TIMEOUT: float = 5
+
def __init__(
self,
agent: AbstractAgent,
@@ -351,7 +359,14 @@ async def _start_agent_loop(self) -> None:
self.logger.debug("[{}] Calling setup method...".format(self._agent.name))
self._agent.setup()
self.logger.debug("[{}] Run main loop...".format(self._agent.name))
+
self.agent_loop.start()
+
+ await asyncio.wait_for(
+ self.agent_loop.wait_state(AgentLoopStates.started),
+ timeout=self.AGENT_LOOP_STARTED_TIMEOUT,
+ )
+
self._state.set(RuntimeStates.running)
try:
await self.agent_loop.wait_completed()
diff --git a/aea/skills/base.py b/aea/skills/base.py
index 6c66f6ed8a..8c90dd1b5a 100644
--- a/aea/skills/base.py
+++ b/aea/skills/base.py
@@ -22,12 +22,14 @@
import logging
import queue
import re
+import types
from abc import ABC, abstractmethod
+from copy import copy
from logging import Logger
from pathlib import Path
from queue import Queue
from types import SimpleNamespace
-from typing import Any, Dict, Optional, Sequence, Set, Tuple, Type, cast
+from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union, cast
from aea.common import Address
from aea.components.base import Component, load_aea_package
@@ -42,15 +44,16 @@
from aea.exceptions import (
AEAActException,
AEAComponentLoadException,
- AEAException,
AEAHandleException,
AEAInstantiationException,
_StopRuntime,
+ enforce,
parse_exception,
)
from aea.helpers.base import _get_aea_logger_name_prefix, load_module
from aea.helpers.logging import AgentLoggerAdapter
from aea.helpers.storage.generic_storage import Storage
+from aea.mail.base import Envelope, EnvelopeContext
from aea.multiplexer import MultiplexerStatus, OutBox
from aea.protocols.base import Message
from aea.skills.tasks import TaskManager
@@ -252,6 +255,23 @@ def __getattr__(self, item: Any) -> Any:
"""Get attribute."""
return super().__getattribute__(item) # pragma: no cover
+ def send_to_skill(
+ self,
+ message_or_envelope: Union[Message, Envelope],
+ context: Optional[EnvelopeContext] = None,
+ ) -> None:
+ """
+ Send message or envelope to another skill.
+
+ :param message_or_envelope: envelope to send to another skill.
+ if message passed it will be wrapped into envelope with optional envelope context.
+
+ :return: None
+ """
+ if self._agent_context is None: # pragma: nocover
+ raise ValueError("agent context was not set!")
+ self._agent_context.send_to_skill(message_or_envelope, context)
+
class SkillComponent(ABC):
"""This class defines an abstract interface for skill component classes."""
@@ -409,70 +429,7 @@ def parse_module( # pylint: disable=arguments-differ
:param skill_context: the skill context
:return: a list of Behaviour.
"""
- behaviours = {} # type: Dict[str, "Behaviour"]
- if behaviour_configs == {}:
- return behaviours
- behaviour_names = set(
- config.class_name for _, config in behaviour_configs.items()
- )
- behaviour_module = load_module("behaviours", Path(path))
- classes = inspect.getmembers(behaviour_module, inspect.isclass)
- behaviours_classes = list(
- filter(
- lambda x: any(
- re.match(behaviour, x[0]) for behaviour in behaviour_names
- )
- and not str.startswith(x[1].__module__, "aea.")
- and not str.startswith(
- x[1].__module__,
- f"packages.{skill_context.skill_id.author}.skills.{skill_context.skill_id.name}",
- ),
- classes,
- )
- )
-
- name_to_class = dict(behaviours_classes)
- _print_warning_message_for_non_declared_skill_components(
- skill_context,
- set(name_to_class.keys()),
- {
- behaviour_config.class_name
- for behaviour_config in behaviour_configs.values()
- },
- "behaviours",
- path,
- )
-
- for behaviour_id, behaviour_config in behaviour_configs.items():
- behaviour_class_name = cast(str, behaviour_config.class_name)
- skill_context.logger.debug(
- "Processing behaviour {}".format(behaviour_class_name)
- )
- if not behaviour_id.isidentifier():
- raise AEAComponentLoadException( # pragma: nocover
- f"'{behaviour_id}' is not a valid identifier."
- )
- behaviour_class = name_to_class.get(behaviour_class_name, None)
- if behaviour_class is None:
- skill_context.logger.warning(
- "Behaviour '{}' cannot be found.".format(behaviour_class_name)
- )
- else:
- try:
- behaviour = behaviour_class(
- name=behaviour_id,
- configuration=behaviour_config,
- skill_context=skill_context,
- **dict(behaviour_config.args),
- )
- except Exception as e: # pylint: disable=broad-except # pragma: nocover
- e_str = parse_exception(e)
- raise AEAInstantiationException(
- f"An error occured during instantiation of behaviour {skill_context.skill_id}/{behaviour_config.class_name}:\n{e_str}"
- )
- behaviours[behaviour_id] = behaviour
-
- return behaviours
+ return _parse_module(path, behaviour_configs, skill_context, Behaviour)
class Handler(SkillComponent, ABC):
@@ -516,62 +473,7 @@ def parse_module( # pylint: disable=arguments-differ
:param skill_context: the skill context
:return: an handler, or None if the parsing fails.
"""
- handlers = {} # type: Dict[str, "Handler"]
- if handler_configs == {}:
- return handlers
- handler_names = set(config.class_name for _, config in handler_configs.items())
- handler_module = load_module("handlers", Path(path))
- classes = inspect.getmembers(handler_module, inspect.isclass)
- handler_classes = list(
- filter(
- lambda x: any(re.match(handler, x[0]) for handler in handler_names)
- and not str.startswith(x[1].__module__, "aea.")
- and not str.startswith(
- x[1].__module__,
- f"packages.{skill_context.skill_id.author}.skills.{skill_context.skill_id.name}",
- ),
- classes,
- )
- )
-
- name_to_class = dict(handler_classes)
- _print_warning_message_for_non_declared_skill_components(
- skill_context,
- set(name_to_class.keys()),
- {handler_config.class_name for handler_config in handler_configs.values()},
- "handlers",
- path,
- )
- for handler_id, handler_config in handler_configs.items():
- handler_class_name = cast(str, handler_config.class_name)
- skill_context.logger.debug(
- "Processing handler {}".format(handler_class_name)
- )
- if not handler_id.isidentifier():
- raise AEAComponentLoadException( # pragma: nocover
- f"'{handler_id}' is not a valid identifier."
- )
- handler_class = name_to_class.get(handler_class_name, None)
- if handler_class is None:
- skill_context.logger.warning(
- "Handler '{}' cannot be found.".format(handler_class_name)
- )
- else:
- try:
- handler = handler_class(
- name=handler_id,
- configuration=handler_config,
- skill_context=skill_context,
- **dict(handler_config.args),
- )
- except Exception as e: # pylint: disable=broad-except # pragma: nocover
- e_str = parse_exception(e)
- raise AEAInstantiationException(
- f"An error occured during instantiation of handler {skill_context.skill_id}/{handler_config.class_name}:\n{e_str}"
- )
- handlers[handler_id] = handler
-
- return handlers
+ return _parse_module(path, handler_configs, skill_context, Handler)
class Model(SkillComponent, ABC):
@@ -621,113 +523,21 @@ def parse_module( # pylint: disable=arguments-differ
skill_context: SkillContext,
) -> Dict[str, "Model"]:
"""
- Parse the tasks module.
+ Parse the model module.
:param path: path to the Python skill module.
:param model_configs: a list of model configurations.
:param skill_context: the skill context
:return: a list of Model.
"""
- instances = {} # type: Dict[str, "Model"]
- if model_configs == {}:
- return instances
- models = []
-
- model_names = set(config.class_name for _, config in model_configs.items())
-
- # get all Python modules except the standard ones
- ignore_regex = "|".join(["handlers.py", "behaviours.py", "tasks.py", "__.*"])
- all_python_modules = Path(path).glob("*.py")
- module_paths = set(
- map(
- str,
- filter(
- lambda x: not re.match(ignore_regex, x.name), all_python_modules
- ),
- )
- )
-
- for module_path in module_paths:
- skill_context.logger.debug("Trying to load module {}".format(module_path))
- module_name = module_path.replace(".py", "")
- model_module = load_module(module_name, Path(module_path))
- classes = inspect.getmembers(model_module, inspect.isclass)
- filtered_classes = list(
- filter(
- lambda x: any(re.match(model, x[0]) for model in model_names)
- and issubclass(x[1], Model)
- and not str.startswith(x[1].__module__, "aea.")
- and not str.startswith(
- x[1].__module__,
- f"packages.{skill_context.skill_id.author}.skills.{skill_context.skill_id.name}",
- ),
- classes,
- )
- )
- models.extend(filtered_classes)
-
- _check_duplicate_classes(models)
- name_to_class = dict(models)
- _print_warning_message_for_non_declared_skill_components(
- skill_context,
- set(name_to_class.keys()),
- {model_config.class_name for model_config in model_configs.values()},
- "models",
- path,
- )
- for model_id, model_config in model_configs.items():
- model_class_name = model_config.class_name
- skill_context.logger.debug(
- "Processing model id={}, class={}".format(model_id, model_class_name)
- )
- if not model_id.isidentifier():
- raise AEAComponentLoadException( # pragma: nocover
- f"'{model_id}' is not a valid identifier."
- )
- model = name_to_class.get(model_class_name, None)
- if model is None:
- skill_context.logger.warning(
- "Model '{}' cannot be found.".format(model_class_name)
- )
- else:
- try:
- model_instance = model(
- name=model_id,
- skill_context=skill_context,
- configuration=model_config,
- **dict(model_config.args),
- )
- except Exception as e: # pylint: disable=broad-except # pragma: nocover
- e_str = parse_exception(e)
- raise AEAInstantiationException(
- f"An error occured during instantiation of model {skill_context.skill_id}/{model_config.class_name}:\n{e_str}"
- )
- instances[model_id] = model_instance
- setattr(skill_context, model_id, model_instance)
- return instances
-
-
-def _check_duplicate_classes(name_class_pairs: Sequence[Tuple[str, Type]]) -> None:
- """
- Given a sequence of pairs (class_name, class_obj), check whether there are duplicates in the class names.
-
- :param name_class_pairs: the sequence of pairs (class_name, class_obj)
- :return: None
- :raises AEAException: if there are more than one definition of the same class.
- """
- names_to_path: Dict[str, str] = {}
- for class_name, class_obj in name_class_pairs:
- module_path = class_obj.__module__
- if class_name in names_to_path:
- raise AEAException(
- f"Model '{class_name}' present both in {names_to_path[class_name]} and {module_path}. Remove one of them."
- )
- names_to_path[class_name] = module_path
+ return _parse_module(path, model_configs, skill_context, Model)
class Skill(Component):
"""This class implements a skill."""
+ __slots__ = ("_skill_context", "_handlers", "_behaviours", "_models")
+
def __init__(
self,
configuration: SkillConfig,
@@ -749,7 +559,6 @@ def __init__(
if kwargs is not None:
pass
super().__init__(configuration)
- self.config = configuration
self._skill_context = (
skill_context if skill_context is not None else SkillContext()
)
@@ -853,30 +662,93 @@ def from_config(
)
skill_context.logger = cast(Logger, _logger)
- skill = Skill(configuration, skill_context, **kwargs)
-
- directory = configuration.directory
- load_aea_package(configuration)
- handlers_by_id = dict(configuration.handlers.read_all())
- handlers = Handler.parse_module(
- str(directory / "handlers.py"), handlers_by_id, skill_context
+ skill_component_loader = _SkillComponentLoader(
+ configuration, skill_context, **kwargs
)
+ skill = skill_component_loader.load_skill()
+ return skill
- behaviours_by_id = dict(configuration.behaviours.read_all())
- behaviours = Behaviour.parse_module(
- str(directory / "behaviours.py"), behaviours_by_id, skill_context,
- )
- models_by_id = dict(configuration.models.read_all())
- model_instances = Model.parse_module(
- str(directory), models_by_id, skill_context
- )
+def _parse_module(
+ path: str,
+ component_configs: Dict[str, SkillComponentConfiguration],
+ skill_context: SkillContext,
+ component_class: Type,
+) -> Dict[str, Any]:
+ """
+ Parse a module to find skill component classes, and instantiate them.
- skill.handlers.update(handlers)
- skill.behaviours.update(behaviours)
- skill.models.update(model_instances)
+ This is a private framework function,
+ used in SkillComponentClass.parse_module.
- return skill
+ :param path: path to the Python module.
+ :param component_configs: the component configurations.
+ :param skill_context: the skill context.
+ :param component_class: the class of the skill components to be loaded.
+ :return: A mapping from skill component name to the skill component instance.
+ """
+ components: Dict[str, Any] = {}
+ component_type_name = component_class.__name__.lower()
+ component_type_name_plural = component_type_name + "s"
+ if component_configs == {}:
+ return components
+ component_names = set(config.class_name for _, config in component_configs.items())
+ component_module = load_module(component_type_name_plural, Path(path))
+ classes = inspect.getmembers(component_module, inspect.isclass)
+ component_classes = list(
+ filter(
+ lambda x: any(re.match(component, x[0]) for component in component_names)
+ and issubclass(x[1], component_class)
+ and not str.startswith(x[1].__module__, "aea.")
+ and not str.startswith(
+ x[1].__module__,
+ f"packages.{skill_context.skill_id.author}.skills.{skill_context.skill_id.name}",
+ ),
+ classes,
+ )
+ )
+
+ name_to_class = dict(component_classes)
+ _print_warning_message_for_non_declared_skill_components(
+ skill_context,
+ set(name_to_class.keys()),
+ {
+ component_config.class_name
+ for component_config in component_configs.values()
+ },
+ component_type_name_plural,
+ path,
+ )
+ for component_id, component_config in component_configs.items():
+ component_class_name = cast(str, component_config.class_name)
+ skill_context.logger.debug(
+ f"Processing {component_type_name} {component_class_name}"
+ )
+ if not component_id.isidentifier():
+ raise AEAComponentLoadException( # pragma: nocover
+ f"'{component_id}' is not a valid identifier."
+ )
+ component_class = name_to_class.get(component_class_name, None)
+ if component_class is None:
+ skill_context.logger.warning(
+ f"{component_type_name.capitalize()} '{component_class_name}' cannot be found."
+ )
+ else:
+ try:
+ component = component_class(
+ name=component_id,
+ configuration=component_config,
+ skill_context=skill_context,
+ **dict(component_config.args),
+ )
+ except Exception as e: # pylint: disable=broad-except # pragma: nocover
+ e_str = parse_exception(e)
+ raise AEAInstantiationException(
+ f"An error occured during instantiation of component {skill_context.skill_id}/{component_config.class_name}:\n{e_str}"
+ )
+ components[component_id] = component
+
+ return components
def _print_warning_message_for_non_declared_skill_components(
@@ -893,3 +765,356 @@ def _print_warning_message_for_non_declared_skill_components(
class_name, item_type, skill_path
)
)
+
+
+_SKILL_COMPONENT_TYPES = Type[Union[Handler, Behaviour, Model]]
+
+_ComponentsHelperIndex = Dict[_SKILL_COMPONENT_TYPES, Dict[str, SkillComponent]]
+"""
+Helper index to store component instances.
+"""
+
+
+class _SkillComponentLoadingItem: # pylint: disable=too-few-public-methods
+ """Class to represent a triple (component name, component configuration, component class)."""
+
+ def __init__(
+ self,
+ name: str,
+ config: SkillComponentConfiguration,
+ class_: Type[SkillComponent],
+ type_: _SKILL_COMPONENT_TYPES,
+ ):
+ """Initialize the item."""
+ self.name = name
+ self.config = config
+ self.class_ = class_
+ self.type_ = type_
+
+
+class _SkillComponentLoader:
+ """This class implements the loading policy for skill components."""
+
+ def __init__(
+ self, configuration: SkillConfig, skill_context: SkillContext, **kwargs: Any
+ ):
+ """Initialize the helper class."""
+ enforce(
+ configuration.directory is not None,
+ "Configuration not associated to directory.",
+ )
+ self.configuration = configuration
+ self.skill_directory = cast(Path, configuration.directory)
+ self.skill_context = skill_context
+ self.kwargs = kwargs
+
+ self._all_component_names: Set[str] = set()
+
+ self.skill = Skill(self.configuration, self.skill_context, **self.kwargs)
+ self.skill_dotted_path = f"packages.{self.configuration.public_id.author}.skills.{self.configuration.public_id.name}"
+
+ def load_skill(self) -> Skill:
+ """Load the skill."""
+ load_aea_package(self.configuration)
+ python_modules: Set[Path] = self._get_python_modules()
+ declared_component_classes: Dict[
+ _SKILL_COMPONENT_TYPES, Dict[str, SkillComponentConfiguration]
+ ] = self._get_declared_skill_component_configurations()
+ self._all_component_names = {
+ config.class_name
+ for _, types_ in declared_component_classes.items()
+ for _, config in types_.items()
+ }
+ component_classes_by_path: Dict[
+ Path, Set[Tuple[str, Type[SkillComponent]]]
+ ] = self._load_component_classes(python_modules)
+ component_loading_items = self._match_class_and_configurations(
+ component_classes_by_path, declared_component_classes
+ )
+ components = self._get_component_instances(component_loading_items)
+ self._update_skill(components)
+ return self.skill
+
+ def _update_skill(self, components: _ComponentsHelperIndex) -> None:
+ self.skill.handlers.update(
+ cast(Dict[str, Handler], components.get(Handler, {}))
+ )
+ self.skill.behaviours.update(
+ cast(Dict[str, Behaviour], components.get(Behaviour, {}))
+ )
+ self.skill.models.update(cast(Dict[str, Model], components.get(Model, {})))
+ self.skill._set_models_on_context() # pylint: disable=protected-access
+
+ def _get_python_modules(self) -> Set[Path]:
+ """
+ Get all the Python modules of the skill package.
+
+ We ignore '__pycache__' Python modules as they are not relevant.
+
+ :return: a set of paths pointing to all the Python modules in the skill.
+ """
+ ignore_regex = "__pycache__*"
+ all_python_modules = self.skill_directory.rglob("*.py")
+ module_paths: Set[Path] = set(
+ map(
+ lambda p: Path(p).relative_to(self.skill_directory),
+ filter(
+ lambda x: not re.match(ignore_regex, x.name), all_python_modules
+ ),
+ )
+ )
+ return module_paths
+
+ @classmethod
+ def _compute_module_dotted_path(cls, module_path: Path) -> str:
+ """Compute the dotted path for a skill module."""
+ suffix = ".".join(module_path.with_name(module_path.stem).parts)
+ return suffix
+
+ def _filter_classes(
+ self, classes: List[Tuple[str, Type]]
+ ) -> List[Tuple[str, Type[SkillComponent]]]:
+ """
+ Filter classes of skill components.
+
+ :param classes: a list of pairs (class name, class object)
+ :return: a list of the same kind, but filtered with only skill component classes.
+ """
+ filtered_classes = filter(
+ lambda name_and_class: any(
+ re.match(component, name_and_class[0])
+ for component in self._all_component_names
+ )
+ and issubclass(name_and_class[1], SkillComponent)
+ and not str.startswith(name_and_class[1].__module__, "aea.")
+ and not str.startswith(
+ name_and_class[1].__module__, self.skill_dotted_path
+ ),
+ classes,
+ )
+ classes = list(filtered_classes)
+ return cast(List[Tuple[str, Type[SkillComponent]]], classes)
+
+ def _load_component_classes(
+ self, module_paths: Set[Path]
+ ) -> Dict[Path, Set[Tuple[str, Type[SkillComponent]]]]:
+ """
+ Load component classes from Python modules.
+
+ :param module_paths: a set of paths to Python modules.
+ :return: a mapping from path to skill component classes in that module
+ (containing potential duplicates). Skill components in one path
+ are
+ """
+ module_to_classes: Dict[Path, Set[Tuple[str, Type[SkillComponent]]]] = {}
+ for module_path in module_paths:
+ self.skill_context.logger.debug(f"Trying to load module {module_path}")
+ module_dotted_path: str = self._compute_module_dotted_path(module_path)
+ component_module: types.ModuleType = load_module(
+ module_dotted_path, self.skill_directory / module_path
+ )
+ classes: List[Tuple[str, Type]] = inspect.getmembers(
+ component_module, inspect.isclass
+ )
+ filtered_classes: List[
+ Tuple[str, Type[SkillComponent]]
+ ] = self._filter_classes(classes)
+ module_to_classes[module_path] = set(filtered_classes)
+ return module_to_classes
+
+ def _get_declared_skill_component_configurations(
+ self,
+ ) -> Dict[_SKILL_COMPONENT_TYPES, Dict[str, SkillComponentConfiguration]]:
+ """
+ Get all the declared skill component configurations.
+
+ :return:
+ """
+ handlers_by_id = dict(self.configuration.handlers.read_all())
+ behaviours_by_id = dict(self.configuration.behaviours.read_all())
+ models_by_id = dict(self.configuration.models.read_all())
+
+ result: Dict[
+ _SKILL_COMPONENT_TYPES, Dict[str, SkillComponentConfiguration]
+ ] = {}
+ for component_type, components_by_id in [
+ (Handler, handlers_by_id),
+ (Behaviour, behaviours_by_id),
+ (Model, models_by_id),
+ ]:
+ for component_id, component_config in components_by_id.items():
+ result.setdefault(component_type, {})[component_id] = component_config # type: ignore
+ return result
+
+ def _get_component_instances(
+ self, component_loading_items: List[_SkillComponentLoadingItem],
+ ) -> _ComponentsHelperIndex:
+ """
+ Instantiate classes declared in configuration files.
+
+ :param component_loading_items: a list of loading items.
+ :return: the instances of the skill components.
+ """
+ result: _ComponentsHelperIndex = {}
+ for item in component_loading_items:
+ instance = item.class_(
+ name=item.name,
+ configuration=item.config,
+ skill_context=self.skill_context,
+ **item.config.args,
+ )
+ result.setdefault(item.type_, {})[item.name] = instance
+ return result
+
+ @classmethod
+ def _get_skill_component_type(
+ cls, skill_component_type: Type[SkillComponent],
+ ) -> Type[Union[Handler, Behaviour, Model]]:
+ """Get the concrete skill component type."""
+ parent_skill_component_types = list(
+ filter(
+ lambda class_: class_ in (Handler, Behaviour, Model),
+ skill_component_type.__mro__,
+ )
+ )
+ enforce(
+ len(parent_skill_component_types) == 1,
+ f"Class {skill_component_type.__name__} in module {skill_component_type.__module__} is not allowed to inherit from more than one skill component type. Found: {parent_skill_component_types}.",
+ )
+ return cast(
+ Type[Union[Handler, Behaviour, Model]], parent_skill_component_types[0]
+ )
+
+ def _match_class_and_configurations(
+ self,
+ component_classes_by_path: Dict[Path, Set[Tuple[str, Type[SkillComponent]]]],
+ declared_component_classes: Dict[
+ _SKILL_COMPONENT_TYPES, Dict[str, SkillComponentConfiguration]
+ ],
+ ) -> List[_SkillComponentLoadingItem]:
+ """
+ Match skill component classes to their configurations.
+
+ Given a class of a skill component, we can disambiguate it in three ways:
+ - by its name
+ - by its type (one of 'Handler', 'Behaviour', 'Model')
+ - whether the user has set the 'file_path' field.
+ If one of the skill component cannot be disambiguated, we raise error.
+
+ In this function, the above criteria are applied in that order.
+
+ :param component_classes_by_path:
+ :return: None
+ """
+ result: List[_SkillComponentLoadingItem] = []
+
+ class_index: Dict[
+ str, Dict[_SKILL_COMPONENT_TYPES, Set[Type[SkillComponent]]]
+ ] = {}
+ used_classes = set()
+ not_resolved_configurations: Dict[
+ Tuple[_SKILL_COMPONENT_TYPES, str], SkillComponentConfiguration
+ ] = {}
+
+ # populate indexes
+ for _path, component_classes in component_classes_by_path.items():
+ for (component_classname, _component_class) in component_classes:
+ type_ = self._get_skill_component_type(_component_class)
+ class_index.setdefault(component_classname, {}).setdefault(
+ type_, set()
+ ).add(_component_class)
+
+ for component_type, by_id in declared_component_classes.items():
+ for component_id, component_config in by_id.items():
+ path = component_config.file_path
+ class_name = component_config.class_name
+ if path is not None:
+ classes_in_path = component_classes_by_path[path]
+ component_class_or_none: Optional[Type[SkillComponent]] = next(
+ (
+ actual_class
+ for actual_class_name, actual_class in classes_in_path
+ if actual_class_name == class_name
+ ),
+ None,
+ )
+ enforce(
+ component_class_or_none is not None,
+ self._get_error_message_prefix()
+ + f"Cannot find class '{class_name}' for component '{component_id}' of type '{self._type_to_str(component_type)}' of skill '{self.configuration.public_id}' in module {path}",
+ )
+ component_class = cast(
+ Type[SkillComponent], component_class_or_none
+ )
+ actual_component_type = self._get_skill_component_type(
+ component_class
+ )
+ enforce(
+ actual_component_type == component_type,
+ self._get_error_message_prefix()
+ + f"Found class '{class_name}' for component '{component_id}' of type '{self._type_to_str(component_type)}' of skill '{self.configuration.public_id}' in module {path}, but the expected type was {self._type_to_str(component_type)}, found {self._type_to_str(actual_component_type)} ",
+ )
+ used_classes.add(component_class)
+ result.append(
+ _SkillComponentLoadingItem(
+ component_id,
+ component_config,
+ component_class,
+ component_type,
+ )
+ )
+ else:
+ # process the configuration at the end of the loop
+ not_resolved_configurations[
+ (component_type, component_id)
+ ] = component_config
+
+ for (component_type, component_id), component_config in copy(
+ not_resolved_configurations
+ ).items():
+ class_name = component_config.class_name
+ classes_by_type = class_index.get(class_name, {})
+ enforce(
+ class_name in class_index and component_type in classes_by_type,
+ self._get_error_message_prefix()
+ + f"Cannot find class '{class_name}' for skill component '{component_id}' of type '{self._type_to_str(component_type)}'",
+ )
+ classes = classes_by_type[component_type]
+ not_used_classes = classes.difference(used_classes)
+ enforce(
+ not_used_classes != 0,
+ f"Cannot find class of skill '{self.configuration.public_id}' for component configuration '{component_id}' of type '{self._type_to_str(component_type)}'.",
+ )
+ enforce(
+ len(not_used_classes) == 1,
+ self._get_error_message_ambiguous_classes(
+ class_name, not_used_classes, component_type, component_id
+ ),
+ )
+ not_used_class = list(not_used_classes)[0]
+ result.append(
+ _SkillComponentLoadingItem(
+ component_id, component_config, not_used_class, component_type
+ )
+ )
+ used_classes.add(not_used_class)
+
+ return result
+
+ @classmethod
+ def _type_to_str(cls, component_type: _SKILL_COMPONENT_TYPES) -> str:
+ """Get the string of a component type."""
+ return component_type.__name__.lower()
+
+ def _get_error_message_prefix(self) -> str:
+ """Get error message prefix."""
+ return f"Error while loading skill '{self.configuration.public_id}': "
+
+ def _get_error_message_ambiguous_classes(
+ self,
+ class_name: str,
+ not_used_classes: Set,
+ component_type: _SKILL_COMPONENT_TYPES,
+ component_id: str,
+ ) -> str:
+ return f"{self._get_error_message_prefix()}found many classes with name '{class_name}' for component '{component_id}' of type '{self._type_to_str(component_type)}' in the following modules: {', '.join([c.__module__ for c in not_used_classes])}"
diff --git a/aea/skills/scaffold/skill.yaml b/aea/skills/scaffold/skill.yaml
index 0f26f12e5f..aa311bc301 100644
--- a/aea/skills/scaffold/skill.yaml
+++ b/aea/skills/scaffold/skill.yaml
@@ -4,7 +4,7 @@ version: 0.1.0
type: skill
description: The scaffold skill is a scaffold for your own skill implementation.
license: Apache-2.0
-aea_version: '>=0.10.0, <0.11.0'
+aea_version: '>=0.11.0, <0.12.0'
fingerprint:
__init__.py: QmYRssFqDqb3uWDvfoXy93avisjKRx2yf9SbAQXnkRj1QB
behaviours.py: QmNgDDAmBzWBeBF7e5gUCny38kdqVVfpvHGaAZVZcMtm9Q
diff --git a/aea/test_tools/test_cases.py b/aea/test_tools/test_cases.py
index bec9b912af..18edf24dc0 100644
--- a/aea/test_tools/test_cases.py
+++ b/aea/test_tools/test_cases.py
@@ -317,6 +317,7 @@ def is_allowed_diff_in_agent_config(
"description",
"version",
"registry_path",
+ "dependencies", # temporary
]
result = all([key in allowed_diff_keys for key in content1.keys()])
result = result and all(
diff --git a/aea/test_tools/test_skill.py b/aea/test_tools/test_skill.py
index 8dc292b430..ab8e056c61 100644
--- a/aea/test_tools/test_skill.py
+++ b/aea/test_tools/test_skill.py
@@ -38,13 +38,15 @@
from aea.skills.tasks import TaskManager
-COUNTERPARTY_ADDRESS = "counterparty"
+COUNTERPARTY_AGENT_ADDRESS = "counterparty"
+COUNTERPARTY_SKILL_ADDRESS = "some_author/some_skill:0.1.0"
class BaseSkillTestCase:
"""A class to test a skill."""
path_to_skill: Path = Path(".")
+ is_agent_to_agent_messages: bool = True
_skill: Skill
_multiplexer: AsyncMultiplexer
_outbox: OutBox
@@ -149,7 +151,8 @@ def build_incoming_message(
message_id: Optional[int] = None,
target: Optional[int] = None,
to: Optional[Address] = None,
- sender: Address = COUNTERPARTY_ADDRESS,
+ sender: Optional[Address] = None,
+ is_agent_to_agent_messages: Optional[bool] = None,
**kwargs: Any,
) -> Message:
"""
@@ -164,10 +167,19 @@ def build_incoming_message(
:param performative: the performative
:param to: the 'to' address
:param sender: the 'sender' address
+ :param is_agent_to_agent_messages: whether the dialogue is between agents or components
:param kwargs: other attributes
:return: the created incoming message
"""
+ if is_agent_to_agent_messages is None:
+ is_agent_to_agent_messages = self.is_agent_to_agent_messages
+ if sender is None:
+ sender = (
+ COUNTERPARTY_AGENT_ADDRESS
+ if is_agent_to_agent_messages
+ else COUNTERPARTY_SKILL_ADDRESS
+ )
message_attributes = dict() # type: Dict[str, Any]
default_dialogue_reference = Dialogues.new_self_initiated_dialogue_reference()
@@ -186,9 +198,12 @@ def build_incoming_message(
incoming_message = message_type(**message_attributes)
incoming_message.sender = sender
- incoming_message.to = (
- self.skill.skill_context.agent_address if to is None else to
+ default_to = (
+ self.skill.skill_context.agent_address
+ if is_agent_to_agent_messages
+ else str(self.skill.public_id)
)
+ incoming_message.to = default_to if to is None else to
return incoming_message
def build_incoming_message_for_skill_dialogue(
@@ -332,7 +347,8 @@ def prepare_skill_dialogue(
self,
dialogues: Dialogues,
messages: Tuple[DialogueMessage, ...],
- counterparty: Address = COUNTERPARTY_ADDRESS,
+ counterparty: Optional[Address] = None,
+ is_agent_to_agent_messages: Optional[bool] = None,
) -> Dialogue:
"""
Quickly create a dialogue.
@@ -347,9 +363,18 @@ def prepare_skill_dialogue(
:param dialogues: a dialogues class
:param counterparty: the message_id
:param messages: the dialogue_reference
+ :param is_agent_to_agent_messages: whether the dialogue is between agents or components
:return: the created incoming message
"""
+ if is_agent_to_agent_messages is None:
+ is_agent_to_agent_messages = self.is_agent_to_agent_messages
+ if counterparty is None:
+ counterparty = (
+ COUNTERPARTY_AGENT_ADDRESS
+ if is_agent_to_agent_messages
+ else COUNTERPARTY_SKILL_ADDRESS
+ )
if len(messages) == 0:
raise AEAEnforceError("the list of messages must be positive.")
@@ -363,14 +388,20 @@ def prepare_skill_dialogue(
if is_incoming: # first message from the opponent
dialogue_reference = dialogues.new_self_initiated_dialogue_reference()
+ default_to = (
+ self.skill.skill_context.agent_address
+ if is_agent_to_agent_messages
+ else str(self.skill.public_id)
+ )
message = self.build_incoming_message(
message_type=dialogues.message_class,
dialogue_reference=dialogue_reference,
message_id=Dialogue.STARTING_MESSAGE_ID,
target=target or Dialogue.STARTING_TARGET,
performative=performative,
- to=self.skill.skill_context.agent_address,
+ to=default_to,
sender=counterparty,
+ is_agent_to_agent_messages=is_agent_to_agent_messages,
**contents,
)
dialogue = cast(Dialogue, dialogues.update(message))
@@ -402,14 +433,20 @@ def prepare_skill_dialogue(
)
message_id = dialogue.get_incoming_next_message_id()
+ default_to = (
+ self.skill.skill_context.agent_address
+ if is_agent_to_agent_messages
+ else str(self.skill.public_id)
+ )
message = self.build_incoming_message(
message_type=dialogues.message_class,
dialogue_reference=dialogue_reference,
message_id=message_id,
target=target,
performative=performative,
- to=self.skill.skill_context.agent_address,
+ to=default_to,
sender=counterparty,
+ is_agent_to_agent_messages=is_agent_to_agent_messages,
**contents,
)
dialogue = cast(Dialogue, dialogues.update(message))
@@ -434,28 +471,32 @@ def setup(cls, **kwargs: Any) -> None:
asyncio.Queue()
)
cls._outbox = OutBox(cast(Multiplexer, cls._multiplexer))
- _shared_state = cast(Dict[str, Any], kwargs.pop("shared_state", dict()))
+ _shared_state = cast(Optional[Dict[str, Any]], kwargs.pop("shared_state", None))
_skill_config_overrides = cast(
- Dict[str, Any], kwargs.pop("config_overrides", dict())
+ Optional[Dict[str, Any]], kwargs.pop("config_overrides", None)
+ )
+ _dm_context_kwargs = cast(
+ Dict[str, Any], kwargs.pop("dm_context_kwargs", dict())
)
+
agent_context = AgentContext(
identity=identity,
connection_status=cls._multiplexer.connection_status,
outbox=cls._outbox,
decision_maker_message_queue=Queue(),
- decision_maker_handler_context=SimpleNamespace(),
+ decision_maker_handler_context=SimpleNamespace(**_dm_context_kwargs),
task_manager=TaskManager(),
default_ledger_id=identity.default_address_key,
currency_denominations=DEFAULT_CURRENCY_DENOMINATIONS,
default_connection=None,
default_routing={},
- search_service_address="dummy_search_service_address",
+ search_service_address="dummy_author/dummy_search_skill:0.1.0",
decision_maker_address="dummy_decision_maker_address",
data_dir=os.getcwd(),
)
- # This enables pre-populating the 'shared_state' prior to loading the skill
- if _shared_state != dict():
+ # Pre-populate the 'shared_state' prior to loading the skill
+ if _shared_state is not None:
for key, value in _shared_state.items():
agent_context.shared_state[key] = value
@@ -465,8 +506,8 @@ def setup(cls, **kwargs: Any) -> None:
with open_file(skill_configuration_file_path) as fp:
skill_config: SkillConfig = loader.load(fp)
- # This enables overriding the skill's config prior to loading
- if _skill_config_overrides != {}:
+ # Override skill's config prior to loading
+ if _skill_config_overrides is not None:
skill_config.update(_skill_config_overrides)
skill_config.directory = cls.path_to_skill
diff --git a/benchmark/Dockerfile b/benchmark/Dockerfile
index 73aaaa25a4..77fbbac6ec 100644
--- a/benchmark/Dockerfile
+++ b/benchmark/Dockerfile
@@ -10,5 +10,5 @@ RUN apk add --no-cache go
RUN pip install --upgrade pip pipenv
RUN pip install aea[all] --upgrade --force-reinstall
-RUN wget https://raw.githubusercontent.com/fetchai/agents-aea/master/Pipfile
+RUN wget https://raw.githubusercontent.com/fetchai/agents-aea/main/Pipfile
RUN pipenv install -d --deploy --skip-lock --system
diff --git a/benchmark/checks/check_mem_usage.py b/benchmark/checks/check_mem_usage.py
index 9ab5e3e21b..a53ccc5bea 100755
--- a/benchmark/checks/check_mem_usage.py
+++ b/benchmark/checks/check_mem_usage.py
@@ -25,6 +25,7 @@
import click
from aea.protocols.base import Message
+from aea.registries.resources import Resources
from aea.skills.base import Handler
from benchmark.checks.utils import SyncedGeneratorConnection # noqa: I100
from benchmark.checks.utils import (
@@ -62,10 +63,12 @@ def run(duration: int, runtime_mode: str) -> List[Tuple[str, Union[int, float]]]
# import manually due to some lazy imports in decision_maker
import aea.decision_maker.default # noqa: F401
- agent = make_agent(runtime_mode=runtime_mode)
connection = SyncedGeneratorConnection.make()
- agent.resources.add_connection(connection)
+ resources = Resources()
+ resources.add_connection(connection)
+ agent = make_agent(runtime_mode=runtime_mode, resources=resources)
agent.resources.add_skill(make_skill(agent, handlers={"test": TestHandler}))
+
t = Thread(target=agent.start, daemon=True)
t.start()
wait_for_condition(lambda: agent.is_running, timeout=5)
diff --git a/benchmark/checks/check_multiagent.py b/benchmark/checks/check_multiagent.py
index 6b6a76707f..aa22122a8d 100755
--- a/benchmark/checks/check_multiagent.py
+++ b/benchmark/checks/check_multiagent.py
@@ -28,7 +28,9 @@
import click
from aea.configurations.base import ConnectionConfig
+from aea.identity.base import Identity
from aea.protocols.base import Message
+from aea.registries.resources import Resources
from aea.runner import AEARunner
from aea.skills.base import Handler
from benchmark.checks.utils import get_mem_usage_in_mb # noqa: I100
@@ -123,15 +125,24 @@ def run(
skills = []
for i in range(num_of_agents):
- agent = make_agent(agent_name=f"agent{i}", runtime_mode=runtime_mode)
+ resources = Resources()
+ agent_name = f"agent{i}"
+ identity = Identity(agent_name, address=agent_name)
connection = OEFLocalConnection(
local_node,
configuration=ConnectionConfig(
connection_id=OEFLocalConnection.connection_id,
),
- identity=agent.identity,
+ identity=identity,
+ data_dir="tmp",
+ )
+ resources.add_connection(connection)
+ agent = make_agent(
+ agent_name=agent_name,
+ runtime_mode=runtime_mode,
+ resources=resources,
+ identity=identity,
)
- agent.resources.add_connection(connection)
skill = make_skill(agent, handlers={"test": TestHandler})
agent.resources.add_skill(skill)
agents.append(agent)
@@ -156,7 +167,7 @@ def run(
mem_usage = get_mem_usage_in_mb()
local_node.stop()
- runner.stop()
+ runner.stop(timeout=5)
total_messages = sum(
[cast(TestHandler, skill.handlers["test"]).count for skill in skills]
diff --git a/benchmark/checks/check_multiagent_http_dialogues.py b/benchmark/checks/check_multiagent_http_dialogues.py
index 9fe890ef2f..060edd5637 100755
--- a/benchmark/checks/check_multiagent_http_dialogues.py
+++ b/benchmark/checks/check_multiagent_http_dialogues.py
@@ -18,7 +18,6 @@
#
# ------------------------------------------------------------------------------
"""?Memory usage across the time."""
-
import itertools
import os
import struct
@@ -31,8 +30,10 @@
from aea.aea import AEA
from aea.common import Address
from aea.configurations.base import ConnectionConfig
+from aea.identity.base import Identity
from aea.protocols.base import Message, Protocol
from aea.protocols.dialogue.base import Dialogue
+from aea.registries.resources import Resources
from aea.runner import AEARunner
from aea.skills.base import Handler
from benchmark.checks.utils import get_mem_usage_in_mb # noqa: I100
@@ -155,17 +156,26 @@ def run(
agents = []
skills = {}
handler_name = "httpingpong"
+
for i in range(num_of_agents):
agent_name = f"agent{i}"
- agent = make_agent(agent_name=agent_name, runtime_mode=runtime_mode)
+ identity = Identity(agent_name, address=agent_name)
+ resources = Resources()
connection = OEFLocalConnection(
local_node,
configuration=ConnectionConfig(
connection_id=OEFLocalConnection.connection_id,
),
- identity=agent.identity,
+ identity=identity,
+ data_dir="tmp",
+ )
+ resources.add_connection(connection)
+ agent = make_agent(
+ agent_name=agent_name,
+ runtime_mode=runtime_mode,
+ resources=resources,
+ identity=identity,
)
- agent.resources.add_connection(connection)
skill = make_skill(agent, handlers={handler_name: HttpPingPongHandler})
agent.resources.add_skill(skill)
agents.append(agent)
@@ -191,7 +201,7 @@ def run(
mem_usage = get_mem_usage_in_mb()
local_node.stop()
- runner.stop()
+ runner.stop(timeout=5)
total_messages = sum(
[
diff --git a/benchmark/checks/check_proactive.py b/benchmark/checks/check_proactive.py
index cf22109a6f..496b436224 100755
--- a/benchmark/checks/check_proactive.py
+++ b/benchmark/checks/check_proactive.py
@@ -24,6 +24,7 @@
import click
+from aea.registries.resources import Resources
from aea.skills.base import Behaviour
from benchmark.checks.utils import SyncedGeneratorConnection # noqa: I100
from benchmark.checks.utils import (
@@ -66,15 +67,15 @@ def run(duration: int, runtime_mode: str) -> List[Tuple[str, Union[int, float]]]
# import manually due to some lazy imports in decision_maker
import aea.decision_maker.default # noqa: F401
- agent = make_agent(runtime_mode=runtime_mode)
+ resources = Resources()
connection = SyncedGeneratorConnection.make()
- agent.resources.add_connection(connection)
+ resources.add_connection(connection)
+ agent = make_agent(runtime_mode=runtime_mode, resources=resources)
skill = make_skill(agent, behaviours={"test": TestBehaviour})
agent.resources.add_skill(skill)
t = Thread(target=agent.start, daemon=True)
t.start()
wait_for_condition(lambda: agent.is_running, timeout=5)
-
time.sleep(duration)
agent.stop()
t.join(5)
diff --git a/benchmark/checks/check_reactive.py b/benchmark/checks/check_reactive.py
index e5f6e40435..52047af4ec 100755
--- a/benchmark/checks/check_reactive.py
+++ b/benchmark/checks/check_reactive.py
@@ -27,6 +27,7 @@
from aea.mail.base import Envelope
from aea.protocols.base import Message
+from aea.registries.resources import Resources
from aea.skills.base import Handler
from benchmark.checks.utils import GeneratorConnection # noqa: I100
from benchmark.checks.utils import (
@@ -90,8 +91,7 @@ def run(
# import manually due to some lazy imports in decision_maker
import aea.decision_maker.default # noqa: F401
- agent = make_agent(runtime_mode=runtime_mode)
-
+ resources = Resources()
if connection_mode not in CONNECTION_MODES:
raise ValueError(
f"bad connection mode {connection_mode}. valid is one of {list(CONNECTION_MODES.keys())}"
@@ -101,7 +101,9 @@ def run(
conn_cls = type("conn_cls", (TestConnectionMixIn, base_cls), {})
connection = conn_cls.make() # type: ignore # pylint: disable=no-member
- agent.resources.add_connection(connection)
+ resources.add_connection(connection)
+
+ agent = make_agent(runtime_mode=runtime_mode, resources=resources)
agent.resources.add_skill(make_skill(agent, handlers={"test": TestHandler}))
t = Thread(target=agent.start, daemon=True)
t.start()
@@ -110,6 +112,7 @@ def run(
connection.enable()
time.sleep(duration)
connection.disable()
+ time.sleep(0.2) # possible race condition in stop?
agent.stop()
t.join(5)
diff --git a/benchmark/checks/utils.py b/benchmark/checks/utils.py
index 0d826c70ba..6a1e273086 100644
--- a/benchmark/checks/utils.py
+++ b/benchmark/checks/utils.py
@@ -37,10 +37,10 @@
from aea.configurations.constants import (
DEFAULT_LEDGER,
DEFAULT_PROTOCOL,
- FETCHAI,
PACKAGES,
PROTOCOLS,
SKILLS,
+ _FETCHAI_IDENTIFIER,
)
from aea.connections.base import Connection, ConnectionStates
from aea.crypto.wallet import Wallet
@@ -70,11 +70,16 @@ def wait_for_condition(
raise TimeoutError(error_msg)
-def make_agent(agent_name: str = "my_agent", runtime_mode: str = "threaded") -> AEA:
+def make_agent(
+ agent_name: str = "my_agent",
+ runtime_mode: str = "threaded",
+ resources: Optional[Resources] = None,
+ identity: Optional[Identity] = None,
+) -> AEA:
"""Make AEA instance."""
wallet = Wallet({DEFAULT_LEDGER: None})
- identity = Identity(agent_name, address=agent_name)
- resources = Resources()
+ identity = identity or Identity(agent_name, address=agent_name)
+ resources = resources or Resources()
datadir = os.getcwd()
agent_context = MagicMock()
agent_context.agent_name = agent_name
@@ -82,7 +87,7 @@ def make_agent(agent_name: str = "my_agent", runtime_mode: str = "threaded") ->
resources.add_skill(
Skill.from_dir(
- str(PACKAGES_DIR / FETCHAI / SKILLS / ERROR_SKILL_NAME),
+ str(PACKAGES_DIR / _FETCHAI_IDENTIFIER / SKILLS / ERROR_SKILL_NAME),
agent_context=agent_context,
)
)
@@ -90,7 +95,7 @@ def make_agent(agent_name: str = "my_agent", runtime_mode: str = "threaded") ->
Protocol.from_dir(
str(
PACKAGES_DIR
- / FETCHAI
+ / _FETCHAI_IDENTIFIER
/ PROTOCOLS
/ PublicId.from_str(DEFAULT_PROTOCOL).name
)
@@ -159,11 +164,13 @@ async def receive(self, *args: Any, **kwargs: Any) -> Optional["Envelope"]:
return envelope
@classmethod
- def make(cls) -> "GeneratorConnection":
+ def make(cls,) -> "GeneratorConnection":
"""Construct connection instance."""
configuration = ConnectionConfig(connection_id=cls.connection_id,)
test_connection = cls(
- configuration=configuration, identity=Identity("name", "address")
+ configuration=configuration,
+ identity=Identity("name", "address"),
+ data_dir=".tmp",
)
return test_connection
diff --git a/benchmark/framework/aea_test_wrapper.py b/benchmark/framework/aea_test_wrapper.py
index daa2997455..a88328ba97 100644
--- a/benchmark/framework/aea_test_wrapper.py
+++ b/benchmark/framework/aea_test_wrapper.py
@@ -26,7 +26,7 @@
from aea.aea_builder import AEABuilder
from aea.components.base import Component
from aea.configurations.base import SkillConfig
-from aea.crypto.fetchai import FetchAICrypto
+from aea.configurations.constants import _FETCHAI_IDENTIFIER
from aea.mail.base import Envelope
from aea.protocols.base import Message
from aea.skills.base import Handler, Skill, SkillContext
@@ -69,7 +69,7 @@ def make_aea(
builder.set_name(name or self.name)
- builder.add_private_key(FetchAICrypto.identifier, private_key_path=None)
+ builder.add_private_key(_FETCHAI_IDENTIFIER, private_key_path=None)
for component in components:
builder.add_component_instance(component)
diff --git a/benchmark/run_from_branch.sh b/benchmark/run_from_branch.sh
index 10b8ac1975..234a129456 100755
--- a/benchmark/run_from_branch.sh
+++ b/benchmark/run_from_branch.sh
@@ -1,6 +1,6 @@
#!/bin/bash
REPO=https://github.com/fetchai/agents-aea.git
-BRANCH=master
+BRANCH=main
TMP_DIR=$(mktemp -d -t bench-XXXXXXXXXX)
git clone --branch $BRANCH $REPO $TMP_DIR
@@ -12,7 +12,7 @@ pip install pipenv
# this is to install benchmark dependencies
pipenv install --dev --skip-lock
# this is to install the AEA in the Pipenv virtual env
-pipenv run pip install --upgrade aea[all]=="0.10.1"
+pipenv run pip install --upgrade aea[all]=="0.11.0"
chmod +x benchmark/checks/run_benchmark.sh
echo "Start the experiments."
diff --git a/deploy-image/Dockerfile b/deploy-image/Dockerfile
index 77cc1cdc06..97df93c005 100644
--- a/deploy-image/Dockerfile
+++ b/deploy-image/Dockerfile
@@ -13,7 +13,7 @@ ENV PYTHONPATH "$PYTHONPATH:/usr/lib/python3.7/site-packages"
RUN apk add --no-cache go
RUN pip install --upgrade pip
-RUN pip install --upgrade --force-reinstall aea[all]==0.10.1
+RUN pip install --upgrade --force-reinstall aea[all]==0.11.0
# COPY ./packages /home/packages # enable to add packages dir
WORKDIR home
diff --git a/deploy-image/docker-env.sh b/deploy-image/docker-env.sh
index 384106af66..20d63f4282 100755
--- a/deploy-image/docker-env.sh
+++ b/deploy-image/docker-env.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# Swap the following lines if you want to work with 'latest'
-DOCKER_IMAGE_TAG=fetchai/aea-deploy:0.10.1
+DOCKER_IMAGE_TAG=fetchai/aea-deploy:0.11.0
# DOCKER_IMAGE_TAG=fetchai/aea-deploy:latest
DOCKER_BUILD_CONTEXT_DIR=..
diff --git a/develop-image/docker-env.sh b/develop-image/docker-env.sh
index 8a321f54d6..c76ce68503 100755
--- a/develop-image/docker-env.sh
+++ b/develop-image/docker-env.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# Swap the following lines if you want to work with 'latest'
-DOCKER_IMAGE_TAG=fetchai/aea-develop:0.10.1
+DOCKER_IMAGE_TAG=fetchai/aea-develop:0.11.0
# DOCKER_IMAGE_TAG=aea-develop:latest
DOCKER_BUILD_CONTEXT_DIR=..
diff --git a/docs/api/agent_loop.md b/docs/api/agent_loop.md
index c40fa09fe8..95b5290b51 100644
--- a/docs/api/agent_loop.md
+++ b/docs/api/agent_loop.md
@@ -64,6 +64,23 @@ Get agent.
Get current main loop state.
+
+#### wait`_`state
+
+```python
+ | async wait_state(state_or_states: Union[Any, Sequence[Any]]) -> Tuple[Any, Any]
+```
+
+Wait state to be set.
+
+**Arguments**:
+
+- `state_or_states`: state or list of states.
+
+**Returns**:
+
+tuple of previous state and new state.
+
#### is`_`running
@@ -92,6 +109,36 @@ Set event loop and all event loopp related objects.
Run agent loop.
+
+#### send`_`to`_`skill
+
+```python
+ | @abstractmethod
+ | send_to_skill(message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None) -> None
+```
+
+Send message or envelope to another skill.
+
+**Arguments**:
+
+- `message_or_envelope`: envelope to send to another skill.
+if message passed it will be wrapped into envelope with optional envelope context.
+
+**Returns**:
+
+None
+
+
+#### skill2skill`_`queue
+
+```python
+ | @property
+ | @abstractmethod
+ | skill2skill_queue() -> Queue
+```
+
+Get skill to skill message queue.
+
## AsyncAgentLoop Objects
@@ -114,4 +161,33 @@ Init agent loop.
- `agent`: AEA instance
- `loop`: asyncio loop to use. optional
+- `threaded`: is a new thread to be started for the agent loop
+
+
+#### skill2skill`_`queue
+
+```python
+ | @property
+ | skill2skill_queue() -> Queue
+```
+
+Get skill to skill message queue.
+
+
+#### send`_`to`_`skill
+
+```python
+ | send_to_skill(message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None) -> None
+```
+
+Send message or envelope to another skill.
+
+**Arguments**:
+
+- `message_or_envelope`: envelope to send to another skill.
+if message passed it will be wrapped into envelope with optional envelope context.
+
+**Returns**:
+
+None
diff --git a/docs/api/configurations/base.md b/docs/api/configurations/base.md
index f93d5191e3..59a0fdbb80 100644
--- a/docs/api/configurations/base.md
+++ b/docs/api/configurations/base.md
@@ -198,6 +198,17 @@ Get the 'aea_version' attribute.
Set the 'aea_version' attribute.
+
+#### check`_`aea`_`version
+
+```python
+ | check_aea_version() -> None
+```
+
+Check that the AEA version matches the specifier set.
+
+:raises ValueError if the version of the aea framework falls within a specifier.
+
#### directory
@@ -415,17 +426,6 @@ Check that the fingerprint are correct against a directory path.
- the argument is not a valid package directory
- the fingerprints do not match.
-
-#### check`_`aea`_`version
-
-```python
- | check_aea_version() -> None
-```
-
-Check that the AEA version matches the specifier set.
-
-:raises ValueError if the version of the aea framework falls within a specifier.
-
#### check`_`public`_`id`_`consistency
@@ -528,7 +528,7 @@ This class represent a skill component configuration.
#### `__`init`__`
```python
- | __init__(class_name: str, **args: Any) -> None
+ | __init__(class_name: str, file_path: Optional[str] = None, **args: Any) -> None
```
Initialize a skill component configuration.
@@ -629,7 +629,7 @@ Class to represent the agent configuration file.
#### `__`init`__`
```python
- | __init__(agent_name: SimpleIdOrStr, author: SimpleIdOrStr, version: str = "", license_: str = "", aea_version: str = "", fingerprint: Optional[Dict[str, str]] = None, fingerprint_ignore_patterns: Optional[Sequence[str]] = None, build_entrypoint: Optional[str] = None, registry_path: str = DEFAULT_REGISTRY_NAME, description: str = "", logging_config: Optional[Dict] = None, period: Optional[float] = None, execution_timeout: Optional[float] = None, max_reactions: Optional[int] = None, error_handler: Optional[Dict] = None, decision_maker_handler: Optional[Dict] = None, skill_exception_policy: Optional[str] = None, connection_exception_policy: Optional[str] = None, default_ledger: Optional[str] = None, currency_denominations: Optional[Dict[str, str]] = None, default_connection: Optional[str] = None, default_routing: Optional[Dict[str, str]] = None, loop_mode: Optional[str] = None, runtime_mode: Optional[str] = None, storage_uri: Optional[str] = None, data_dir: Optional[str] = None, component_configurations: Optional[Dict[ComponentId, Dict]] = None) -> None
+ | __init__(agent_name: SimpleIdOrStr, author: SimpleIdOrStr, version: str = "", license_: str = "", aea_version: str = "", fingerprint: Optional[Dict[str, str]] = None, fingerprint_ignore_patterns: Optional[Sequence[str]] = None, build_entrypoint: Optional[str] = None, registry_path: str = DEFAULT_REGISTRY_NAME, description: str = "", logging_config: Optional[Dict] = None, period: Optional[float] = None, execution_timeout: Optional[float] = None, max_reactions: Optional[int] = None, error_handler: Optional[Dict] = None, decision_maker_handler: Optional[Dict] = None, skill_exception_policy: Optional[str] = None, connection_exception_policy: Optional[str] = None, default_ledger: Optional[str] = None, currency_denominations: Optional[Dict[str, str]] = None, default_connection: Optional[str] = None, default_routing: Optional[Dict[str, str]] = None, loop_mode: Optional[str] = None, runtime_mode: Optional[str] = None, storage_uri: Optional[str] = None, data_dir: Optional[str] = None, component_configurations: Optional[Dict[ComponentId, Dict]] = None, dependencies: Optional[Dependencies] = None) -> None
```
Instantiate the agent configuration object.
@@ -867,3 +867,21 @@ Initialize a protocol configuration object.
Return the JSON representation.
+
+## AEAVersionError Objects
+
+```python
+class AEAVersionError(ValueError)
+```
+
+Special Exception for version error.
+
+
+#### `__`init`__`
+
+```python
+ | __init__(package_id: PublicId, aea_version_specifiers: SpecifierSet) -> None
+```
+
+Init exception.
+
diff --git a/docs/api/configurations/data_types.md b/docs/api/configurations/data_types.md
index 957c3985b3..e3252303d9 100644
--- a/docs/api/configurations/data_types.md
+++ b/docs/api/configurations/data_types.md
@@ -345,6 +345,24 @@ the public id object.
- `ValueError`: if the string in input is not well formatted.
+
+#### try`_`from`_`str
+
+```python
+ | @classmethod
+ | try_from_str(cls, public_id_string: str) -> Optional["PublicId"]
+```
+
+Safely try to get public id from string.
+
+**Arguments**:
+
+- `public_id_string`: the public id in string format.
+
+**Returns**:
+
+the public id object or None
+
#### from`_`uri`_`path
@@ -559,7 +577,7 @@ Get the package identifier without the version.
| from_uri_path(cls, package_id_uri_path: str) -> "PackageId"
```
-Initialize the public id from the string.
+Initialize the package id from the string.
>>> str(PackageId.from_uri_path("skill/author/package_name/0.1.0"))
'(skill, author/package_name:0.1.0)'
@@ -572,11 +590,11 @@ ValueError: Input 'very/bad/formatted:input' is not well formatted.
**Arguments**:
-- `public_id_uri_path`: the public id in uri path string format.
+- `package_id_uri_path`: the package id in uri path string format.
**Returns**:
-the public id object.
+the package id object.
**Raises**:
diff --git a/docs/api/context/base.md b/docs/api/context/base.md
index 52a9c6a70e..a6cacc6b9f 100644
--- a/docs/api/context/base.md
+++ b/docs/api/context/base.md
@@ -16,7 +16,7 @@ Provide read access to relevant objects of the agent for the skills.
#### `__`init`__`
```python
- | __init__(identity: Identity, connection_status: MultiplexerStatus, outbox: OutBox, decision_maker_message_queue: Queue, decision_maker_handler_context: SimpleNamespace, task_manager: TaskManager, default_ledger_id: str, currency_denominations: Dict[str, str], default_connection: Optional[PublicId], default_routing: Dict[PublicId, PublicId], search_service_address: Address, decision_maker_address: Address, data_dir: str, storage_callable: Callable[[], Optional[Storage]] = lambda: None, **kwargs: Any) -> None
+ | __init__(identity: Identity, connection_status: MultiplexerStatus, outbox: OutBox, decision_maker_message_queue: Queue, decision_maker_handler_context: SimpleNamespace, task_manager: TaskManager, default_ledger_id: str, currency_denominations: Dict[str, str], default_connection: Optional[PublicId], default_routing: Dict[PublicId, PublicId], search_service_address: Address, decision_maker_address: Address, data_dir: str, storage_callable: Callable[[], Optional[Storage]] = lambda: None, send_to_skill: Optional[Callable] = None, **kwargs: Any) -> None
```
Initialize an agent context.
@@ -39,6 +39,24 @@ Initialize an agent context.
- `storage_callable`: function that returns optional storage attached to agent.
- `kwargs`: keyword arguments to be attached in the agent context namespace.
+
+#### send`_`to`_`skill
+
+```python
+ | send_to_skill(message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None) -> None
+```
+
+Send message or envelope to another skill.
+
+**Arguments**:
+
+- `message_or_envelope`: envelope to send to another skill.
+if message passed it will be wrapped into envelope with optional envelope context.
+
+**Returns**:
+
+None
+
#### storage
diff --git a/docs/api/crypto/plugin.md b/docs/api/crypto/plugin.md
new file mode 100644
index 0000000000..77b7813c63
--- /dev/null
+++ b/docs/api/crypto/plugin.md
@@ -0,0 +1,77 @@
+
+# aea.crypto.plugin
+
+Implementation of plug-in mechanism for cryptos.
+
+
+## Plugin Objects
+
+```python
+class Plugin()
+```
+
+Class that implements an AEA plugin.
+
+
+#### `__`init`__`
+
+```python
+ | __init__(group: str, entry_point: EntryPoint)
+```
+
+Initialize the plugin.
+
+**Arguments**:
+
+- `group`: the group the plugin belongs to.
+- `entry_point`: the entrypoint.
+
+
+#### name
+
+```python
+ | @property
+ | name() -> str
+```
+
+Get the plugin identifier.
+
+
+#### group
+
+```python
+ | @property
+ | group() -> str
+```
+
+Get the group.
+
+
+#### attr
+
+```python
+ | @property
+ | attr() -> str
+```
+
+Get the class name.
+
+
+#### entry`_`point`_`path
+
+```python
+ | @property
+ | entry_point_path() -> str
+```
+
+Get the entry point path.
+
+
+#### load`_`all`_`plugins
+
+```python
+load_all_plugins() -> None
+```
+
+Load all plugins.
+
diff --git a/docs/api/error_handler/base.md b/docs/api/error_handler/base.md
index 9a7dbf20c9..2c632ae730 100644
--- a/docs/api/error_handler/base.md
+++ b/docs/api/error_handler/base.md
@@ -38,7 +38,7 @@ None
```python
| @classmethod
| @abstractmethod
- | send_decoding_error(cls, envelope: Envelope, logger: Logger) -> None
+ | send_decoding_error(cls, envelope: Envelope, exception: Exception, logger: Logger) -> None
```
Handle a decoding error.
@@ -46,25 +46,29 @@ Handle a decoding error.
**Arguments**:
- `envelope`: the envelope
+- `exception`: the exception raised during decoding
+- `logger`: the logger
**Returns**:
None
-
-#### send`_`unsupported`_`skill
+
+#### send`_`no`_`active`_`handler
```python
| @classmethod
| @abstractmethod
- | send_unsupported_skill(cls, envelope: Envelope, logger: Logger) -> None
+ | send_no_active_handler(cls, envelope: Envelope, reason: str, logger: Logger) -> None
```
-Handle the received envelope in case the skill is not supported.
+Handle the received envelope in case the handler is not supported.
**Arguments**:
- `envelope`: the envelope
+- `reason`: the reason for the failure
+- `logger`: the logger
**Returns**:
diff --git a/docs/api/error_handler/default.md b/docs/api/error_handler/default.md
index 7599ec681b..4d2f424e5b 100644
--- a/docs/api/error_handler/default.md
+++ b/docs/api/error_handler/default.md
@@ -35,7 +35,7 @@ None
```python
| @classmethod
- | send_decoding_error(cls, envelope: Envelope, logger: Logger) -> None
+ | send_decoding_error(cls, envelope: Envelope, exception: Exception, logger: Logger) -> None
```
Handle a decoding error.
@@ -43,24 +43,27 @@ Handle a decoding error.
**Arguments**:
- `envelope`: the envelope
+- `exception`: the exception raised during decoding
+- `logger`: the logger
**Returns**:
None
-
-#### send`_`unsupported`_`skill
+
+#### send`_`no`_`active`_`handler
```python
| @classmethod
- | send_unsupported_skill(cls, envelope: Envelope, logger: Logger) -> None
+ | send_no_active_handler(cls, envelope: Envelope, reason: str, logger: Logger) -> None
```
-Handle the received envelope in case the skill is not supported.
+Handle the received envelope in case the handler is not supported.
**Arguments**:
- `envelope`: the envelope
+- `reason`: the reason for the failure
**Returns**:
diff --git a/docs/api/exceptions.md b/docs/api/exceptions.md
index 0f7b0afa73..3562e2334c 100644
--- a/docs/api/exceptions.md
+++ b/docs/api/exceptions.md
@@ -66,6 +66,15 @@ class AEAInstantiationException(AEAException)
Class for exceptions that are raised for instantiation errors of AEA packages.
+
+## AEAPluginError Objects
+
+```python
+class AEAPluginError(AEAException)
+```
+
+Class for exceptions that are raised for wrong plugin setup of the working set.
+
## AEAEnforceError Objects
diff --git a/docs/api/helpers/preference_representations/base.md b/docs/api/helpers/preference_representations/base.md
index f4553eb198..4242269593 100644
--- a/docs/api/helpers/preference_representations/base.md
+++ b/docs/api/helpers/preference_representations/base.md
@@ -7,7 +7,7 @@ Preference representation helpers.
#### logarithmic`_`utility
```python
-logarithmic_utility(utility_params_by_good_id: Dict[str, float], quantities_by_good_id: Dict[str, int], quantity_shift: int = 1) -> float
+logarithmic_utility(utility_params_by_good_id: Dict[str, float], quantities_by_good_id: Dict[str, int], quantity_shift: int = 100) -> float
```
Compute agent's utility given her utility function params and a good bundle.
@@ -16,7 +16,8 @@ Compute agent's utility given her utility function params and a good bundle.
- `utility_params_by_good_id`: utility params by good identifier
- `quantities_by_good_id`: quantities by good identifier
-- `quantity_shift`: a non-negative factor to shift the quantities in the utility function (to ensure the natural logarithm can be used on the entire range of quantities)
+- `quantity_shift`: a non-negative factor to shift the quantities in the utility function (to
+ensure the natural logarithm can be used on the entire range of quantities)
**Returns**:
diff --git a/docs/api/helpers/search/models.md b/docs/api/helpers/search/models.md
index 2a6767c377..1e44f8396a 100644
--- a/docs/api/helpers/search/models.md
+++ b/docs/api/helpers/search/models.md
@@ -208,6 +208,16 @@ Initialize a data model.
- `name`: the name of the data model.
- `attributes`: the attributes of the data model.
+
+#### attributes`_`by`_`name
+
+```python
+ | @property
+ | attributes_by_name() -> Dict[str, Attribute]
+```
+
+Get the attributes by name.
+
#### `__`eq`__`
diff --git a/docs/api/mail/base.md b/docs/api/mail/base.md
index 57fffc4eb3..cea15e7958 100644
--- a/docs/api/mail/base.md
+++ b/docs/api/mail/base.md
@@ -3,24 +3,6 @@
Mail module abstract base classes.
-
-## AEAConnectionError Objects
-
-```python
-class AEAConnectionError(Exception)
-```
-
-Exception class for connection errors.
-
-
-## Empty Objects
-
-```python
-class Empty(Exception)
-```
-
-Exception for when the inbox is empty.
-
## URI Objects
@@ -174,13 +156,13 @@ Compare with another object.
class EnvelopeContext()
```
-Extra information for the handling of an envelope.
+Contains context information of an envelope.
#### `__`init`__`
```python
- | __init__(connection_id: Optional[PublicId] = None, skill_id: Optional[PublicId] = None, uri: Optional[URI] = None) -> None
+ | __init__(connection_id: Optional[PublicId] = None, uri: Optional[URI] = None) -> None
```
Initialize the envelope context.
@@ -188,38 +170,37 @@ Initialize the envelope context.
**Arguments**:
- `connection_id`: the connection id used for routing the outgoing envelope in the multiplexer.
-- `skill_id`: the skill id used for routing the incoming envelope in the AEA.
- `uri`: the URI sent with the envelope.
-
-#### connection`_`id
+
+#### uri
```python
| @property
- | connection_id() -> Optional[PublicId]
+ | uri() -> Optional[URI]
```
-Get the connection id.
+Get the URI.
-
-#### skill`_`id
+
+#### connection`_`id
```python
| @property
- | skill_id() -> Optional[PublicId]
+ | connection_id() -> Optional[PublicId]
```
-Get the skill id.
+Get the connection id to route the envelope.
-
-#### uri`_`raw
+
+#### connection`_`id
```python
- | @property
- | uri_raw() -> str
+ | @connection_id.setter
+ | connection_id(connection_id: PublicId) -> None
```
-Get uri in string format.
+Set the 'via' connection id.
#### `__`str`__`
@@ -239,6 +220,24 @@ Get the string representation.
Compare with another object.
+
+## AEAConnectionError Objects
+
+```python
+class AEAConnectionError(Exception)
+```
+
+Exception class for connection errors.
+
+
+## Empty Objects
+
+```python
+class Empty(Exception)
+```
+
+Exception for when the inbox is empty.
+
## EnvelopeSerializer Objects
@@ -440,38 +439,20 @@ Get the protocol-specific message.
```python
| @property
- | context() -> EnvelopeContext
+ | context() -> Optional[EnvelopeContext]
```
Get the envelope context.
-
-#### skill`_`id
-
-```python
- | @property
- | skill_id() -> Optional[PublicId]
-```
-
-Get the skill id from an envelope context, if set.
-
-**Returns**:
-
-skill id
-
-
-#### connection`_`id
+
+#### to`_`as`_`public`_`id
```python
| @property
- | connection_id() -> Optional[PublicId]
+ | to_as_public_id() -> Optional[PublicId]
```
-Get the connection id from an envelope context, if set.
-
-**Returns**:
-
-connection id
+Get to as public id.
#### is`_`sender`_`public`_`id
@@ -493,6 +474,16 @@ Check if sender is a public id.
Check if to is a public id.
+
+#### is`_`component`_`to`_`component`_`message
+
+```python
+ | @property
+ | is_component_to_component_message() -> bool
+```
+
+Whether or not the message contained is component to component.
+
#### `__`eq`__`
diff --git a/docs/api/manager/manager.md b/docs/api/manager/manager.md
index 9878607d78..f64f644e0f 100644
--- a/docs/api/manager/manager.md
+++ b/docs/api/manager/manager.md
@@ -3,6 +3,33 @@
This module contains the implementation of AEA agents manager.
+
+## ProjectNotFoundError Objects
+
+```python
+class ProjectNotFoundError(ValueError)
+```
+
+Project not found exception.
+
+
+## ProjectCheckError Objects
+
+```python
+class ProjectCheckError(ValueError)
+```
+
+Project check error exception.
+
+
+#### `__`init`__`
+
+```python
+ | __init__(msg: str, source_exception: Exception)
+```
+
+Init exception.
+
## AgentRunAsyncTask Objects
@@ -134,7 +161,7 @@ Multi agents manager.
#### `__`init`__`
```python
- | __init__(working_dir: str, mode: str = "async", registry_path: str = DEFAULT_REGISTRY_NAME) -> None
+ | __init__(working_dir: str, mode: str = "async", registry_path: str = DEFAULT_REGISTRY_NAME, auto_add_remove_project: bool = False) -> None
```
Initialize manager.
@@ -142,6 +169,13 @@ Initialize manager.
**Arguments**:
- `working_dir`: directory to store base agents.
+- `mode`: str. async or threaded
+- `registry_path`: str. path to the local packages registry
+- `auto_add_remove_project`: bool. add/remove project on the first agent add/last agent remove
+
+**Returns**:
+
+None
#### data`_`dir
@@ -213,6 +247,18 @@ registry, and then from remote registry in case of failure).
the MultiAgentManager instance.
+
+#### last`_`start`_`status
+
+```python
+ | @property
+ | last_start_status() -> Tuple[
+ | bool, Dict[PublicId, List[Dict]], List[Tuple[PublicId, List[Dict], Exception]],
+ | ]
+```
+
+Get status of the last agents start loading state.
+
#### stop`_`manager
@@ -249,6 +295,7 @@ registry, and then from remote registry in case of failure).
**Arguments**:
- `public_id`: the public if of the agent project.
+
- `local`: whether or not to fetch from local registry.
- `remote`: whether or not to fetch from remote registry.
- `restore`: bool flag for restoring already fetched agent.
@@ -279,7 +326,7 @@ list of public ids of projects
#### add`_`agent
```python
- | add_agent(public_id: PublicId, agent_name: Optional[str] = None, agent_overrides: Optional[dict] = None, component_overrides: Optional[List[dict]] = None) -> "MultiAgentManager"
+ | add_agent(public_id: PublicId, agent_name: Optional[str] = None, agent_overrides: Optional[dict] = None, component_overrides: Optional[List[dict]] = None, local: bool = False, remote: bool = False, restore: bool = False) -> "MultiAgentManager"
```
Create new agent configuration based on project with config overrides applied.
@@ -294,6 +341,10 @@ Alias is stored in memory only!
- `component_overrides`: overrides for component section.
- `config`: agent config (used for agent re-creation).
+- `local`: whether or not to fetch from local registry.
+- `remote`: whether or not to fetch from remote registry.
+- `restore`: bool flag for restoring already fetched agent.
+
**Returns**:
manager
@@ -389,7 +440,7 @@ list of agents names
#### remove`_`agent
```python
- | remove_agent(agent_name: str) -> "MultiAgentManager"
+ | remove_agent(agent_name: str, skip_project_auto_remove: bool = False) -> "MultiAgentManager"
```
Remove agent alias definition from registry.
@@ -397,6 +448,7 @@ Remove agent alias definition from registry.
**Arguments**:
- `agent_name`: agent name to remove
+- `skip_project_auto_remove`: disable auto project remove on last agent removed.
**Returns**:
diff --git a/docs/api/manager/project.md b/docs/api/manager/project.md
index 7b8d444305..4d8a8748f2 100644
--- a/docs/api/manager/project.md
+++ b/docs/api/manager/project.md
@@ -100,6 +100,15 @@ Remove project, do cleanup.
Get builder instance.
+
+#### check
+
+```python
+ | check() -> None
+```
+
+Check we can still construct an AEA from the project with builder.build.
+
## AgentAlias Objects
diff --git a/docs/api/crypto/cosmos.md b/docs/api/plugins/aea_ledger_cosmos/cosmos.md
similarity index 72%
rename from docs/api/crypto/cosmos.md
rename to docs/api/plugins/aea_ledger_cosmos/cosmos.md
index 08a68ef106..a5a92cbb18 100644
--- a/docs/api/crypto/cosmos.md
+++ b/docs/api/plugins/aea_ledger_cosmos/cosmos.md
@@ -1,9 +1,9 @@
-
-# aea.crypto.cosmos
+
+# plugins.aea-ledger-cosmos.aea`_`ledger`_`cosmos.cosmos
Cosmos module wrapping the public and private key cryptography and ledger api.
-
+
## CosmosHelper Objects
```python
@@ -12,7 +12,7 @@ class CosmosHelper(Helper)
Helper class usable as Mixin for CosmosApi or as standalone class.
-
+
#### is`_`transaction`_`settled
```python
@@ -30,7 +30,7 @@ Check whether a transaction is settled or not.
True if the transaction has been settled, False o/w.
-
+
#### get`_`code`_`id
```python
@@ -48,7 +48,7 @@ Retrieve the `code_id` from a transaction receipt.
the code id, if present
-
+
#### get`_`contract`_`address
```python
@@ -66,7 +66,7 @@ Retrieve the `contract_address` from a transaction receipt.
the contract address, if present
-
+
#### is`_`transaction`_`valid
```python
@@ -88,7 +88,7 @@ Check whether a transaction is valid or not.
True if the random_message is equals to tx['input']
-
+
#### generate`_`tx`_`nonce
```python
@@ -107,7 +107,7 @@ Generate a unique hash to distinguish txs with the same terms.
return the hash in hex.
-
+
#### get`_`address`_`from`_`public`_`key
```python
@@ -125,7 +125,7 @@ Get the address from the public key.
str
-
+
#### recover`_`message
```python
@@ -145,7 +145,7 @@ Recover the addresses from the hash.
the recovered addresses
-
+
#### recover`_`public`_`keys`_`from`_`message
```python
@@ -165,7 +165,7 @@ Get the public key used to produce the `signature` of the `message`
the recovered public keys
-
+
#### get`_`hash
```python
@@ -183,7 +183,7 @@ Get the hash of a message.
the hash of the message.
-
+
#### is`_`valid`_`address
```python
@@ -197,7 +197,7 @@ Check if the address is valid.
- `address`: the address to validate
-
+
#### load`_`contract`_`interface
```python
@@ -215,7 +215,7 @@ Load contract interface.
the interface
-
+
## CosmosCrypto Objects
```python
@@ -224,7 +224,7 @@ class CosmosCrypto(Crypto[SigningKey])
Class wrapping the Account Generation from Ethereum ledger.
-
+
#### `__`init`__`
```python
@@ -237,7 +237,7 @@ Instantiate an ethereum crypto object.
- `private_key_path`: the private key path of the agent
-
+
#### private`_`key
```python
@@ -251,7 +251,7 @@ Return a private key.
a private key string
-
+
#### public`_`key
```python
@@ -265,7 +265,7 @@ Return a public key in hex format.
a public key string in hex format
-
+
#### address
```python
@@ -279,7 +279,7 @@ Return the address for the key pair.
a display_address str
-
+
#### load`_`private`_`key`_`from`_`path
```python
@@ -297,7 +297,7 @@ Load a private key in hex format from a file.
the Entity.
-
+
#### sign`_`message
```python
@@ -315,7 +315,7 @@ Sign a message in bytes string form.
signature of the message in string form
-
+
#### sign`_`transaction
```python
@@ -332,7 +332,7 @@ Sign a transaction in bytes string form.
signed transaction
-
+
#### generate`_`private`_`key
```python
@@ -342,7 +342,7 @@ signed transaction
Generate a key pair for cosmos network.
-
+
#### dump
```python
@@ -359,7 +359,7 @@ Serialize crypto object as binary stream to `fp` (a `.write()`-supporting file-l
None
-
+
## `_`CosmosApi Objects
```python
@@ -368,7 +368,7 @@ class _CosmosApi(LedgerApi)
Class to interact with the Cosmos SDK via a HTTP APIs.
-
+
#### `__`init`__`
```python
@@ -377,7 +377,7 @@ Class to interact with the Cosmos SDK via a HTTP APIs.
Initialize the Cosmos ledger APIs.
-
+
#### api
```python
@@ -387,7 +387,7 @@ Initialize the Cosmos ledger APIs.
Get the underlying API object.
-
+
#### get`_`balance
```python
@@ -396,7 +396,7 @@ Get the underlying API object.
Get the balance of a given account.
-
+
#### get`_`state
```python
@@ -410,7 +410,7 @@ API specification, which takes a path (strings separated by '/'). The
convention here is to define the root of the path (txs, blocks, etc.)
as the callable_name and the rest of the path as args.
-
+
#### get`_`deploy`_`transaction
```python
@@ -427,7 +427,7 @@ Dispatches to _get_storage_transaction and _get_init_transaction based on kwargs
- `deployer_address`: The address that will deploy the contract.
:returns tx: the transaction dictionary.
-
+
#### get`_`handle`_`transaction
```python
@@ -441,6 +441,9 @@ Create a CosmWasm HandleMsg transaction.
- `sender_address`: the sender address of the message initiator.
- `contract_address`: the address of the smart contract.
- `handle_msg`: HandleMsg in JSON format.
+- `amount`: Funds amount sent with transaction.
+- `tx_fee`: the tx fee accepted.
+- `denom`: the name of the denomination of the contract funds
- `gas`: Maximum amount of gas to be used on executing command.
- `memo`: any string comment.
- `chain_id`: the Chain ID of the CosmWasm transaction. Default is 1 (i.e. mainnet).
@@ -449,7 +452,7 @@ Create a CosmWasm HandleMsg transaction.
the unsigned CosmWasm HandleMsg
-
+
#### execute`_`contract`_`query
```python
@@ -467,7 +470,7 @@ Execute a CosmWasm QueryMsg. QueryMsg doesn't require signing.
the message receipt
-
+
#### get`_`transfer`_`transaction
```python
@@ -492,7 +495,7 @@ Submit a transfer transaction to the ledger.
the transfer transaction
-
+
#### send`_`signed`_`transaction
```python
@@ -509,17 +512,16 @@ Send a signed transaction and wait for confirmation.
tx_digest, if present
-
+
#### is`_`cosmwasm`_`transaction
```python
- | @staticmethod
| is_cosmwasm_transaction(tx_signed: JSONLike) -> bool
```
Check whether it is a cosmwasm tx.
-
+
#### is`_`transfer`_`transaction
```python
@@ -529,7 +531,7 @@ Check whether it is a cosmwasm tx.
Check whether it is a transfer tx.
-
+
#### get`_`transaction`_`receipt
```python
@@ -546,7 +548,7 @@ Get the transaction receipt for a transaction digest.
the tx receipt, if present
-
+
#### get`_`transaction
```python
@@ -563,7 +565,7 @@ Get the transaction for a transaction digest.
the tx, if present
-
+
#### get`_`contract`_`instance
```python
@@ -581,7 +583,7 @@ Get the instance of a contract.
the contract instance
-
+
#### get`_`last`_`code`_`id
```python
@@ -594,7 +596,7 @@ Get ID of latest deployed .wasm bytecode.
code id of last deployed .wasm bytecode
-
+
#### get`_`last`_`contract`_`address
```python
@@ -611,7 +613,7 @@ Get contract address of latest initialised contract by its ID.
contract address of last initialised contract
-
+
#### update`_`with`_`gas`_`estimate
```python
@@ -628,7 +630,7 @@ Attempts to update the transaction with a gas estimate
the updated transaction
-
+
## CosmosApi Objects
```python
@@ -637,7 +639,7 @@ class CosmosApi(_CosmosApi, CosmosHelper)
Class to interact with the Cosmos SDK via a HTTP APIs.
-
+
## CosmosFaucetApi Objects
```python
@@ -646,7 +648,7 @@ class CosmosFaucetApi(FaucetApi)
Cosmos testnet faucet API.
-
+
#### `__`init`__`
```python
@@ -655,7 +657,7 @@ Cosmos testnet faucet API.
Initialize CosmosFaucetApi.
-
+
#### get`_`wealth
```python
diff --git a/docs/api/crypto/ethereum.md b/docs/api/plugins/aea_ledger_ethereum/ethereum.md
similarity index 67%
rename from docs/api/crypto/ethereum.md
rename to docs/api/plugins/aea_ledger_ethereum/ethereum.md
index 02d4ae585b..16f6db449a 100644
--- a/docs/api/crypto/ethereum.md
+++ b/docs/api/plugins/aea_ledger_ethereum/ethereum.md
@@ -1,9 +1,9 @@
-
-# aea.crypto.ethereum
+
+# plugins.aea-ledger-ethereum.aea`_`ledger`_`ethereum.ethereum
Ethereum module wrapping the public and private key cryptography and ledger api.
-
+
## SignedTransactionTranslator Objects
```python
@@ -12,7 +12,7 @@ class SignedTransactionTranslator()
Translator for SignedTransaction.
-
+
#### to`_`dict
```python
@@ -22,7 +22,7 @@ Translator for SignedTransaction.
Write SignedTransaction to dict.
-
+
#### from`_`dict
```python
@@ -32,7 +32,7 @@ Write SignedTransaction to dict.
Get SignedTransaction from dict.
-
+
## AttributeDictTranslator Objects
```python
@@ -41,7 +41,7 @@ class AttributeDictTranslator()
Translator for AttributeDict.
-
+
#### to`_`dict
```python
@@ -51,7 +51,7 @@ Translator for AttributeDict.
Simplify to dict.
-
+
#### from`_`dict
```python
@@ -61,7 +61,7 @@ Simplify to dict.
Get back attribute dict.
-
+
## EthereumCrypto Objects
```python
@@ -70,7 +70,7 @@ class EthereumCrypto(Crypto[Account])
Class wrapping the Account Generation from Ethereum ledger.
-
+
#### `__`init`__`
```python
@@ -83,7 +83,7 @@ Instantiate an ethereum crypto object.
- `private_key_path`: the private key path of the agent
-
+
#### private`_`key
```python
@@ -97,7 +97,7 @@ Return a private key.
a private key string
-
+
#### public`_`key
```python
@@ -111,7 +111,7 @@ Return a public key in hex format.
a public key string in hex format
-
+
#### address
```python
@@ -125,7 +125,7 @@ Return the address for the key pair.
a display_address str
-
+
#### load`_`private`_`key`_`from`_`path
```python
@@ -143,7 +143,7 @@ Load a private key in hex format from a file.
the Entity.
-
+
#### sign`_`message
```python
@@ -161,7 +161,7 @@ Sign a message in bytes string form.
signature of the message in string form
-
+
#### sign`_`transaction
```python
@@ -178,7 +178,7 @@ Sign a transaction in bytes string form.
signed transaction
-
+
#### generate`_`private`_`key
```python
@@ -188,7 +188,7 @@ signed transaction
Generate a key pair for ethereum network.
-
+
#### dump
```python
@@ -205,7 +205,7 @@ Serialize crypto object as binary stream to `fp` (a `.write()`-supporting file-l
None
-
+
## EthereumHelper Objects
```python
@@ -214,7 +214,7 @@ class EthereumHelper(Helper)
Helper class usable as Mixin for EthereumApi or as standalone class.
-
+
#### is`_`transaction`_`settled
```python
@@ -232,7 +232,7 @@ Check whether a transaction is settled or not.
True if the transaction has been settled, False o/w.
-
+
#### is`_`transaction`_`valid
```python
@@ -254,7 +254,7 @@ Check whether a transaction is valid or not.
True if the random_message is equals to tx['input']
-
+
#### generate`_`tx`_`nonce
```python
@@ -273,7 +273,7 @@ Generate a unique hash to distinguish txs with the same terms.
return the hash in hex.
-
+
#### get`_`address`_`from`_`public`_`key
```python
@@ -291,7 +291,7 @@ Get the address from the public key.
str
-
+
#### recover`_`message
```python
@@ -311,7 +311,7 @@ Recover the addresses from the hash.
the recovered addresses
-
+
#### recover`_`public`_`keys`_`from`_`message
```python
@@ -331,7 +331,7 @@ Get the public key used to produce the `signature` of the `message`
the recovered public keys
-
+
#### get`_`hash
```python
@@ -349,7 +349,7 @@ Get the hash of a message.
the hash of the message.
-
+
#### load`_`contract`_`interface
```python
@@ -367,7 +367,7 @@ Load contract interface.
the interface
-
+
## EthereumApi Objects
```python
@@ -376,7 +376,7 @@ class EthereumApi(LedgerApi, EthereumHelper)
Class to interact with the Ethereum Web3 APIs.
-
+
#### `__`init`__`
```python
@@ -389,7 +389,7 @@ Initialize the Ethereum ledger APIs.
- `address`: the endpoint for Web3 APIs.
-
+
#### api
```python
@@ -399,7 +399,7 @@ Initialize the Ethereum ledger APIs.
Get the underlying API object.
-
+
#### get`_`balance
```python
@@ -408,7 +408,7 @@ Get the underlying API object.
Get the balance of a given account.
-
+
#### get`_`state
```python
@@ -417,7 +417,7 @@ Get the balance of a given account.
Call a specified function on the ledger API.
-
+
#### get`_`transfer`_`transaction
```python
@@ -440,7 +440,7 @@ Submit a transfer transaction to the ledger.
the transfer transaction
-
+
#### update`_`with`_`gas`_`estimate
```python
@@ -457,7 +457,7 @@ Attempts to update the transaction with a gas estimate
the updated transaction
-
+
#### send`_`signed`_`transaction
```python
@@ -474,7 +474,7 @@ Send a signed transaction and wait for confirmation.
tx_digest, if present
-
+
#### get`_`transaction`_`receipt
```python
@@ -491,7 +491,7 @@ Get the transaction receipt for a transaction digest.
the tx receipt, if present
-
+
#### get`_`transaction
```python
@@ -508,7 +508,7 @@ Get the transaction for a transaction digest.
the tx, if present
-
+
#### get`_`contract`_`instance
```python
@@ -526,7 +526,7 @@ Get the instance of a contract.
the contract instance
-
+
#### get`_`deploy`_`transaction
```python
@@ -543,7 +543,7 @@ Get the transaction to deploy the smart contract.
- `gas`: the gas to be used
:returns tx: the transaction dictionary.
-
+
#### is`_`valid`_`address
```python
@@ -557,7 +557,7 @@ Check if the address is valid.
- `address`: the address to validate
-
+
## EthereumFaucetApi Objects
```python
@@ -566,7 +566,7 @@ class EthereumFaucetApi(FaucetApi)
Ethereum testnet faucet API.
-
+
#### get`_`wealth
```python
@@ -584,7 +584,7 @@ Get wealth from the faucet for the provided address.
None
-
+
## LruLockWrapper Objects
```python
@@ -593,7 +593,7 @@ class LruLockWrapper()
Wrapper for LRU with threading.Lock.
-
+
#### `__`init`__`
```python
@@ -602,7 +602,7 @@ Wrapper for LRU with threading.Lock.
Init wrapper.
-
+
#### `__`getitem`__`
```python
@@ -611,7 +611,7 @@ Init wrapper.
Get item
-
+
#### `__`setitem`__`
```python
@@ -620,7 +620,7 @@ Get item
Set item.
-
+
#### `__`contains`__`
```python
@@ -629,7 +629,7 @@ Set item.
Contain item.
-
+
#### `__`delitem`__`
```python
@@ -638,7 +638,7 @@ Contain item.
Del item.
-
+
#### set`_`wrapper`_`for`_`web3py`_`session`_`cache
```python
diff --git a/docs/api/plugins/aea_ledger_fetchai/_cosmos.md b/docs/api/plugins/aea_ledger_fetchai/_cosmos.md
new file mode 100644
index 0000000000..d849086144
--- /dev/null
+++ b/docs/api/plugins/aea_ledger_fetchai/_cosmos.md
@@ -0,0 +1,678 @@
+
+# plugins.aea-ledger-fetchai.aea`_`ledger`_`fetchai.`_`cosmos
+
+Cosmos module wrapping the public and private key cryptography and ledger api.
+
+
+## CosmosHelper Objects
+
+```python
+class CosmosHelper(Helper)
+```
+
+Helper class usable as Mixin for CosmosApi or as standalone class.
+
+
+#### is`_`transaction`_`settled
+
+```python
+ | @staticmethod
+ | is_transaction_settled(tx_receipt: JSONLike) -> bool
+```
+
+Check whether a transaction is settled or not.
+
+**Arguments**:
+
+- `tx_receipt`: the receipt of the transaction.
+
+**Returns**:
+
+True if the transaction has been settled, False o/w.
+
+
+#### get`_`code`_`id
+
+```python
+ | @staticmethod
+ | get_code_id(tx_receipt: JSONLike) -> Optional[int]
+```
+
+Retrieve the `code_id` from a transaction receipt.
+
+**Arguments**:
+
+- `tx_receipt`: the receipt of the transaction.
+
+**Returns**:
+
+the code id, if present
+
+
+#### get`_`contract`_`address
+
+```python
+ | @staticmethod
+ | get_contract_address(tx_receipt: JSONLike) -> Optional[str]
+```
+
+Retrieve the `contract_address` from a transaction receipt.
+
+**Arguments**:
+
+- `tx_receipt`: the receipt of the transaction.
+
+**Returns**:
+
+the contract address, if present
+
+
+#### is`_`transaction`_`valid
+
+```python
+ | @staticmethod
+ | is_transaction_valid(tx: JSONLike, seller: Address, client: Address, tx_nonce: str, amount: int) -> bool
+```
+
+Check whether a transaction is valid or not.
+
+**Arguments**:
+
+- `tx`: the transaction.
+- `seller`: the address of the seller.
+- `client`: the address of the client.
+- `tx_nonce`: the transaction nonce.
+- `amount`: the amount we expect to get from the transaction.
+
+**Returns**:
+
+True if the random_message is equals to tx['input']
+
+
+#### generate`_`tx`_`nonce
+
+```python
+ | @staticmethod
+ | generate_tx_nonce(seller: Address, client: Address) -> str
+```
+
+Generate a unique hash to distinguish txs with the same terms.
+
+**Arguments**:
+
+- `seller`: the address of the seller.
+- `client`: the address of the client.
+
+**Returns**:
+
+return the hash in hex.
+
+
+#### get`_`address`_`from`_`public`_`key
+
+```python
+ | @classmethod
+ | get_address_from_public_key(cls, public_key: str) -> str
+```
+
+Get the address from the public key.
+
+**Arguments**:
+
+- `public_key`: the public key
+
+**Returns**:
+
+str
+
+
+#### recover`_`message
+
+```python
+ | @classmethod
+ | recover_message(cls, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[Address, ...]
+```
+
+Recover the addresses from the hash.
+
+**Arguments**:
+
+- `message`: the message we expect
+- `signature`: the transaction signature
+- `is_deprecated_mode`: if the deprecated signing was used
+
+**Returns**:
+
+the recovered addresses
+
+
+#### recover`_`public`_`keys`_`from`_`message
+
+```python
+ | @classmethod
+ | recover_public_keys_from_message(cls, message: bytes, signature: str, is_deprecated_mode: bool = False) -> Tuple[str, ...]
+```
+
+Get the public key used to produce the `signature` of the `message`
+
+**Arguments**:
+
+- `message`: raw bytes used to produce signature
+- `signature`: signature of the message
+- `is_deprecated_mode`: if the deprecated signing was used
+
+**Returns**:
+
+the recovered public keys
+
+
+#### get`_`hash
+
+```python
+ | @staticmethod
+ | get_hash(message: bytes) -> str
+```
+
+Get the hash of a message.
+
+**Arguments**:
+
+- `message`: the message to be hashed.
+
+**Returns**:
+
+the hash of the message.
+
+
+#### is`_`valid`_`address
+
+```python
+ | @classmethod
+ | is_valid_address(cls, address: Address) -> bool
+```
+
+Check if the address is valid.
+
+**Arguments**:
+
+- `address`: the address to validate
+
+
+#### load`_`contract`_`interface
+
+```python
+ | @classmethod
+ | load_contract_interface(cls, file_path: Path) -> Dict[str, str]
+```
+
+Load contract interface.
+
+**Arguments**:
+
+- `file_path`: the file path to the interface
+
+**Returns**:
+
+the interface
+
+
+## CosmosCrypto Objects
+
+```python
+class CosmosCrypto(Crypto[SigningKey])
+```
+
+Class wrapping the Account Generation from Ethereum ledger.
+
+
+#### `__`init`__`
+
+```python
+ | __init__(private_key_path: Optional[str] = None) -> None
+```
+
+Instantiate an ethereum crypto object.
+
+**Arguments**:
+
+- `private_key_path`: the private key path of the agent
+
+
+#### private`_`key
+
+```python
+ | @property
+ | private_key() -> str
+```
+
+Return a private key.
+
+**Returns**:
+
+a private key string
+
+
+#### public`_`key
+
+```python
+ | @property
+ | public_key() -> str
+```
+
+Return a public key in hex format.
+
+**Returns**:
+
+a public key string in hex format
+
+
+#### address
+
+```python
+ | @property
+ | address() -> str
+```
+
+Return the address for the key pair.
+
+**Returns**:
+
+a display_address str
+
+
+#### load`_`private`_`key`_`from`_`path
+
+```python
+ | @classmethod
+ | load_private_key_from_path(cls, file_name: str) -> SigningKey
+```
+
+Load a private key in hex format from a file.
+
+**Arguments**:
+
+- `file_name`: the path to the hex file.
+
+**Returns**:
+
+the Entity.
+
+
+#### sign`_`message
+
+```python
+ | sign_message(message: bytes, is_deprecated_mode: bool = False) -> str
+```
+
+Sign a message in bytes string form.
+
+**Arguments**:
+
+- `message`: the message to be signed
+- `is_deprecated_mode`: if the deprecated signing is used
+
+**Returns**:
+
+signature of the message in string form
+
+
+#### sign`_`transaction
+
+```python
+ | sign_transaction(transaction: JSONLike) -> JSONLike
+```
+
+Sign a transaction in bytes string form.
+
+**Arguments**:
+
+- `transaction`: the transaction to be signed
+
+**Returns**:
+
+signed transaction
+
+
+#### generate`_`private`_`key
+
+```python
+ | @classmethod
+ | generate_private_key(cls) -> SigningKey
+```
+
+Generate a key pair for cosmos network.
+
+
+#### dump
+
+```python
+ | dump(fp: BinaryIO) -> None
+```
+
+Serialize crypto object as binary stream to `fp` (a `.write()`-supporting file-like object).
+
+**Arguments**:
+
+- `fp`: the output file pointer. Must be set in binary mode (mode='wb')
+
+**Returns**:
+
+None
+
+
+## `_`CosmosApi Objects
+
+```python
+class _CosmosApi(LedgerApi)
+```
+
+Class to interact with the Cosmos SDK via a HTTP APIs.
+
+
+#### `__`init`__`
+
+```python
+ | __init__(**kwargs: Any) -> None
+```
+
+Initialize the Cosmos ledger APIs.
+
+
+#### api
+
+```python
+ | @property
+ | api() -> Any
+```
+
+Get the underlying API object.
+
+
+#### get`_`balance
+
+```python
+ | get_balance(address: Address) -> Optional[int]
+```
+
+Get the balance of a given account.
+
+
+#### get`_`state
+
+```python
+ | get_state(callable_name: str, *args: Any, **kwargs: Any) -> Optional[JSONLike]
+```
+
+Call a specified function on the ledger API.
+
+Based on the cosmos REST
+API specification, which takes a path (strings separated by '/'). The
+convention here is to define the root of the path (txs, blocks, etc.)
+as the callable_name and the rest of the path as args.
+
+
+#### get`_`deploy`_`transaction
+
+```python
+ | get_deploy_transaction(contract_interface: Dict[str, str], deployer_address: Address, **kwargs: Any, ,) -> Optional[JSONLike]
+```
+
+Get the transaction to deploy the smart contract.
+
+Dispatches to _get_storage_transaction and _get_init_transaction based on kwargs.
+
+**Arguments**:
+
+- `contract_interface`: the contract interface.
+- `deployer_address`: The address that will deploy the contract.
+:returns tx: the transaction dictionary.
+
+
+#### get`_`handle`_`transaction
+
+```python
+ | get_handle_transaction(sender_address: Address, contract_address: Address, handle_msg: Any, amount: int, tx_fee: int, denom: Optional[str] = None, gas: int = 0, memo: str = "", chain_id: Optional[str] = None) -> Optional[JSONLike]
+```
+
+Create a CosmWasm HandleMsg transaction.
+
+**Arguments**:
+
+- `sender_address`: the sender address of the message initiator.
+- `contract_address`: the address of the smart contract.
+- `handle_msg`: HandleMsg in JSON format.
+- `amount`: Funds amount sent with transaction.
+- `tx_fee`: the tx fee accepted.
+- `denom`: the name of the denomination of the contract funds
+- `gas`: Maximum amount of gas to be used on executing command.
+- `memo`: any string comment.
+- `chain_id`: the Chain ID of the CosmWasm transaction. Default is 1 (i.e. mainnet).
+
+**Returns**:
+
+the unsigned CosmWasm HandleMsg
+
+
+#### execute`_`contract`_`query
+
+```python
+ | execute_contract_query(contract_address: Address, query_msg: JSONLike) -> Optional[JSONLike]
+```
+
+Execute a CosmWasm QueryMsg. QueryMsg doesn't require signing.
+
+**Arguments**:
+
+- `contract_address`: the address of the smart contract.
+- `query_msg`: QueryMsg in JSON format.
+
+**Returns**:
+
+the message receipt
+
+
+#### get`_`transfer`_`transaction
+
+```python
+ | get_transfer_transaction(sender_address: Address, destination_address: Address, amount: int, tx_fee: int, tx_nonce: str, denom: Optional[str] = None, gas: int = 80000, memo: str = "", chain_id: Optional[str] = None, **kwargs: Any, ,) -> Optional[JSONLike]
+```
+
+Submit a transfer transaction to the ledger.
+
+**Arguments**:
+
+- `sender_address`: the sender address of the payer.
+- `destination_address`: the destination address of the payee.
+- `amount`: the amount of wealth to be transferred.
+- `tx_fee`: the transaction fee.
+- `tx_nonce`: verifies the authenticity of the tx
+- `denom`: the denomination of tx fee and amount
+- `gas`: the gas used.
+- `memo`: memo to include in tx.
+- `chain_id`: the chain ID of the transaction.
+
+**Returns**:
+
+the transfer transaction
+
+
+#### send`_`signed`_`transaction
+
+```python
+ | send_signed_transaction(tx_signed: JSONLike) -> Optional[str]
+```
+
+Send a signed transaction and wait for confirmation.
+
+**Arguments**:
+
+- `tx_signed`: the signed transaction
+
+**Returns**:
+
+tx_digest, if present
+
+
+#### is`_`cosmwasm`_`transaction
+
+```python
+ | is_cosmwasm_transaction(tx_signed: JSONLike) -> bool
+```
+
+Check whether it is a cosmwasm tx.
+
+
+#### is`_`transfer`_`transaction
+
+```python
+ | @staticmethod
+ | is_transfer_transaction(tx_signed: JSONLike) -> bool
+```
+
+Check whether it is a transfer tx.
+
+
+#### get`_`transaction`_`receipt
+
+```python
+ | get_transaction_receipt(tx_digest: str) -> Optional[JSONLike]
+```
+
+Get the transaction receipt for a transaction digest.
+
+**Arguments**:
+
+- `tx_digest`: the digest associated to the transaction.
+
+**Returns**:
+
+the tx receipt, if present
+
+
+#### get`_`transaction
+
+```python
+ | get_transaction(tx_digest: str) -> Optional[JSONLike]
+```
+
+Get the transaction for a transaction digest.
+
+**Arguments**:
+
+- `tx_digest`: the digest associated to the transaction.
+
+**Returns**:
+
+the tx, if present
+
+
+#### get`_`contract`_`instance
+
+```python
+ | get_contract_instance(contract_interface: Dict[str, str], contract_address: Optional[str] = None) -> Any
+```
+
+Get the instance of a contract.
+
+**Arguments**:
+
+- `contract_interface`: the contract interface.
+- `contract_address`: the contract address.
+
+**Returns**:
+
+the contract instance
+
+
+#### get`_`last`_`code`_`id
+
+```python
+ | get_last_code_id() -> int
+```
+
+Get ID of latest deployed .wasm bytecode.
+
+**Returns**:
+
+code id of last deployed .wasm bytecode
+
+
+#### get`_`last`_`contract`_`address
+
+```python
+ | get_last_contract_address(code_id: int) -> str
+```
+
+Get contract address of latest initialised contract by its ID.
+
+**Arguments**:
+
+- `code_id`: id of deployed CosmWasm bytecode
+
+**Returns**:
+
+contract address of last initialised contract
+
+
+#### update`_`with`_`gas`_`estimate
+
+```python
+ | update_with_gas_estimate(transaction: JSONLike) -> JSONLike
+```
+
+Attempts to update the transaction with a gas estimate
+
+**Arguments**:
+
+- `transaction`: the transaction
+
+**Returns**:
+
+the updated transaction
+
+
+## CosmosApi Objects
+
+```python
+class CosmosApi(_CosmosApi, CosmosHelper)
+```
+
+Class to interact with the Cosmos SDK via a HTTP APIs.
+
+
+## CosmosFaucetApi Objects
+
+```python
+class CosmosFaucetApi(FaucetApi)
+```
+
+Cosmos testnet faucet API.
+
+
+#### `__`init`__`
+
+```python
+ | __init__(poll_interval: Optional[float] = None)
+```
+
+Initialize CosmosFaucetApi.
+
+
+#### get`_`wealth
+
+```python
+ | get_wealth(address: Address, url: Optional[str] = None) -> None
+```
+
+Get wealth from the faucet for the provided address.
+
+**Arguments**:
+
+- `address`: the address.
+- `url`: the url
+
+**Returns**:
+
+None
+:raises: RuntimeError of explicit faucet failures
+
diff --git a/docs/api/crypto/fetchai.md b/docs/api/plugins/aea_ledger_fetchai/fetchai.md
similarity index 56%
rename from docs/api/crypto/fetchai.md
rename to docs/api/plugins/aea_ledger_fetchai/fetchai.md
index d1d44776e4..8bff79a609 100644
--- a/docs/api/crypto/fetchai.md
+++ b/docs/api/plugins/aea_ledger_fetchai/fetchai.md
@@ -1,9 +1,9 @@
-
-# aea.crypto.fetchai
+
+# plugins.aea-ledger-fetchai.aea`_`ledger`_`fetchai.fetchai
Fetchai module wrapping the public and private key cryptography and ledger api.
-
+
## FetchAIHelper Objects
```python
@@ -12,7 +12,7 @@ class FetchAIHelper(CosmosHelper)
Helper class usable as Mixin for FetchAIApi or as standalone class.
-
+
## FetchAICrypto Objects
```python
@@ -21,7 +21,7 @@ class FetchAICrypto(CosmosCrypto)
Class wrapping the Entity Generation from Fetch.AI ledger.
-
+
## FetchAIApi Objects
```python
@@ -30,7 +30,7 @@ class FetchAIApi(_CosmosApi, FetchAIHelper)
Class to interact with the Fetch ledger APIs.
-
+
#### `__`init`__`
```python
@@ -39,7 +39,7 @@ Class to interact with the Fetch ledger APIs.
Initialize the Fetch.ai ledger APIs.
-
+
## FetchAIFaucetApi Objects
```python
diff --git a/docs/api/protocols/dialogue/base.md b/docs/api/protocols/dialogue/base.md
index df79d91235..68d5cad605 100644
--- a/docs/api/protocols/dialogue/base.md
+++ b/docs/api/protocols/dialogue/base.md
@@ -169,8 +169,7 @@ class _DialogueMeta(type)
Metaclass for Dialogue.
-Adds slot support forevery subclass
-Creates classlevvel Rules instance
+Creates class level Rules instance to share among instances
#### `__`new`__`
@@ -422,7 +421,7 @@ The incomplete dialogue label
| dialogue_labels() -> Set[DialogueLabel]
```
-Get the dialogue labels (incomplete and complete, if it exists)
+Get the dialogue labels (incomplete and complete, if it exists).
**Returns**:
diff --git a/docs/api/skills/base.md b/docs/api/skills/base.md
index 8501e4ff08..24ea005277 100644
--- a/docs/api/skills/base.md
+++ b/docs/api/skills/base.md
@@ -302,6 +302,24 @@ Get the agent context namespace.
Get attribute.
+
+#### send`_`to`_`skill
+
+```python
+ | send_to_skill(message_or_envelope: Union[Message, Envelope], context: Optional[EnvelopeContext] = None) -> None
+```
+
+Send message or envelope to another skill.
+
+**Arguments**:
+
+- `message_or_envelope`: envelope to send to another skill.
+if message passed it will be wrapped into envelope with optional envelope context.
+
+**Returns**:
+
+None
+
## SkillComponent Objects
@@ -619,7 +637,7 @@ Tear the class down.
| parse_module(cls, path: str, model_configs: Dict[str, SkillComponentConfiguration], skill_context: SkillContext) -> Dict[str, "Model"]
```
-Parse the tasks module.
+Parse the model module.
**Arguments**:
@@ -758,3 +776,48 @@ Load the skill from configuration.
the skill.
+
+## `_`SkillComponentLoadingItem Objects
+
+```python
+class _SkillComponentLoadingItem()
+```
+
+Class to represent a triple (component name, component configuration, component class).
+
+
+#### `__`init`__`
+
+```python
+ | __init__(name: str, config: SkillComponentConfiguration, class_: Type[SkillComponent], type_: _SKILL_COMPONENT_TYPES)
+```
+
+Initialize the item.
+
+
+## `_`SkillComponentLoader Objects
+
+```python
+class _SkillComponentLoader()
+```
+
+This class implements the loading policy for skill components.
+
+
+#### `__`init`__`
+
+```python
+ | __init__(configuration: SkillConfig, skill_context: SkillContext, **kwargs: Any)
+```
+
+Initialize the helper class.
+
+
+#### load`_`skill
+
+```python
+ | load_skill() -> Skill
+```
+
+Load the skill.
+
diff --git a/docs/api/test_tools/test_skill.md b/docs/api/test_tools/test_skill.md
index ec44907fb3..766294b1f8 100644
--- a/docs/api/test_tools/test_skill.md
+++ b/docs/api/test_tools/test_skill.md
@@ -118,7 +118,7 @@ boolean result of the evaluation and accompanied message
#### build`_`incoming`_`message
```python
- | build_incoming_message(message_type: Type[Message], performative: Message.Performative, dialogue_reference: Optional[Tuple[str, str]] = None, message_id: Optional[int] = None, target: Optional[int] = None, to: Optional[Address] = None, sender: Address = COUNTERPARTY_ADDRESS, **kwargs: Any, ,) -> Message
+ | build_incoming_message(message_type: Type[Message], performative: Message.Performative, dialogue_reference: Optional[Tuple[str, str]] = None, message_id: Optional[int] = None, target: Optional[int] = None, to: Optional[Address] = None, sender: Optional[Address] = None, is_agent_to_agent_messages: Optional[bool] = None, **kwargs: Any, ,) -> Message
```
Quickly create an incoming message with the provided attributes.
@@ -134,6 +134,7 @@ For any attribute not provided, the corresponding default value in message is us
- `performative`: the performative
- `to`: the 'to' address
- `sender`: the 'sender' address
+- `is_agent_to_agent_messages`: whether the dialogue is between agents or components
- `kwargs`: other attributes
**Returns**:
@@ -176,7 +177,7 @@ the created incoming message
#### prepare`_`skill`_`dialogue
```python
- | prepare_skill_dialogue(dialogues: Dialogues, messages: Tuple[DialogueMessage, ...], counterparty: Address = COUNTERPARTY_ADDRESS) -> Dialogue
+ | prepare_skill_dialogue(dialogues: Dialogues, messages: Tuple[DialogueMessage, ...], counterparty: Optional[Address] = None, is_agent_to_agent_messages: Optional[bool] = None) -> Dialogue
```
Quickly create a dialogue.
@@ -193,6 +194,7 @@ for any other message, it is the index of the message before it in the tuple of
- `dialogues`: a dialogues class
- `counterparty`: the message_id
- `messages`: the dialogue_reference
+- `is_agent_to_agent_messages`: whether the dialogue is between agents or components
**Returns**:
diff --git a/docs/aries-cloud-agent-demo.md b/docs/aries-cloud-agent-demo.md
index 8197c033e1..01351a58ca 100644
--- a/docs/aries-cloud-agent-demo.md
+++ b/docs/aries-cloud-agent-demo.md
@@ -180,7 +180,7 @@ Now you can create **Alice_AEA** and **Faber_AEA** in terminals 3 and 4 respecti
In the third terminal, fetch **Alice_AEA** and move into its project folder:
``` bash
-aea fetch fetchai/aries_alice:0.21.0
+aea fetch fetchai/aries_alice:0.22.0
cd aries_alice
```
@@ -191,11 +191,11 @@ The following steps create **Alice_AEA** from scratch:
``` bash
aea create aries_alice
cd aries_alice
-aea add connection fetchai/p2p_libp2p:0.16.0
-aea add connection fetchai/soef:0.17.0
-aea add connection fetchai/http_client:0.17.0
-aea add connection fetchai/webhook:0.13.0
-aea add skill fetchai/aries_alice:0.16.0
+aea add connection fetchai/p2p_libp2p:0.17.0
+aea add connection fetchai/soef:0.18.0
+aea add connection fetchai/http_client:0.18.0
+aea add connection fetchai/webhook:0.14.0
+aea add skill fetchai/aries_alice:0.17.0
```
fetchai/default:0.12.0
protocol which satisfies the following protobuf schema:
+fetchai/default:0.13.0
protocol which satisfies the following protobuf schema:
``` proto
syntax = "proto3";
@@ -121,7 +121,7 @@ message DefaultMessage{
fetchai/default:0.12.0
protocol. The protobuf schema is given above.
+fetchai/default:0.13.0
protocol. The protobuf schema is given above.
Note
+For agent-to-agent communication it is advisable to have a single skill implement a given protocol. Skills can then forward the messages via skill-to-skill communication to other skills where required. Otherwise, received agent-to-agent messages will be forwarded to all skills implementing a handler for the specified protocol and the developer needs to take care to handle them appropriately (e.g. avoid multiple replies to a single message). +
+Note
-Currently `p2p_libp2p` connection limits the message total size to 3 MB. +
Currently `p2p_libp2p` connection limits the total message size to 3 MB.
vendor
folder populated with some default packages?fetchai/default:0.12.0
, fetchai/state_update:0.10.0
and fetchai/signing:0.10.0
protocols. These (as all other packages installed from the registry) are placed in the vendor
folder.
+All AEA projects by default hold the fetchai/default:0.13.0
, fetchai/state_update:0.11.0
and fetchai/signing:0.11.0
protocols. These (as all other packages installed from the registry) are placed in the vendor
folder.
@@ -684,7 +686,7 @@ class Strategy(Model): return description ``` -We create a `Model` type dialogue class and place it in `dialogues.py`. These classes ensure that the message flow satisfies the `fetchai/oef_search:0.13.0` protocol and keep track of the individual messages being sent and received. +We create a `Model` type dialogue class and place it in `dialogues.py`. These classes ensure that the message flow satisfies the `fetchai/oef_search:0.14.0` protocol and keep track of the individual messages being sent and received. ``` python from aea.protocols.base import Message @@ -728,7 +730,7 @@ class OefSearchDialogues(Model, BaseOefSearchDialogues): BaseOefSearchDialogues.__init__( self, - self_address=self.context.agent_address, + self_address=str(self.skill_id), role_from_first_message=role_from_first_message, ) @@ -749,7 +751,7 @@ from packages.fetchai.skills.simple_service_registration.dialogues import ( OefSearchDialogues, ) -LEDGER_API_ADDRESS = "fetchai/ledger:0.13.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.14.0" class OefSearchHandler(Handler): @@ -849,7 +851,7 @@ version: 0.4.0 type: skill description: The simple service registration skills is a skill to register a service. license: Apache-2.0 -aea_version: '>=0.10.0, <0.11.0' +aea_version: '>=0.11.0, <0.12.0' fingerprint: __init__.py: QmNkZAetyctaZCUf6ACxP5onGWsSxu2hjSNoFmJ3ta6Lta behaviours.py: QmRr1oe3zWKyPcktzKP4BiKqjCqmKjEDdLUQhn1JzNm4nD @@ -860,7 +862,7 @@ fingerprint_ignore_patterns: [] connections: [] contracts: [] protocols: -- fetchai/oef_search:0.13.0 +- fetchai/oef_search:0.14.0 skills: [] behaviours: service: diff --git a/docs/skill-testing.md b/docs/skill-testing.md index 55ad74f175..4411b630d1 100644 --- a/docs/skill-testing.md +++ b/docs/skill-testing.md @@ -117,6 +117,6 @@ In the above, we mock the logger before running `my_behaviour`'s `act()` method ## Next steps -You can consult the `fetchai/generic_buyer` and `fetchai/generic_seller` skills and their associated tests here to study how `BaseSkillTestCase` can help you in testing your skills. +You can consult the `fetchai/generic_buyer` and `fetchai/generic_seller` skills and their associated tests here to study how `BaseSkillTestCase` can help you in testing your skills. You can also refer to the API to study the different methods `BaseSkillTestCase` makes available to make testing your skills easier. diff --git a/docs/skill.md b/docs/skill.md index 703fd8831a..d8636f6efb 100644 --- a/docs/skill.md +++ b/docs/skill.md @@ -262,7 +262,7 @@ handlers: models: {} dependencies: {} protocols: -- fetchai/default:0.12.0 +- fetchai/default:0.13.0 ``` @@ -275,7 +275,7 @@ All AEAs have a default `error` skill that contains error handling code for a nu * Envelopes with decoding errors * Invalid messages with respect to the registered protocol -The error skill relies on the `fetchai/default:0.12.0` protocol which provides error codes for the above. +The error skill relies on the `fetchai/default:0.13.0` protocol which provides error codes for the above. ## Custom Error handler diff --git a/docs/standalone-transaction.md b/docs/standalone-transaction.md index bf23bb1275..6af876a360 100644 --- a/docs/standalone-transaction.md +++ b/docs/standalone-transaction.md @@ -1,11 +1,17 @@ In this guide, we will generate some wealth for the Fetch.ai testnet and create a standalone transaction. After the completion of the transaction, we get the transaction digest. With this we can search for the transaction on the block explorer +This guide requires the `aea-ledger-fetchai` plug-in installed in your Python environment: +```bash +pip install aea-ledger-fetchai +``` + First, import the python and application specific libraries and set the static variables. ``` python import logging -from aea.crypto.fetchai import FetchAICrypto +from aea_ledger_fetchai import FetchAICrypto + from aea.crypto.helpers import create_private_key, try_generate_testnet_wealth from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet @@ -86,7 +92,8 @@ Finally, we create a transaction that sends the funds to the `wallet_2` ``` python import logging -from aea.crypto.fetchai import FetchAICrypto +from aea_ledger_fetchai import FetchAICrypto + from aea.crypto.helpers import create_private_key, try_generate_testnet_wealth from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet diff --git a/docs/tac-skills-contract.md b/docs/tac-skills-contract.md index 63e315ea67..868b81abc6 100644 --- a/docs/tac-skills-contract.md +++ b/docs/tac-skills-contract.md @@ -101,7 +101,7 @@ Follow the Preliminaries and =0.1.0"}, + "aea-ledger-ethereum": {"version": "<0.2.0,>=0.1.0"} +}' +aea config set agent.default_connection fetchai/p2p_libp2p:0.17.0 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum aea config set --type bool vendor.fetchai.skills.tac_control.is_abstract true aea config set --type dict agent.default_routing \ '{ - "fetchai/contract_api:0.11.0": "fetchai/ledger:0.13.0", - "fetchai/ledger_api:0.10.0": "fetchai/ledger:0.13.0", - "fetchai/oef_search:0.13.0": "fetchai/soef:0.17.0" + "fetchai/contract_api:0.12.0": "fetchai/ledger:0.14.0", + "fetchai/ledger_api:0.11.0": "fetchai/ledger:0.14.0", + "fetchai/oef_search:0.14.0": "fetchai/soef:0.18.0" }' aea config set --type list vendor.fetchai.connections.p2p_libp2p.cert_requests \ '[{"identifier": "acn", "ledger_id": "ethereum", "not_after": "2022-01-01", "not_before": "2021-01-01", "public_key": "fetchai", "save_path": ".certs/conn_cert.txt"}]' @@ -147,14 +152,14 @@ aea add-key ethereum ethereum_private_key.txt In a separate terminal, in the root directory, fetch at least two participants: ``` bash -aea fetch fetchai/tac_participant:0.22.0 --alias tac_participant_one +aea fetch fetchai/tac_participant:0.23.0 --alias tac_participant_one cd tac_participant_one aea generate-key ethereum aea add-key ethereum ethereum_private_key.txt aea install aea build cd .. -aea fetch fetchai/tac_participant:0.22.0 --alias tac_participant_two +aea fetch fetchai/tac_participant:0.23.0 --alias tac_participant_two cd tac_participant_two aea generate-key ethereum aea add-key ethereum ethereum_private_key.txt @@ -174,21 +179,26 @@ aea create tac_participant_two Build participant one: ``` bash cd tac_participant_one -aea add connection fetchai/p2p_libp2p:0.16.0 -aea add connection fetchai/soef:0.17.0 -aea add connection fetchai/ledger:0.13.0 -aea add skill fetchai/tac_participation:0.17.0 -aea add skill fetchai/tac_negotiation:0.20.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.16.0 +aea add connection fetchai/p2p_libp2p:0.17.0 +aea add connection fetchai/soef:0.18.0 +aea add connection fetchai/ledger:0.14.0 +aea add skill fetchai/tac_participation:0.18.0 +aea add skill fetchai/tac_negotiation:0.21.0 +aea config set --type dict agent.dependencies \ +'{ + "aea-ledger-fetchai": {"version": "<0.2.0,>=0.1.0"}, + "aea-ledger-ethereum": {"version": "<0.2.0,>=0.1.0"} +}' +aea config set agent.default_connection fetchai/p2p_libp2p:0.17.0 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool aea config set --type dict agent.default_routing \ '{ - "fetchai/contract_api:0.11.0": "fetchai/ledger:0.13.0", - "fetchai/ledger_api:0.10.0": "fetchai/ledger:0.13.0", - "fetchai/oef_search:0.13.0": "fetchai/soef:0.17.0" + "fetchai/contract_api:0.12.0": "fetchai/ledger:0.14.0", + "fetchai/ledger_api:0.11.0": "fetchai/ledger:0.14.0", + "fetchai/oef_search:0.14.0": "fetchai/soef:0.18.0" }' aea config set --type dict agent.decision_maker_handler \ '{ @@ -206,21 +216,26 @@ aea build Then, build participant two: ``` bash cd tac_participant_two -aea add connection fetchai/p2p_libp2p:0.16.0 -aea add connection fetchai/soef:0.17.0 -aea add connection fetchai/ledger:0.13.0 -aea add skill fetchai/tac_participation:0.17.0 -aea add skill fetchai/tac_negotiation:0.20.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.16.0 +aea add connection fetchai/p2p_libp2p:0.17.0 +aea add connection fetchai/soef:0.18.0 +aea add connection fetchai/ledger:0.14.0 +aea add skill fetchai/tac_participation:0.18.0 +aea add skill fetchai/tac_negotiation:0.21.0 +aea config set --type dict agent.dependencies \ +'{ + "aea-ledger-fetchai": {"version": "<0.2.0,>=0.1.0"}, + "aea-ledger-ethereum": {"version": "<0.2.0,>=0.1.0"} +}' +aea config set agent.default_connection fetchai/p2p_libp2p:0.17.0 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.connections.soef.config.chain_identifier ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool aea config set --type dict agent.default_routing \ '{ - "fetchai/contract_api:0.11.0": "fetchai/ledger:0.13.0", - "fetchai/ledger_api:0.10.0": "fetchai/ledger:0.13.0", - "fetchai/oef_search:0.13.0": "fetchai/soef:0.17.0" + "fetchai/contract_api:0.12.0": "fetchai/ledger:0.14.0", + "fetchai/ledger_api:0.11.0": "fetchai/ledger:0.14.0", + "fetchai/oef_search:0.14.0": "fetchai/soef:0.18.0" }' aea config set --type dict agent.decision_maker_handler \ '{ @@ -274,7 +289,7 @@ Briefly run the controller AEA: aea run ``` -Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.16.0 -u public_uri` to retrieve the address.) +Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.17.0 -u public_uri` to retrieve the address.) Then, in the participant one, run this command (replace `SOME_ADDRESS` with the correct value as described above): diff --git a/docs/tac-skills.md b/docs/tac-skills.md index 931c3be64b..e7f7be3b70 100644 --- a/docs/tac-skills.md +++ b/docs/tac-skills.md @@ -100,7 +100,7 @@ Follow the Preliminaries and =0.1.0"} +}' +aea config set agent.default_connection fetchai/p2p_libp2p:0.17.0 aea config set agent.default_ledger fetchai aea config set --type dict agent.default_routing \ '{ - "fetchai/oef_search:0.13.0": "fetchai/soef:0.17.0" + "fetchai/oef_search:0.14.0": "fetchai/soef:0.18.0" }' aea install aea build @@ -134,12 +138,12 @@ aea build In a separate terminal, in the root directory, fetch at least two participants: ``` bash -aea fetch fetchai/tac_participant:0.22.0 --alias tac_participant_one +aea fetch fetchai/tac_participant:0.23.0 --alias tac_participant_one cd tac_participant_one aea install aea build cd .. -aea fetch fetchai/tac_participant:0.22.0 --alias tac_participant_two +aea fetch fetchai/tac_participant:0.23.0 --alias tac_participant_two cd tac_participant_two aea build ``` @@ -156,17 +160,21 @@ aea create tac_participant_two Build participant one: ``` bash cd tac_participant_one -aea add connection fetchai/p2p_libp2p:0.16.0 -aea add connection fetchai/soef:0.17.0 -aea add connection fetchai/ledger:0.13.0 -aea add skill fetchai/tac_participation:0.17.0 -aea add skill fetchai/tac_negotiation:0.20.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.16.0 +aea add connection fetchai/p2p_libp2p:0.17.0 +aea add connection fetchai/soef:0.18.0 +aea add connection fetchai/ledger:0.14.0 +aea add skill fetchai/tac_participation:0.18.0 +aea add skill fetchai/tac_negotiation:0.21.0 +aea config set --type dict agent.dependencies \ +'{ + "aea-ledger-fetchai": {"version": "<0.2.0,>=0.1.0"} +}' +aea config set agent.default_connection fetchai/p2p_libp2p:0.17.0 aea config set agent.default_ledger fetchai aea config set --type dict agent.default_routing \ '{ - "fetchai/ledger_api:0.10.0": "fetchai/ledger:0.13.0", - "fetchai/oef_search:0.13.0": "fetchai/soef:0.17.0" + "fetchai/ledger_api:0.11.0": "fetchai/ledger:0.14.0", + "fetchai/oef_search:0.14.0": "fetchai/soef:0.18.0" }' aea config set --type dict agent.decision_maker_handler \ '{ @@ -180,17 +188,21 @@ aea build Then, build participant two: ``` bash cd tac_participant_two -aea add connection fetchai/p2p_libp2p:0.16.0 -aea add connection fetchai/soef:0.17.0 -aea add connection fetchai/ledger:0.13.0 -aea add skill fetchai/tac_participation:0.17.0 -aea add skill fetchai/tac_negotiation:0.20.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.16.0 +aea add connection fetchai/p2p_libp2p:0.17.0 +aea add connection fetchai/soef:0.18.0 +aea add connection fetchai/ledger:0.14.0 +aea add skill fetchai/tac_participation:0.18.0 +aea add skill fetchai/tac_negotiation:0.21.0 +aea config set --type dict agent.dependencies \ +'{ + "aea-ledger-fetchai": {"version": "<0.2.0,>=0.1.0"} +}' +aea config set agent.default_connection fetchai/p2p_libp2p:0.17.0 aea config set agent.default_ledger fetchai aea config set --type dict agent.default_routing \ '{ - "fetchai/ledger_api:0.10.0": "fetchai/ledger:0.13.0", - "fetchai/oef_search:0.13.0": "fetchai/soef:0.17.0" + "fetchai/ledger_api:0.11.0": "fetchai/ledger:0.14.0", + "fetchai/oef_search:0.14.0": "fetchai/soef:0.18.0" }' aea config set --type dict agent.decision_maker_handler \ '{ @@ -245,13 +257,13 @@ Briefly run the controller AEA: aea run ``` -Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.16.0 -u public_uri` to retrieve the address.) +Once you see a message of the form `To join its network use multiaddr 'SOME_ADDRESS'` take note of the address. (Alternatively, use `aea get-multiaddress fetchai -c -i fetchai/p2p_libp2p:0.17.0 -u public_uri` to retrieve the address.)