{"id":42963688,"url":"https://github.com/zabertech/python-izaber-wamp","last_synced_at":"2026-01-30T23:34:32.282Z","repository":{"id":62571988,"uuid":"116989329","full_name":"zabertech/python-izaber-wamp","owner":"zabertech","description":"Web Application Messaging Protocol (WAMP) support for iZaber framework","archived":false,"fork":false,"pushed_at":"2024-10-29T23:36:56.000Z","size":74,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-10-30T00:46:55.644Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zabertech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-01-10T17:31:31.000Z","updated_at":"2024-10-29T23:37:01.000Z","dependencies_parsed_at":"2024-10-29T16:59:59.803Z","dependency_job_id":null,"html_url":"https://github.com/zabertech/python-izaber-wamp","commit_stats":{"total_commits":23,"total_committers":3,"mean_commits":7.666666666666667,"dds":"0.17391304347826086","last_synced_commit":"8da009bf3aa5458c8d8ae79841fd79574c1435c0"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/zabertech/python-izaber-wamp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zabertech%2Fpython-izaber-wamp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zabertech%2Fpython-izaber-wamp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zabertech%2Fpython-izaber-wamp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zabertech%2Fpython-izaber-wamp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zabertech","download_url":"https://codeload.github.com/zabertech/python-izaber-wamp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zabertech%2Fpython-izaber-wamp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28923266,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T22:32:35.345Z","status":"ssl_error","status_checked_at":"2026-01-30T22:32:31.927Z","response_time":66,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-30T23:34:31.704Z","updated_at":"2026-01-30T23:34:32.275Z","avatar_url":"https://github.com/zabertech.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# izaber.wamp\n\n[[_TOC_]]\n\n## Overview\nBase module that brings together most of the requirements to allow WAMP\nconnectivity within Zaber\n\n## Documentation\n\n### Configuration\n\nThis library expects the `izaber.yaml` to be setup with something like the following:\n\n```yaml\ndefault:\n  wamp:\n    connection:\n      url: 'wss://nexus.izaber.com/ws'\n      username: 'USERNAME'\n      password: 'PASSWORD'\n```\n\nOnce that's set up, upon calling `izaber.initialize`, the library will eastablish a new WAMP connection.\n\n### Basic Calling Example\n\nIn this example, we call a registered function at URI `com.izaber.wamp.auth.whoami`. This is pretty straight-forward:\n\n1. Import `izaber` and `izaber_wamp`\n2. Initialize the library with `initialize`\n3. Call the function \n\nSince the `izaber_wamp` library is designed with Zaber mind, the `com.iaber.wamp` can be omitted from the call request like follows:\n\n```python\nfrom izaber import initialize\nfrom izaber_wamp import wamp\n\ninitialize()\n\n# Note that wamp.whomi() does the same thing\nme = wamp.call('auth.whoami')\nprint(me)\n\n```\n\nPositional and keyword arguments can be provided after the URI much like a normal python function call.\n\nIn the following example, 2 position parameters are provided as well as two keyword arguments:\n\n```python\nfrom izaber import initialize\nfrom izaber_wamp import wamp\n\ninitialize()\n\n# Prefix is added to become: com.izaber.wamp.example.call\nresults = wamp.call('example.call', 'pos1', 'pos2', keyword1='value1', keyword2='value2')\nprint(results)\n```\n\nIf for some reason the call fails or the session is not permitted to call that URI, the code will return an error and this library will throw an exception.\n\n### Basic Registration Example\n\nTo make URIs that other scripts can call, a script must register a URI. This requires a `backend` role (contact IT to help create an account). Most individual users have a `frontend` role which has pretty restrictive permissions for security reasons.\n\nFor this example, the `izaber.yaml` file must be configured with an account that has the `backend` role. Providing that, this code will register the URI `com.izaber.wamp.example.hello`.\n\nThe way that's done is to setup the script to receive calls to a particular URI. When called, the nexus server will provide additional information on the call being made including things like exactly *what* the URI (Useful in the case that the registered URI was a pattern match) and which user/role made the call.\n\n```python\nimport time\n\nfrom izaber import initialize\nfrom izaber_wamp import wamp\n\ninitialize()\n\ndef example_hello_fn(event):\n    \"\"\" Demonstrates creating a registered callable that does not require\n        arguments. All registered functions receive a positional parameter `event`\n        that holds a swampyer.messages.INVOCATION instance. This object will contain\n        metadata details on the call made itself\n    \"\"\"\n\n    # Details will almost always contain a hash such as:\n    # {\"caller\": 8185620139956162, \"caller_authid\": \"zaber\", \"caller_authrole\": \"frontend\"}\n    # In very rare cases (such as trusted component calls), the caller details will\n    # not be available as there are no details to be provided.\n    details = event.details\n    authid = details.get('caller_authid', '\u003cunknown\u003e')\n    authrole = details.get('\"aller_authrole', '\u003cunknown\u003e')\n    return f\"Hello {authid} (role: {authrole})!\"\n\nregistration_id = wamp.register('example.hello', example_hello_fn)\nprint(registration_id)\n\nwhile True:\n    time.sleep(1)\n```\n\nIn this example, the service will attempt to remain connected while the code is running. The while loop at the bottom will keep this script running indefinitely. Swampyer in the background, if disconnected, will attempt to reconnect all registrations previously made. To exercise more control on the reconnection process, have a look at the \"Hooking `join` for Services\" section below.\n\nIt is also possible to provide additional connection options when registering via a 3rd argument to `register` or via key `details`. The primary usages for this option are:\n\n1. Is to enable [`force_reregister` option](https://crossbar.io/docs/Registration-Options/#force-reregister) to allow a script to punt other registrations off of a URI. The reason is that sometimes, if a script is aborted (eg. Ctrl-C) the session hangs on for a bit on the server side. Unless we force it, any registered URIs will be held on by the zombied session until timeouts reap it.\n2. Change the [`match` option](https://crossbar.io/docs/Registration-Options/#match) so that instead of exact matches, we can do things like prefix or wildcard matches\n\nSee [Registration Options](https://crossbar.io/docs/Registration-Options/) in the crossbar documentation.\n\nIn the following example we set both the `force_reregister` and `match` options. The `force_reregister` set to `True` will disconnect any other scripts with the same URI. The `match` option set to `prefix` will then match anything below `com.izaber.wamp.hello` (such as `com.izaber.wamp.hello.suboption`)\n\n```python\nimport time\n\nfrom izaber import initialize\nfrom izaber_wamp import wamp\n\ninitialize()\n\ndef example_hello_fn(event):\n    \"\"\" Demonstrates creating a registered callable that does not require\n        arguments. All registered functions receive a positional parameter `event`\n        that holds a swampyer.messages.INVOCATION instance. This object will contain\n        metadata details on the call made itself\n    \"\"\"\n\n    # Details will almost always contain a hash such as:\n    # {\"caller\": 8185620139956162, \"caller_authid\": \"zaber\", \"caller_authrole\": \"frontend\", \"procedure\": \"com.izaber.wamp.hello.test\"}\n    # In very rare cases (such as trusted component calls), the caller details will\n    # not be available as there are no details to be provided.\n    details = event.details\n    authid = details.get('caller_authid', '\u003cunknown\u003e')\n    authrole = details.get('caller_authrole', '\u003cunknown\u003e')\n    uri = details.get('procedure','\u003cunknown'\u003e)\n    return f\"Hello {authid} (role: {authrole})! Called from: \u003c{uri}\u003e\"\n\n# Register on 'com.izaber.wamp.example.hello'. However, due to the\n# 'match': 'prefix', this registration will match all all of the following and more:\n# com.izaber.wamp.example.hello.toot\n# com.izaber.wamp.example.hello.yep\n# com.izaber.wamp.example.hello.this.will.also.work\n# The response to the URI, then, can be handled by a single function\n# by parsing out the full URI called\nregistration_id = wamp.register(\n                        'example.hello',\n                        example_hello_fn,\n                        {\n                          'force_reregister': True,\n                          'match': 'prefix',\n                        })\nprint(registration_id)\n\nwhile True:\n    time.sleep(1)\n```\n\nIf for some reason it's required to remove the registered function for availability, with the `registration_id`, `unregister` may be called:\n\n```python\nwamp.unregister(registration_id)\n```\n\n### Publish Example\n\nScripts can create data and are able to publish the message to a predefined queue. These queues are named similar to the calling URIs and the data published is arbitrary. Publications are fire and forget and if there is no one listening for the information, it will simply be disposed by the nexus server.\n\nThis example publishes a hello world message to the `test.sub` URI.\n\n```python\nfrom izaber import initialize\nfrom izaber_wamp import wamp\n\ninitialize()\n\nhello_message = {\n    \"Hello\": \"World!\"\n}\nwamp.publish('test.sub', hello_message)\n```\n\n### Basic Subscription\n\nIn this example, this creates a simple subscription to receive push notifications from the server.\n\n```python\nfrom izaber import initialize\nfrom izaber_wamp import wamp\n\ninitialize()\n\ndef subscribe_event(event, *args, **kwargs):\n    print(\"subscription event:\", event)\n    print(\"subscription event received args:\", args)\n    print(\"subscription event received keyword args:\", kwargs)\n\nsub_id = wamp.subscribe('test.sub', subscribe_event)\n\ntime.sleep(10000)\n```\n\n### Hooking `join` for Services\n\nIf the script is to be a service, that is, a long-running script that registers or acts upon subscription events, then hooking the `join` event will be important. The connection status of the server being independant of the script means the script may end up disconnected from the server at any point.\n\nBy hooking the `join` event, when the script manages to reconnect, any actions attached to the `join` will be called meaning it's a good time to reestablish all registrations and subscriptions in a predictably.\n\nIn this example, the code will create a registration for `com.izaber.wamp.test.call` and a subscription for `com.izaber.wamp.test.sub`.\n\n```python\n#!/usr/bin/env python3\n\nfrom izaber import initialize\nfrom izaber_wamp import wamp\n\ndef test_call(event, *args, **kwargs):\n    print(f\"Test call called! {args} {kwargs}\")\n\ndef test_sub(event, *args, **kwargs):\n    print(f\"Test event called! {args} {kwargs}\")\n\ndef join_hook(details):\n    res = wamp.register('test.call', test_call)\n    print(f\"Registered test.call with {res}\")\n    res = wamp.subscribe('test.sub', test_sub)\n    print(f\"Subscribed test.call with {res}\")\n\n# Must be called before the initialize!\nwamp.hook('join', join_hook)\n\ninitialize()\n\ninput(\"Hit enter to stop service\\n\")\n```\n\n### Roster Example\n\nRosters allow for clients to register information to a shared name where the information has the same lifetime as the client's connection. Instead of creating a service that collects what is out there for each possible service, Nexus has a roster system built right in.\n\nThese are used at Zaber in two locations:\n\n1. Zerp database that are available: Rosters are used as a way to flag under the name `roster.zerp.databases` all the databases available for connection. Previsouly we were using a pub/sub system where available databases had 1s to \"report in\" that they were available.\n2. Dashboards that are active and available: As the dashboard system is mostly decentralized, they are tied together by menus. In the past it would require the first dashboard that connected to the WAMP bus to establish a service that handled the registration of all the dashboards available around Zaber.\n\nSo rosters are useful when:\n\n- Information must be shared between a group of scripts\n- The information cannot be predetermined (eg. like lists of services that are present)\n- The information is only relevant while the client is connected\n\nThis makes rosters especially useful when building a system that can flexibly add features and services based upon a \"hey who's available to do stuff\" paradigm.\n\n#### Registering A Roster Entry\n\nRegistering rosters will require a user with backend level permissions. Frontend users can do queries on public records.\n\nThis is an example of using rosters to query all services grouped by a keyword\n\n```python\nimport time\nfrom izaber import initialize, config\nfrom izaber_wamp import wamp\n\ninitialize()\n\nROSTER_KEY = f\"roster.example.{config.wamp.connection.username}.test\"\n\n# This data is arbitrary primitive data: this can be a list, a dict, a string, a number.\n# All nexus does is store and retreive it for applications to do something with so it\n# has no expectations for the data aside from it being possible to serialize to JSON\nEG_DATA = {\n            'random': 'data'\n          }\n\nresult = wamp.roster_register(ROSTER_KEY, EG_DATA)\nprint(f\"{result=}\")\n\n# Sleep for the sake of example. Usually your code may be doing something like waiting for requests\ntime.sleep(1000)\n\n# We can then delete our roster entry explicity like the following\n# or have the system delete it for us when this code eventually disconnects\nwamp.roster_unregister(ROSTER_KEY)\n```\n\n#### Fetching Roster Entries\n\nFrontend users can do queries on public records.\n\nThis is an example of requesting a roster of available services grouped by a keyword\n\n```python\nfrom izaber import initialize, config\nfrom izaber_wamp import wamp\n\ninitialize()\n\nROSTER_KEY = f\"roster.example.{config.wamp.connection.username}.test\"\n\n# Now we can retreive the data\nresult2 = wamp.roster_query(ROSTER_KEY)\nprint(f\"{result2=}\")\n```\n\n### Current User Identity\n\nAs a script can be used by multiple users, it will probably be quite helpful to know who was using the script. Along with the fact that it's possible to change the connected user, being able to determine the authenticated identity to Nexus may be tricky. To handle this there is the `whoami()`.\n\n```python\nfrom izaber import initialize, config\nfrom izaber_wamp import wamp\n\ninitialize()\n\nprint(wamp.whoami())\n```\n\nThis function will return a dictionary like:\n\n```python\n{'authid': 'zaber', 'role': 'frontend'}\n```\n\n- `authid` is the crossbar name for the user login\n- `role` is the type of user. For now, can be: `frontend`, `backend`, and `trust`. May be extended later with LDAP groupings\n\n### User Metadata (Preferences)\n\nIt is possible to store user preferences on nexus so that the information can be shared between across logins. This can be used for things like storing preferences for darkmode or public keys.\n\n```python\nfrom izaber import initialize, config\nfrom izaber_wamp import wamp\n\ninitialize()\n\nPREFERENCE_KEY = f\"some.preference.key\"\n\n# This data is arbitrary primitive data: this can be a list, a dict, a string, a number.\n# All nexus does is store and retreive it for applications to do something with so it\n# has no expectations for the data aside from it being possible to serialize to JSON\nEG_DATA = {\n            'random': 'data'\n          }\n\nresult = wamp.metadata_set(PREFERENCE_KEY, EG_DATA)\nprint(f\"{result=}\")\n\n# Now we can retreive the data\nresult2 = wamp.metadata_get(PREFERENCE_KEY)\nprint(f\"{result2=}\")\n\n# Get all the metadata keys associated with the user\nresult3 = wamp.metadata_list()\nprint(f\"{result3=}\")\n\n# We can then delete our metadata entry explicity like the following\n# or have the system delete it for us when this code eventually disconnects\nwamp.metadata_delete(PREFERENCE_KEY)\n```\n\n[Logging in](https://nexus.izaber.com/) will allow you to review, edit, delete what metadata/preferences have already been created in your account as well as creating new entries.\n\n### Switching to another user\n\nWhile the intention is to allow users to predefine their username and password in `izaber.yaml`, some development patterns request users to login themselves.\n\nRegardless of the pattern used, ensure that passwords never get stored within code.\n\n```python\nfrom izaber import initialize\nfrom izaber_wamp import wamp\n\ninitialize()\n\ndef print_current_user():\n    current_user = wamp.whoami()\n    print(current_user[\"authid\"])\n\n# At this point, the `wamp` user will have the default configured user within the\n# izaber.yaml, assuming we have credentials to another user we wish to switch to\n# we can do:\nanother_username = 'username'\nanother_password = 'password'\n\ntry:\n    print_current_user()\n    wamp.change_user(another_username, another_password)\n    print_current_user()\nexcept Exception as ex:\n    print(f\"Oops, that didn't work {ex=}\")\n```\n\n### Prevent Connection Initialization (Delay Login)\n\nIn some cases you may wish to take advantage of the framework without having the system initialize a user by default. Perhaps it's a script that runs, requests the username and password from a user then started performing actions.\n\nIn that case, disable the initialization via the `AUTORUN` variable then use the `wamp.change_user(...)` \n\n```python\nfrom izaber import initialize\nfrom izaber_wamp import wamp\nimport izaber_wamp\n\nizaber_wamp.AUTORUN = False\n\ninitialize()\n\ndef print_current_user():\n    current_user = wamp.whoami()\n    print(current_user[\"authid\"])\n\nanother_username = 'zaber'\nanother_password = 'password'\n\ntry:\n    wamp.change_user(another_username, another_password)\n    print_current_user()\nexcept Exception as ex:\n    print(f\"Oops, that didn't work {ex=}\")\n```\n\n### One Time Password (OTP)\n\nOne Time Password (OTP) creation can only be performed when the user is connected via username and password, not by username and OTP (or API key). There is a special case where it may be possible generate new OTP passwords while connected via API key but that requires special configuration on Nexus. The reason is that we do not wish malicious scripts grabbing API keys which cascade into additional API key/OTP generations.\n\nOTP can be used in the place of a password for a single-use token for processes or another application to be used within 10 minutes. This really is only useful for specialized applications: Eg for internal applications to transition to another transport (eg: from a python script to web application)\n\n```python\nfrom izaber import initialize\nfrom izaber_wamp import wamp\n\ninitialize()\n\notp = wamp.otp_create()\nprint(otp)\n```\n\nThis code, if successful will generate an OTP like:\n\n```python\notp = {\n    'uuid': 'EJu2jQqNTW6ey7kBxQVy_Q',\n    'origin': 'zaber@10.131.0.122',\n    'expires': '2024-10-29T12:32:14.803923-07:00',\n    'permissions': [],\n    'key': 'zISlv82_mM2zf8Pw3g_NL0lvwvEA1hDaPfAtLDiGufU',\n    'owner': 'OgngrWDrRLq56WSEqWj0zQ',\n    'plaintext_key': 'voiiG2HVlAajSMUtG3rq2aDIZihfzZFU',\n    'login': 'zaber'\n}\n```\n\nThe critical keys in this datastructure is `login` and `plaintext_key` which can be passed into a subsequent connection request.\n\n### API Key Generation\n\nAPI key creation can only be performed when the user is connected via username and password, not by username and API key (or OTP). There is a special case where it may be possible generate new OTP passwords while connected via API key but that requires special configuration on Nexus. The reason is that we do not wish malicious scripts grabbing API keys which cascade into additional API key/OTP generations.\n\nAPI keys can be used in the place of a password on individual computers and scripts. This is quite useful from a security perspective since the compromise of a single API key doesn't mean compromise every other system that a user's password may function on.\n\n```python\nfrom izaber import initialize\nfrom izaber_wamp import wamp\n\ninitialize()\n\napikey = wamp.apikeys_create({\n                \"description\": \"Purpose of key\",\n            })\nprint(apikey)\n```\n\nThis should return a key like:\n\n```python\n{'description': 'Purpose of key',\n 'expires': None,\n 'key': 'NjzXJN0tEqul9iPxhPdwqL8TdA1nsIxM3_-viuxWVWI',\n 'owner': 'OgngrWDrRLq56WSEqWj0zQ',\n 'permissions': [],\n 'plaintext_key': '1612S4FDxvEY7wep1P62uVtMPru7axPX',\n 'uuid': 'pu3tazccR9i8WgxJNJMgeQ'}\n```\n\n### List Existing API Keys\nAPI key listing can only be performed when the user is connected via username and password, not by username and API key (or OTP). There is a special case where it may be possible generate new OTP passwords while connected via API key but that requires special configuration on Nexus. The reason is that we do not wish malicious scripts grabbing API keys which cascade into additional API key/OTP generations.\n\nPlease note that while currently it's possible to see the plaintext (secret) part of the keys, that will be deprecated in a future update. We currently store both the plaintext and hashed version of the keys. A future update will drop the plaintext key and while it will not impact login, it will mean that the plaintext version will only be available at creation in the future.\n\n```python\nfrom izaber import initialize\nfrom izaber_wamp import wamp\nimport pprint\n\ninitialize()\n\nfor apikey in wamp.apikeys_list():\n    pprint.pprint(apikey)\n```\n\nAPI keys may be limited to certain calls and even expiry. Example follows where they key is limited to a set of basic URIs.\n\n```python\n{'description': '',\n 'expires': None,\n 'key': 'Akj1238x234kj-2jkah1b234-kjahsdjfb12b31c-45',\n 'owner': 'OgngrWDrRLq56WSEqWj0zQ',\n 'permissions': [{'perms': 'c',\n                  'uri': 'com.izaber.wamp.zerp:testing:testing.attr.type:object.execute.fields_get',\n                  'uuid': 'VXrQS6-NR0-ZrorbpH_ktw'},\n                 {'perms': 'c',\n                  'uri': 'com.izaber.wamp.zerp:testing:testing.attr.type:object.execute.search',\n                  'uuid': 'xPwgd6VpSuaPdhMDyuEjHg'},\n                 {'perms': 'c',\n                  'uri': 'com.izaber.wamp.zerp:testing:testing.attr.type:object.execute.zerp_search_read',\n                  'uuid': '2toI4c46S22hDdKSUDyNjw'},\n                 {'perms': 'c',\n                  'uri': 'com.izaber.wamp.zerp:testing:testing.attr.type:object.execute.read',\n                  'uuid': 'cMAv_ooQQOSalV8yr5EFRQ'},\n                 {'perms': 'c',\n                  'uri': 'com.izaber.wamp.zerp:testing:testing.test.series.type:object.execute.fields_get',\n                  'uuid': 'nXlwSHgvTQqSKT0ig1yzqQ'},\n                 {'perms': 'c',\n                  'uri': 'com.izaber.wamp.zerp:testing:testing.test.series.type:object.execute.search',\n                  'uuid': '7sqYP6CrQwavlOCjggJhkw'},\n                 {'perms': 'c',\n                  'uri': 'com.izaber.wamp.zerp:testing:testing.test.series.type:object.execute.zerp_search_read',\n                  'uuid': 'OpA0m5QUQuav2sBzsBmszQ'},\n                 {'perms': 'c',\n                  'uri': 'com.izaber.wamp.zerp:testing:testing.test.series.type:object.execute.read',\n                  'uuid': 'h48MSpChRYOqrlqJvjvAEg'}],\n 'plaintext_key': '1233-2343-cvc1-dfgsdfg4234d',\n 'uuid': 'gK9PAOGdRsq-72xcoT-95A'}\n```\n\n## Installation\n\nThis library is uploaded to PyPi. Installation for usage can be done with:\n\n`pip install izaber-wamp`\n\n## Development\n\nFor hacking on the code, this requires the following:\n\n- `git`\n- `\u003e=python3.8`\n- [pdm](https://pdm-project.org/en/latest/)\n\n### Setup\n\n```bash\ngit clone git@github.com:zabertech/python-izaber-wamp.git\ncd python-izaber-wamp\npdm install\n```\n\nAnd now it's possible to make changes to the code\n\n### Tests via Docker\n\nIt's not always desireable to pollute the environment with multiple versions of python so using docker compose is the recommend method for testing.\n\n```bash\ndocker compose up\ndocker compose logs -f src\n```\n\nIf you would like to work within the container, have a look at the `docker-compose.yml` and update the `CMD` to `sleep infinity` and it will provide a shell environment (via something like `docker compose exec src bash`) for testing the code within a container.\n\n### Packaging\n\n- Ensure that the `pyproject.toml` has the newest version.\n- Update the `VERSIONS.md` with the changes made into the library\n- Then, assuming access to the pypi account.\n    ```bash\n    pdm build\n    pdm publish\n    ```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzabertech%2Fpython-izaber-wamp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzabertech%2Fpython-izaber-wamp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzabertech%2Fpython-izaber-wamp/lists"}