diff --git a/.github/workflows/dev2master.yml b/.github/workflows/dev2master.yml deleted file mode 100644 index e59e57eb..00000000 --- a/.github/workflows/dev2master.yml +++ /dev/null @@ -1,19 +0,0 @@ -# This workflow will generate a distribution and upload it to PyPI - -name: Push dev -> master -on: - workflow_dispatch: - -jobs: - build_and_publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - ref: dev - - name: Push dev -> master - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: master \ No newline at end of file diff --git a/.github/workflows/propose_release.yml b/.github/workflows/propose_release.yml new file mode 100644 index 00000000..47a12301 --- /dev/null +++ b/.github/workflows/propose_release.yml @@ -0,0 +1,32 @@ +name: Propose Stable Release +on: + workflow_dispatch: + inputs: + release_type: + type: choice + description: Release Type + options: + - build + - minor + - major +jobs: + update_version: + uses: neongeckocom/.github/.github/workflows/propose_semver_release.yml@master + with: + release_type: ${{ inputs.release_type }} + version_file: ovos_utils/version.py + alpha_var: VERSION_ALPHA + build_var: VERSION_BUILD + minor_var: VERSION_MINOR + major_var: VERSION_MAJOR + update_changelog: True + branch: dev + + pull_changes: + needs: update_version + uses: neongeckocom/.github/.github/workflows/pull_master.yml@master + with: + pr_assignee: ${{ github.actor }} + pr_draft: false + pr_title: ${{ needs.update_version.outputs.version }} + pr_body: ${{ needs.update_version.outputs.changelog }} diff --git a/.github/workflows/publish_alpha.yml b/.github/workflows/publish_alpha.yml index accf69d6..26060b40 100644 --- a/.github/workflows/publish_alpha.yml +++ b/.github/workflows/publish_alpha.yml @@ -19,55 +19,46 @@ on: workflow_dispatch: jobs: + update_version: + uses: neongeckocom/.github/.github/workflows/propose_semver_release.yml@master + with: + release_type: "alpha" + version_file: ovos_utils/version.py + alpha_var: VERSION_ALPHA + build_var: VERSION_BUILD + minor_var: VERSION_MINOR + major_var: VERSION_MAJOR + update_changelog: True + branch: dev build_and_publish: runs-on: ubuntu-latest + needs: update_version steps: - - uses: actions/checkout@v2 - with: - ref: dev - fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - - name: Setup Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install Build Tools - run: | - python -m pip install build wheel - - name: Increment Version - run: | - VER=$(python setup.py --version) - python scripts/bump_alpha.py - - name: "Generate release changelog" - uses: heinrichreimer/github-changelog-generator-action@v2.3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - id: changelog - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Increment Version - branch: dev - - name: version - run: echo "::set-output name=version::$(python setup.py --version)" - id: version - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token with: - tag_name: V${{ steps.version.outputs.version }} - release_name: Release ${{ steps.version.outputs.version }} + tag_name: V${{ needs.update_version.outputs.version }} + release_name: Release ${{ needs.update_version.outputs.version }} body: | Changes in this Release - ${{ steps.changelog.outputs.changelog }} + ${{ needs.update_version.outputs.changelog }} draft: false prerelease: true commitish: dev + - name: Checkout Repository + uses: actions/checkout@v2 + with: + ref: dev + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - name: Build Distribution Packages run: | python setup.py sdist bdist_wheel - - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@master + - name: Publish to PyPI + if: False + # TODO: Remove test patch above + uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{secrets.PYPI_TOKEN}} diff --git a/.github/workflows/publish_build.yml b/.github/workflows/publish_build.yml deleted file mode 100644 index 279eedbf..00000000 --- a/.github/workflows/publish_build.yml +++ /dev/null @@ -1,77 +0,0 @@ -# This workflow will generate a distribution and upload it to PyPI - -name: Publish Build Release ..X -on: - workflow_dispatch: - -jobs: - build_and_publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - ref: dev - fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - - name: Setup Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install Build Tools - run: | - python -m pip install build wheel - - name: Remove alpha (declare stable) - run: | - VER=$(python setup.py --version) - python scripts/remove_alpha.py - - name: "Generate release changelog" - uses: heinrichreimer/github-changelog-generator-action@v2.3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - id: changelog - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Declare alpha stable - branch: dev - - name: Push dev -> master - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: master - force: true - - name: version - run: echo "::set-output name=version::$(python setup.py --version)" - id: version - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token - with: - tag_name: V${{ steps.version.outputs.version }} - release_name: Release ${{ steps.version.outputs.version }} - body: | - Changes in this Release - ${{ steps.changelog.outputs.changelog }} - draft: false - prerelease: false - commitish: dev - - name: Build Distribution Packages - run: | - python setup.py sdist bdist_wheel - - name: Prepare next Build version - run: echo "::set-output name=version::$(python setup.py --version)" - id: alpha - - name: Increment Version ${{ steps.alpha.outputs.version }}Alpha0 - run: | - VER=$(python setup.py --version) - python scripts/bump_build.py - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Prepare Next Version - branch: dev - - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@master - with: - password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/publish_major.yml b/.github/workflows/publish_major.yml deleted file mode 100644 index 1148d331..00000000 --- a/.github/workflows/publish_major.yml +++ /dev/null @@ -1,77 +0,0 @@ -# This workflow will generate a distribution and upload it to PyPI - -name: Publish Major Release X.0.0 -on: - workflow_dispatch: - -jobs: - build_and_publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - ref: dev - fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - - name: Setup Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install Build Tools - run: | - python -m pip install build wheel - - name: Remove alpha (declare stable) - run: | - VER=$(python setup.py --version) - python scripts/remove_alpha.py - - name: "Generate release changelog" - uses: heinrichreimer/github-changelog-generator-action@v2.3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - id: changelog - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Declare alpha stable - branch: dev - - name: Push dev -> master - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: master - force: true - - name: version - run: echo "::set-output name=version::$(python setup.py --version)" - id: version - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token - with: - tag_name: V${{ steps.version.outputs.version }} - release_name: Release ${{ steps.version.outputs.version }} - body: | - Changes in this Release - ${{ steps.changelog.outputs.changelog }} - draft: false - prerelease: false - commitish: master - - name: Build Distribution Packages - run: | - python setup.py sdist bdist_wheel - - name: Prepare next Major version - run: echo "::set-output name=version::$(python setup.py --version)" - id: alpha - - name: Increment Version ${{ steps.alpha.outputs.version }}Alpha0 - run: | - VER=$(python setup.py --version) - python scripts/bump_major.py - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Prepare Next Version - branch: dev - - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@master - with: - password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/publish_minor.yml b/.github/workflows/publish_minor.yml deleted file mode 100644 index 4ab885b8..00000000 --- a/.github/workflows/publish_minor.yml +++ /dev/null @@ -1,77 +0,0 @@ -# This workflow will generate a distribution and upload it to PyPI - -name: Publish Minor Release .X.0 -on: - workflow_dispatch: - -jobs: - build_and_publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - ref: dev - fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - - name: Setup Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install Build Tools - run: | - python -m pip install build wheel - - name: Remove alpha (declare stable) - run: | - VER=$(python setup.py --version) - python scripts/remove_alpha.py - - name: "Generate release changelog" - uses: heinrichreimer/github-changelog-generator-action@v2.3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - id: changelog - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Declare alpha stable - branch: dev - - name: Push dev -> master - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - branch: master - force: true - - name: version - run: echo "::set-output name=version::$(python setup.py --version)" - id: version - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token - with: - tag_name: V${{ steps.version.outputs.version }} - release_name: Release ${{ steps.version.outputs.version }} - body: | - Changes in this Release - ${{ steps.changelog.outputs.changelog }} - draft: false - prerelease: false - commitish: master - - name: Build Distribution Packages - run: | - python setup.py sdist bdist_wheel - - name: Prepare next Minor version - run: echo "::set-output name=version::$(python setup.py --version)" - id: alpha - - name: Increment Version ${{ steps.alpha.outputs.version }}Alpha0 - run: | - VER=$(python setup.py --version) - python scripts/bump_minor.py - - name: Commit to dev - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Prepare Next Version - branch: dev - - name: Publish to Test PyPI - uses: pypa/gh-action-pypi-publish@master - with: - password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml new file mode 100644 index 00000000..3272b82c --- /dev/null +++ b/.github/workflows/publish_release.yml @@ -0,0 +1,45 @@ +name: Publish Release +on: + push: + branches: + - master + +jobs: + github_release: + if: false + # TODO: Remove test patch above + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: master + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + - name: version + run: echo "::set-output name=version::$(python setup.py --version)" + id: version + - name: "Generate release changelog" + uses: heinrichreimer/github-changelog-generator-action@v2.3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + id: changelog + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: V${{ steps.version.outputs.version }} + release_name: Release ${{ steps.version.outputs.version }} + body: | + Changes in this Release + ${{ steps.changelog.outputs.changelog }} + draft: false + prerelease: false + commitish: master + - name: Build Distribution Packages + run: | + python setup.py sdist bdist_wheel + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index fb2a95c6..51537bda 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -52,10 +52,10 @@ jobs: python -m pip install build wheel - name: Install core repo run: | - pip install . + pip install .[extras] - name: Install test dependencies run: | - pip install pytest pytest-timeout pytest-cov + pip install pytest pytest-timeout pytest-cov mock - name: Run unittests run: | pytest --cov=ovos_utils --cov-report xml test/unittests diff --git a/CHANGELOG.md b/CHANGELOG.md index 298c1ad0..96a9845d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,649 +1,149 @@ # Changelog -## [V0.0.30a4](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.30a4) (2023-03-09) +## [V0.0.31a18](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a18) (2023-04-18) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.30a3...V0.0.30a4) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a17...V0.0.31a18) -**Merged pull requests:** - -- Update dependencies to stable versions [\#107](https://github.com/OpenVoiceOS/ovos-utils/pull/107) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.30a3](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.30a3) (2023-03-08) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.30a2...V0.0.30a3) - -**Merged pull requests:** - -- Bump ovos-config dependency cleanup module init [\#104](https://github.com/OpenVoiceOS/ovos-utils/pull/104) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.30a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.30a2) (2023-03-08) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.30a1...V0.0.30a2) - -**Implemented enhancements:** - -- feat/console\_scripts [\#105](https://github.com/OpenVoiceOS/ovos-utils/pull/105) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.30a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.30a1) (2023-03-08) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.29...V0.0.30a1) - -**Merged pull requests:** - -- Implement module\_property decorator with unit test [\#103](https://github.com/OpenVoiceOS/ovos-utils/pull/103) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.29](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.29) (2023-03-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.29a2...V0.0.29) - -## [V0.0.29a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.29a2) (2023-03-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.29a1...V0.0.29a2) - -**Fixed bugs:** - -- fix/circular\_import [\#101](https://github.com/OpenVoiceOS/ovos-utils/pull/101) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.29a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.29a1) (2023-03-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.28...V0.0.29a1) - -**Implemented enhancements:** - -- Migrate/lock monotonic event [\#100](https://github.com/OpenVoiceOS/ovos-utils/pull/100) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.28](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.28) (2023-02-24) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.28a7...V0.0.28) - -## [V0.0.28a7](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.28a7) (2023-02-16) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.28a6...V0.0.28a7) - -**Merged pull requests:** - -- Refactor SSH helpers and add generic systemd helpers [\#95](https://github.com/OpenVoiceOS/ovos-utils/pull/95) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.28a6](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.28a6) (2023-02-15) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.28a5...V0.0.28a6) - -**Merged pull requests:** - -- Handle default network config values if core configuration is incomplete [\#99](https://github.com/OpenVoiceOS/ovos-utils/pull/99) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.28a5](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.28a5) (2023-02-15) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.28a4...V0.0.28a5) - -**Implemented enhancements:** - -- port network utils from mk2 [\#85](https://github.com/OpenVoiceOS/ovos-utils/issues/85) -- improve network checks [\#88](https://github.com/OpenVoiceOS/ovos-utils/pull/88) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.28a4](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.28a4) (2023-02-08) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.28a3...V0.0.28a4) - -**Implemented enhancements:** - -- minor utils fix [\#98](https://github.com/OpenVoiceOS/ovos-utils/pull/98) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.28a3](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.28a3) (2023-02-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.28a2...V0.0.28a3) - -**Implemented enhancements:** - -- feat/runtime\_requirements gui [\#97](https://github.com/OpenVoiceOS/ovos-utils/pull/97) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.28a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.28a2) (2023-02-04) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.28a1...V0.0.28a2) - -**Implemented enhancements:** - -- feat/network\_reqs\_from\_workshop [\#96](https://github.com/OpenVoiceOS/ovos-utils/pull/96) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.28a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.28a1) (2023-01-25) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.27...V0.0.28a1) - -**Fixed bugs:** - -- According to the usage, you should be able to pass the name to LOG\(\). [\#94](https://github.com/OpenVoiceOS/ovos-utils/pull/94) ([gmsoft-tuxicoman](https://github.com/gmsoft-tuxicoman)) - -## [V0.0.27](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.27) (2023-01-20) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.27a8...V0.0.27) - -## [V0.0.27a8](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.27a8) (2023-01-20) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.27a7...V0.0.27a8) - -**Merged pull requests:** - -- Log deprecation warning in `layers` module [\#93](https://github.com/OpenVoiceOS/ovos-utils/pull/93) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.27a7](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.27a7) (2023-01-12) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.27a6...V0.0.27a7) +## [V0.0.31a17](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a17) (2023-04-17) -**Merged pull requests:** - -- add transient duration config [\#92](https://github.com/OpenVoiceOS/ovos-utils/pull/92) ([emphasize](https://github.com/emphasize)) - -## [V0.0.27a6](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.27a6) (2023-01-05) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.27a5...V0.0.27a6) - -**Fixed bugs:** - -- fix/mouse\_detect\_again [\#90](https://github.com/OpenVoiceOS/ovos-utils/pull/90) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.27a5](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.27a5) (2022-12-16) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.27a4...V0.0.27a5) - -**Implemented enhancements:** - -- sync utils with core [\#89](https://github.com/OpenVoiceOS/ovos-utils/pull/89) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.27a4](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.27a4) (2022-11-30) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.27a3...V0.0.27a4) - -**Merged pull requests:** - -- Add background\_color to show image and show animated image [\#86](https://github.com/OpenVoiceOS/ovos-utils/pull/86) ([AIIX](https://github.com/AIIX)) - -## [V0.0.27a3](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.27a3) (2022-11-15) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.27a2...V0.0.27a3) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a16...V0.0.31a17) **Merged pull requests:** -- gui notification callback data [\#84](https://github.com/OpenVoiceOS/ovos-utils/pull/84) ([AIIX](https://github.com/AIIX)) +- Update release automation [\#126](https://github.com/OpenVoiceOS/ovos-utils/pull/126) ([NeonDaniel](https://github.com/NeonDaniel)) -## [V0.0.27a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.27a2) (2022-11-11) +## [V0.0.31a16](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a16) (2023-04-14) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.27a1...V0.0.27a2) - -**Fixed bugs:** - -- fix sudo flag again [\#83](https://github.com/OpenVoiceOS/ovos-utils/pull/83) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.27a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.27a1) (2022-11-11) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.26...V0.0.27a1) - -**Fixed bugs:** - -- fix sudo flag [\#82](https://github.com/OpenVoiceOS/ovos-utils/pull/82) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.26](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.26) (2022-10-29) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.26a2...V0.0.26) - -## [V0.0.26a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.26a2) (2022-10-22) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.26a1...V0.0.26a2) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a15...V0.0.31a16) **Implemented enhancements:** -- refactor some stuff to properties for better compatibility with ovos-… [\#80](https://github.com/OpenVoiceOS/ovos-utils/pull/80) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.26a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.26a1) (2022-10-19) +- Update `wait_for_exit_signal` to use Event.wait instead of looped sleep [\#128](https://github.com/OpenVoiceOS/ovos-utils/pull/128) ([NeonDaniel](https://github.com/NeonDaniel)) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25...V0.0.26a1) - -**Implemented enhancements:** +## [V0.0.31a15](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a15) (2023-04-14) -- feat/event\_wrappers\_in\_outils [\#79](https://github.com/OpenVoiceOS/ovos-utils/pull/79) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25) (2022-10-18) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a15...V0.0.25) - -**Merged pull requests:** - -- license + vulnerability tests [\#78](https://github.com/OpenVoiceOS/ovos-utils/pull/78) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a15](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a15) (2022-10-18) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a14...V0.0.25a15) - -**Fixed bugs:** - -- fix input detect again [\#77](https://github.com/OpenVoiceOS/ovos-utils/pull/77) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a14](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a14) (2022-10-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a13...V0.0.25a14) - -**Fixed bugs:** - -- fallback to True for mouse detection if libinput is missing [\#76](https://github.com/OpenVoiceOS/ovos-utils/pull/76) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a13](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a13) (2022-10-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a12...V0.0.25a13) - -**Implemented enhancements:** - -- scan /dev/input for device detection [\#75](https://github.com/OpenVoiceOS/ovos-utils/pull/75) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a12](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a12) (2022-10-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a11...V0.0.25a12) - -**Fixed bugs:** - -- feat/xinput support [\#74](https://github.com/OpenVoiceOS/ovos-utils/pull/74) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a11](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a11) (2022-10-11) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a10...V0.0.25a11) - -**Merged pull requests:** - -- add mail api point to ovos api service [\#73](https://github.com/OpenVoiceOS/ovos-utils/pull/73) ([AIIX](https://github.com/AIIX)) - -## [V0.0.25a10](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a10) (2022-10-10) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a9...V0.0.25a10) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a14...V0.0.31a15) **Closed issues:** -- `get_mycroft_bus` ignores Configuration [\#71](https://github.com/OpenVoiceOS/ovos-utils/issues/71) +- Is there an option to disable the log file creation [\#124](https://github.com/OpenVoiceOS/ovos-utils/issues/124) **Merged pull requests:** -- Update `ovos_config` references, Read config in `get_mycroft_bus` [\#72](https://github.com/OpenVoiceOS/ovos-utils/pull/72) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.25a9](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a9) (2022-10-10) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a8...V0.0.25a9) - -**Fixed bugs:** - -- remove "logs" subfolder [\#70](https://github.com/OpenVoiceOS/ovos-utils/pull/70) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a8](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a8) (2022-10-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a7...V0.0.25a8) - -**Implemented enhancements:** +- Stable extra dependencies [\#127](https://github.com/OpenVoiceOS/ovos-utils/pull/127) ([NeonDaniel](https://github.com/NeonDaniel)) -- Update log.py [\#69](https://github.com/OpenVoiceOS/ovos-utils/pull/69) ([JarbasAl](https://github.com/JarbasAl)) +## [V0.0.31a14](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a14) (2023-04-13) -## [V0.0.25a7](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a7) (2022-10-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a6...V0.0.25a7) - -**Implemented enhancements:** - -- feat/email\_utils [\#68](https://github.com/OpenVoiceOS/ovos-utils/pull/68) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a6](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a6) (2022-09-28) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a5...V0.0.25a6) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a13...V0.0.31a14) **Merged pull requests:** -- Add geolocate methods support in ovos\_api\_service [\#67](https://github.com/OpenVoiceOS/ovos-utils/pull/67) ([AIIX](https://github.com/AIIX)) - -## [V0.0.25a5](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a5) (2022-09-16) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a4...V0.0.25a5) - -**Implemented enhancements:** +- Update docstrings, annotate deprecation, and outline unit tests [\#119](https://github.com/OpenVoiceOS/ovos-utils/pull/119) ([NeonDaniel](https://github.com/NeonDaniel)) -- Add methods for controlled notifications [\#66](https://github.com/OpenVoiceOS/ovos-utils/pull/66) ([AIIX](https://github.com/AIIX)) +## [V0.0.31a13](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a13) (2023-04-13) -## [V0.0.25a4](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a4) (2022-09-10) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a3...V0.0.25a4) - -**Implemented enhancements:** - -- feat/timed\_lru\_cache [\#65](https://github.com/OpenVoiceOS/ovos-utils/pull/65) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a3](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a3) (2022-09-10) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a2...V0.0.25a3) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a12...V0.0.31a13) **Fixed bugs:** -- fix/syntax\_error [\#64](https://github.com/OpenVoiceOS/ovos-utils/pull/64) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.25a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a2) (2022-09-08) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.25a1...V0.0.25a2) - -**Merged pull requests:** - -- Add method to restart arbitrary systemd service [\#63](https://github.com/OpenVoiceOS/ovos-utils/pull/63) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.25a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.25a1) (2022-09-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.24...V0.0.25a1) - -**Implemented enhancements:** - -- add more api methods [\#62](https://github.com/OpenVoiceOS/ovos-utils/pull/62) ([AIIX](https://github.com/AIIX)) - -## [V0.0.24](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.24) (2022-09-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.24a4...V0.0.24) - -## [V0.0.24a4](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.24a4) (2022-09-06) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.24a3...V0.0.24a4) - -**Merged pull requests:** - -- add systemctl mycroft restart option [\#61](https://github.com/OpenVoiceOS/ovos-utils/pull/61) ([AIIX](https://github.com/AIIX)) - -## [V0.0.24a3](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.24a3) (2022-09-06) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.24a2...V0.0.24a3) - -**Implemented enhancements:** - -- feat/ovos\_api [\#60](https://github.com/OpenVoiceOS/ovos-utils/pull/60) ([JarbasAl](https://github.com/JarbasAl)) +- \[log\] Only creates directory if not stdout [\#125](https://github.com/OpenVoiceOS/ovos-utils/pull/125) ([goldyfruit](https://github.com/goldyfruit)) -## [V0.0.24a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.24a2) (2022-08-17) +## [V0.0.31a12](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a12) (2023-04-12) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.24a1...V0.0.24a2) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a11...V0.0.31a12) **Merged pull requests:** -- Handle exceptions getting cache directory when MemoryTempfile fails \(i.e. in a chroot\) [\#58](https://github.com/OpenVoiceOS/ovos-utils/pull/58) ([NeonDaniel](https://github.com/NeonDaniel)) +- feat/native\_OCP [\#120](https://github.com/OpenVoiceOS/ovos-utils/pull/120) ([JarbasAl](https://github.com/JarbasAl)) -## [V0.0.24a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.24a1) (2022-08-15) +## [V0.0.31a11](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a11) (2023-04-11) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.23...V0.0.24a1) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a10...V0.0.31a11) **Merged pull requests:** -- Add extend about data method to gui utils [\#57](https://github.com/OpenVoiceOS/ovos-utils/pull/57) ([AIIX](https://github.com/AIIX)) +- Deprecate internal `ovos_config.config` references [\#122](https://github.com/OpenVoiceOS/ovos-utils/pull/122) ([NeonDaniel](https://github.com/NeonDaniel)) -## [V0.0.23](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.23) (2022-07-20) +## [V0.0.31a10](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a10) (2023-04-11) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.23a7...V0.0.23) - -## [V0.0.23a7](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.23a7) (2022-07-20) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.23a6...V0.0.23a7) - -**Implemented enhancements:** - -- Skill location utilities [\#55](https://github.com/OpenVoiceOS/ovos-utils/pull/55) ([NeonDaniel](https://github.com/NeonDaniel)) - -**Merged pull requests:** - -- Update release tag workflows to include version change commits [\#56](https://github.com/OpenVoiceOS/ovos-utils/pull/56) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a6](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.23a6) (2022-07-06) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.23a5...V0.0.23a6) - -**Merged pull requests:** - -- refactor/use ovos\_config package [\#52](https://github.com/OpenVoiceOS/ovos-utils/pull/52) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a5](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.23a5) (2022-07-06) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.23a4...V0.0.23a5) - -**Implemented enhancements:** - -- port/file\_watcher [\#54](https://github.com/OpenVoiceOS/ovos-utils/pull/54) ([NeonJarbas](https://github.com/NeonJarbas)) - -## [V0.0.23a4](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.23a4) (2022-07-06) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.23a3...V0.0.23a4) - -**Merged pull requests:** - -- Loosen mycroft-messagebus-client dependency to allow 0.10.0 [\#53](https://github.com/OpenVoiceOS/ovos-utils/pull/53) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a3](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.23a3) (2022-06-15) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.23a2...V0.0.23a3) - -**Fixed bugs:** - -- Prevent raising exception when msm config not present [\#51](https://github.com/OpenVoiceOS/ovos-utils/pull/51) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.23a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.23a2) (2022-06-10) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.23a1...V0.0.23a2) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a9...V0.0.31a10) **Fixed bugs:** -- fix/allow\_LF\_lang\_to\_be\_None [\#50](https://github.com/OpenVoiceOS/ovos-utils/pull/50) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.23a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.23a1) (2022-06-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.22...V0.0.23a1) - -**Fixed bugs:** - -- fix/screen\_check [\#49](https://github.com/OpenVoiceOS/ovos-utils/pull/49) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.22](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.22) (2022-06-02) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.22a3...V0.0.22) +- fix/bus compat [\#121](https://github.com/OpenVoiceOS/ovos-utils/pull/121) ([JarbasAl](https://github.com/JarbasAl)) -## [V0.0.22a3](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.22a3) (2022-06-02) +## [V0.0.31a9](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a9) (2023-04-11) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.22a2...V0.0.22a3) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a8...V0.0.31a9) **Fixed bugs:** -- Fix/full lang [\#48](https://github.com/OpenVoiceOS/ovos-utils/pull/48) ([NeonJarbas](https://github.com/NeonJarbas)) +- add back wrapper around removed methods [\#118](https://github.com/OpenVoiceOS/ovos-utils/pull/118) ([JarbasAl](https://github.com/JarbasAl)) -## [V0.0.22a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.22a2) (2022-05-31) +## [V0.0.31a8](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a8) (2023-04-11) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.22a1...V0.0.22a2) - -**Implemented enhancements:** - -- feat/lang\_utils [\#47](https://github.com/OpenVoiceOS/ovos-utils/pull/47) ([NeonJarbas](https://github.com/NeonJarbas)) - -## [V0.0.22a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.22a1) (2022-05-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.21...V0.0.22a1) - -**Implemented enhancements:** - -- support ovos-shell [\#45](https://github.com/OpenVoiceOS/ovos-utils/pull/45) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.21](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.21) (2022-05-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.21a6...V0.0.21) - -## [V0.0.21a6](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.21a6) (2022-05-17) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.21a5...V0.0.21a6) - -**Implemented enhancements:** - -- add InputDeviceHelper to detect available inputs on current system [\#44](https://github.com/OpenVoiceOS/ovos-utils/pull/44) ([AIIX](https://github.com/AIIX)) - -## [V0.0.21a5](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.21a5) (2022-05-12) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.21a4...V0.0.21a5) - -**Implemented enhancements:** - -- Add widgets helper [\#43](https://github.com/OpenVoiceOS/ovos-utils/pull/43) ([AIIX](https://github.com/AIIX)) - -## [V0.0.21a4](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.21a4) (2022-05-09) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.21a3...V0.0.21a4) - -**Merged pull requests:** - -- refactor/migrate\_adapt\_boilerplate [\#42](https://github.com/OpenVoiceOS/ovos-utils/pull/42) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.21a3](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.21a3) (2022-05-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.21a2...V0.0.21a3) - -**Merged pull requests:** - -- add sdist [\#41](https://github.com/OpenVoiceOS/ovos-utils/pull/41) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.21a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.21a2) (2022-05-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.21a1...V0.0.21a2) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a7...V0.0.31a8) **Merged pull requests:** -- Fix/remove unused dep [\#40](https://github.com/OpenVoiceOS/ovos-utils/pull/40) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.21a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.21a1) (2022-05-07) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.20...V0.0.21a1) - -**Fixed bugs:** - -- Fix/adapt [\#39](https://github.com/OpenVoiceOS/ovos-utils/pull/39) ([JarbasAl](https://github.com/JarbasAl)) +- Update input device checks [\#81](https://github.com/OpenVoiceOS/ovos-utils/pull/81) ([NeonDaniel](https://github.com/NeonDaniel)) -## [V0.0.20](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.20) (2022-04-27) +## [V0.0.31a7](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a7) (2023-04-10) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.20a4...V0.0.20) - -## [V0.0.20a4](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.20a4) (2022-04-27) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.20a3...V0.0.20a4) - -**Implemented enhancements:** - -- Adds ovos service api [\#38](https://github.com/OpenVoiceOS/ovos-utils/pull/38) ([AIIX](https://github.com/AIIX)) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a6...V0.0.31a7) **Merged pull requests:** -- notify matrix chat on PR merged [\#37](https://github.com/OpenVoiceOS/ovos-utils/pull/37) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.20a3](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.20a3) (2022-03-23) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.20a2...V0.0.20a3) - -**Implemented enhancements:** - -- Feat/list utils [\#36](https://github.com/OpenVoiceOS/ovos-utils/pull/36) ([JarbasAl](https://github.com/JarbasAl)) +- feat/PKGBUILD [\#116](https://github.com/OpenVoiceOS/ovos-utils/pull/116) ([JarbasAl](https://github.com/JarbasAl)) +- Feat/optional ovos config [\#106](https://github.com/OpenVoiceOS/ovos-utils/pull/106) ([JarbasAl](https://github.com/JarbasAl)) -## [V0.0.20a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.20a2) (2022-03-16) +## [V0.0.31a6](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a6) (2023-04-07) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.20a1...V0.0.20a2) - -**Implemented enhancements:** - -- Feat/process utils [\#35](https://github.com/OpenVoiceOS/ovos-utils/pull/35) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.20a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.20a1) (2022-03-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.19...V0.0.20a1) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a5...V0.0.31a6) **Fixed bugs:** -- Fix/resolve resource file [\#34](https://github.com/OpenVoiceOS/ovos-utils/pull/34) ([JarbasAl](https://github.com/JarbasAl)) +- fix/missing\_dependency [\#115](https://github.com/OpenVoiceOS/ovos-utils/pull/115) ([JarbasAl](https://github.com/JarbasAl)) -## [V0.0.19](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.19) (2022-03-03) +## [V0.0.31a5](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a5) (2023-04-07) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.19a3...V0.0.19) - -## [V0.0.19a3](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.19a3) (2022-03-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.19a2...V0.0.19a3) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a4...V0.0.31a5) **Fixed bugs:** -- fix/platform\_detect [\#33](https://github.com/OpenVoiceOS/ovos-utils/pull/33) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.19a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.19a2) (2022-03-03) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.17a6...V0.0.19a2) +- fix/missing\_dependency [\#114](https://github.com/OpenVoiceOS/ovos-utils/pull/114) ([JarbasAl](https://github.com/JarbasAl)) -**Breaking changes:** +## [V0.0.31a4](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a4) (2023-04-06) -- Refactor/remove deprecated [\#11](https://github.com/OpenVoiceOS/ovos-utils/pull/11) ([JarbasAl](https://github.com/JarbasAl)) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a3...V0.0.31a4) **Merged pull requests:** -- Fix/package workflow [\#32](https://github.com/OpenVoiceOS/ovos-utils/pull/32) ([JarbasAl](https://github.com/JarbasAl)) +- Update rapidfuzz dependency [\#112](https://github.com/OpenVoiceOS/ovos-utils/pull/112) ([NeonDaniel](https://github.com/NeonDaniel)) -## [V0.0.17a6](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.17a6) (2022-02-25) +## [V0.0.31a3](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a3) (2023-04-05) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.17a5...V0.0.17a6) - -**Fixed bugs:** - -- Handle stopwatch.stop before started [\#31](https://github.com/OpenVoiceOS/ovos-utils/pull/31) ([NeonDaniel](https://github.com/NeonDaniel)) - -## [V0.0.17a5](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.17a5) (2022-02-25) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.18...V0.0.17a5) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a2...V0.0.31a3) **Merged pull requests:** -- Refactor/workflows from template [\#30](https://github.com/OpenVoiceOS/ovos-utils/pull/30) ([JarbasAl](https://github.com/JarbasAl)) - -## [V0.0.18](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.18) (2022-02-24) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.17a4...V0.0.18) - -## [V0.0.17a4](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.17a4) (2022-02-24) - -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/0.0.12...V0.0.17a4) +- feat/FakeMessage [\#111](https://github.com/OpenVoiceOS/ovos-utils/pull/111) ([JarbasAl](https://github.com/JarbasAl)) -**Implemented enhancements:** - -- Feat/diagnostic mode [\#18](https://github.com/OpenVoiceOS/ovos-utils/pull/18) ([NeonJarbas](https://github.com/NeonJarbas)) -- fix/play\_audio [\#14](https://github.com/OpenVoiceOS/ovos-utils/pull/14) ([NeonJarbas](https://github.com/NeonJarbas)) -- Feat/better stop watch [\#10](https://github.com/OpenVoiceOS/ovos-utils/pull/10) ([JarbasAl](https://github.com/JarbasAl)) -- Feat/more file utils [\#9](https://github.com/OpenVoiceOS/ovos-utils/pull/9) ([JarbasAl](https://github.com/JarbasAl)) -- add notification api change and style parameter [\#6](https://github.com/OpenVoiceOS/ovos-utils/pull/6) ([AIIX](https://github.com/AIIX)) +## [V0.0.31a2](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a2) (2023-04-05) -**Fixed bugs:** - -- Fix/core module detection + refactor xdg config path utils [\#28](https://github.com/OpenVoiceOS/ovos-utils/pull/28) ([NeonJarbas](https://github.com/NeonJarbas)) -- Fix typo in configuration paths [\#27](https://github.com/OpenVoiceOS/ovos-utils/pull/27) ([NeonDaniel](https://github.com/NeonDaniel)) -- Add back colour dependency [\#24](https://github.com/OpenVoiceOS/ovos-utils/pull/24) ([NeonDaniel](https://github.com/NeonDaniel)) -- Add user config home to `get_xdg_config_dirs` [\#23](https://github.com/OpenVoiceOS/ovos-utils/pull/23) ([NeonDaniel](https://github.com/NeonDaniel)) -- Loosen dependency versions for OVOS image compat [\#16](https://github.com/OpenVoiceOS/ovos-utils/pull/16) ([NeonDaniel](https://github.com/NeonDaniel)) -- Ovos conf [\#12](https://github.com/OpenVoiceOS/ovos-utils/pull/12) ([NeonJarbas](https://github.com/NeonJarbas)) -- fix/update\_enclosure\_api [\#8](https://github.com/OpenVoiceOS/ovos-utils/pull/8) ([JarbasAl](https://github.com/JarbasAl)) -- Fix/nested delete [\#7](https://github.com/OpenVoiceOS/ovos-utils/pull/7) ([JarbasAl](https://github.com/JarbasAl)) - -**Closed issues:** - -- Dependency Version specs [\#13](https://github.com/OpenVoiceOS/ovos-utils/issues/13) -- dependencies are duplicated + add version specs [\#5](https://github.com/OpenVoiceOS/ovos-utils/issues/5) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.31a1...V0.0.31a2) **Merged pull requests:** -- feat/packaging workflows [\#29](https://github.com/OpenVoiceOS/ovos-utils/pull/29) ([JarbasAl](https://github.com/JarbasAl)) -- Update license\_tests.yml [\#26](https://github.com/OpenVoiceOS/ovos-utils/pull/26) ([NeonJarbas](https://github.com/NeonJarbas)) -- Feat/pypi workflow [\#25](https://github.com/OpenVoiceOS/ovos-utils/pull/25) ([NeonJarbas](https://github.com/NeonJarbas)) -- feat/license tests workflow [\#22](https://github.com/OpenVoiceOS/ovos-utils/pull/22) ([JarbasAl](https://github.com/JarbasAl)) -- refactor/replace\_pyxdg [\#21](https://github.com/OpenVoiceOS/ovos-utils/pull/21) ([NeonJarbas](https://github.com/NeonJarbas)) -- refactor/bump\_requests [\#20](https://github.com/OpenVoiceOS/ovos-utils/pull/20) ([NeonJarbas](https://github.com/NeonJarbas)) -- refactor/deprecate\_inflection [\#19](https://github.com/OpenVoiceOS/ovos-utils/pull/19) ([NeonJarbas](https://github.com/NeonJarbas)) -- Refactor requirements to read from files [\#15](https://github.com/OpenVoiceOS/ovos-utils/pull/15) ([NeonDaniel](https://github.com/NeonDaniel)) +- refactor/ovos-bus-client [\#110](https://github.com/OpenVoiceOS/ovos-utils/pull/110) ([JarbasAl](https://github.com/JarbasAl)) -## [0.0.12](https://github.com/OpenVoiceOS/ovos-utils/tree/0.0.12) (2021-11-04) +## [V0.0.31a1](https://github.com/OpenVoiceOS/ovos-utils/tree/V0.0.31a1) (2023-03-23) -[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/25fe462e3c19a58f32dc1fd940bf7c96fc18e6de...0.0.12) +[Full Changelog](https://github.com/OpenVoiceOS/ovos-utils/compare/V0.0.30...V0.0.31a1) -**Implemented enhancements:** +**Merged pull requests:** -- release0.0.12/extract get\_local\_settings and save\_settings from core [\#4](https://github.com/OpenVoiceOS/ovos-utils/pull/4) ([JarbasAl](https://github.com/JarbasAl)) -- Adds checked path to log that root config path was not found [\#3](https://github.com/OpenVoiceOS/ovos-utils/pull/3) ([NeonDaniel](https://github.com/NeonDaniel)) +- Add show input box method for skills [\#109](https://github.com/OpenVoiceOS/ovos-utils/pull/109) ([AIIX](https://github.com/AIIX)) diff --git a/ovos_utils/__init__.py b/ovos_utils/__init__.py index 4b063031..c2342305 100644 --- a/ovos_utils/__init__.py +++ b/ovos_utils/__init__.py @@ -14,7 +14,7 @@ import re from functools import lru_cache, wraps from os.path import isdir, join -from threading import Thread +from threading import Thread, Event from time import monotonic_ns from time import sleep @@ -23,6 +23,7 @@ # TODO: Deprecate below imports from ovos_utils.file_utils import resolve_ovos_resource_file, resolve_resource_file from ovos_utils.network_utils import get_ip, get_external_ip, is_connected_dns, is_connected_http, is_connected +from ovos_utils.log import LOG class classproperty(property): @@ -33,6 +34,9 @@ def __get__(self, owner_self, owner_cls): def ensure_mycroft_import(): + # TODO: Deprecate in 0.1.0 + LOG.warning("This method is deprecated. Anything depending on `mycroft`" + "should install `ovos-core` as a dependency") try: import mycroft except ImportError: @@ -46,6 +50,9 @@ def ensure_mycroft_import(): def get_mycroft_root(): + # TODO: Deprecate in 0.1.0 + LOG.warning("This method is deprecated. Code should import from the current" + "namespace; other system paths are irrelevant.") paths = [ "/opt/venvs/mycroft-core/lib/python3.7/site-packages/", # mark1/2 "/opt/venvs/mycroft-core/lib/python3.4/site-packages/ ", # old mark1 installs @@ -134,8 +141,7 @@ def loop(*args, **kwargs): def wait_for_exit_signal(): """Blocks until KeyboardInterrupt is received""" try: - while True: - sleep(100) + Event().wait() except KeyboardInterrupt: pass diff --git a/ovos_utils/bracket_expansion.py b/ovos_utils/bracket_expansion.py index 1c69a6f5..35f02c1d 100644 --- a/ovos_utils/bracket_expansion.py +++ b/ovos_utils/bracket_expansion.py @@ -1,7 +1,8 @@ import re +from typing import List -def expand_parentheses(sent): +def expand_parentheses(sent: List[str]) -> List[str]: """ ['1', '(', '2', '|', '3, ')'] -> [['1', '2'], ['1', '3']] For example: diff --git a/ovos_utils/configuration.py b/ovos_utils/configuration.py index 4fe38f9b..9a234928 100644 --- a/ovos_utils/configuration.py +++ b/ovos_utils/configuration.py @@ -1,61 +1,398 @@ -from ovos_utils.system import search_mycroft_core_location, is_running_from_module -from ovos_utils.xdg_utils import ( - xdg_config_home, - xdg_config_dirs, - xdg_data_home, - xdg_data_dirs, - xdg_cache_home -) +import json +from os import makedirs +from os.path import join, expanduser, exists, isfile + +import ovos_utils.xdg_utils as xdg from ovos_utils.log import LOG -from ovos_config.locations import ( - get_xdg_config_dirs, - get_xdg_data_dirs, - get_xdg_data_save_path, - get_xdg_config_save_path, - get_xdg_cache_save_path, - find_default_config, - find_user_config, - get_config_locations, - get_webcache_location, - get_xdg_config_locations -) - -from ovos_config.locale import get_default_lang -from ovos_config.meta import ( - get_ovos_config, - get_ovos_default_config_paths, - is_using_xdg, - get_xdg_base, - set_xdg_base, - set_config_filename, - set_default_config, - get_config_filename -) - -from ovos_config.config import ( - read_mycroft_config, - update_mycroft_config -) - -from ovos_config.models import ( - LocalConf, - ReadOnlyConfig, - MycroftUserConfig, - MycroftDefaultConfig, - MycroftSystemConfig, - MycroftXDGConfig -) - -from ovos_config.meta import save_ovos_config as save_ovos_core_config - -LOG.warning("configuration moved to the `ovos_config` package. This submodule " - "will be removed in ovos_utils 0.1.0") - - -def set_config_name(name, core_folder=None): - # TODO deprecate, was only out in a couple versions - LOG.warning("This reference is deprecated, use " - "`ovos_config.meta.set_config_filename`") - # renamed to match HolmesV - set_config_filename(name, core_folder) + +# TODO - deprecate this submodule in 0.1.0 +# note that a couple of these are also used inside ovos-utils +# perhaps those usages should also move into workshop ? + +def get_default_lang(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.locale import get_default_lang as _get + return _get() + except ImportError: + return read_mycroft_config().get("lang", "en-us") + + +def find_user_config(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.locations import find_user_config as _get + return _get() + except ImportError: + + return join(get_xdg_config_save_path(), get_config_filename()) + + +def get_webcache_location(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + return join(get_xdg_config_save_path(), 'web_cache.json') + + +def get_config_locations(default=True, web_cache=True, system=True, + old_user=True, user=True): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.locations import get_config_locations as _get + return _get(default, web_cache, system, old_user, user) + except ImportError: + locs = [] + ovos_cfg = get_ovos_config() + if default: + locs.append(ovos_cfg["default_config_path"]) + if system: + locs.append(f"/etc/{ovos_cfg['base_folder']}/{ovos_cfg['config_filename']}") + if web_cache: + locs.append(get_webcache_location()) + if old_user: + locs.append(f"~/.{ovos_cfg['base_folder']}/{ovos_cfg['config_filename']}") + if user: + locs.append(f"{get_xdg_config_save_path()}/{ovos_cfg['config_filename']}") + return locs + + +def get_ovos_config(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.meta import get_ovos_config as _get + return _get() + except ImportError: + return {"xdg": True, + "base_folder": "mycroft", + "config_filename": "mycroft.conf"} + + +def get_xdg_base(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.meta import get_xdg_base as _get + return _get() + except ImportError: + return "mycroft" + + +def get_xdg_config_locations(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + # This includes both the user config and + # /etc/xdg/mycroft/mycroft.conf + xdg_paths = list(reversed( + [join(p, get_config_filename()) + for p in get_xdg_config_dirs()] + )) + return xdg_paths + + +def get_xdg_data_dirs(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.locations import get_xdg_data_dirs as _get + return _get() + except ImportError: + return [expanduser("~/.local/share/mycroft")] + + +def get_xdg_config_dirs(folder=None): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.locations import get_xdg_config_dirs as _get + return _get() + except ImportError: + folder = folder or get_xdg_base() + xdg_dirs = xdg.xdg_config_dirs() + [xdg.xdg_config_home()] + return [join(path, folder) for path in xdg_dirs] + + +def get_xdg_cache_save_path(folder=None): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.locations import get_xdg_cache_save_path as _get + return _get() + except ImportError: + folder = folder or get_xdg_base() + return join(xdg.xdg_cache_home(), folder) + + +def get_xdg_data_save_path(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.locations import get_xdg_data_save_path as _get + return _get() + except ImportError: + return expanduser("~/.local/share/mycroft") + + +def get_xdg_config_save_path(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.locations import get_xdg_config_save_path as _get + return _get() + except ImportError: + return expanduser("~/.config/mycroft") + + +def is_using_xdg(): + """ DEPRECATED """ + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + return True + + +def set_xdg_base(*args, **kwargs): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.meta import set_xdg_base as _set + _set(*args, **kwargs) + except: + pass + + +def set_config_filename(*args, **kwargs): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.meta import config_filename as _set + _set(*args, **kwargs) + except: + pass + + +def get_config_filename(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.locale import get_config_filename as _get + return _get() + except ImportError: + return "mycroft.conf" + + +def get_ovos_default_config_paths(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.meta import get_ovos_default_config_paths as _get + return _get() + except: + return ["/etc/OpenVoiceOS/ovos.conf"] + + +def read_mycroft_config(): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config import Configuration + return Configuration() + except ImportError: + pass + path = expanduser(f"~/.config/mycroft/mycroft.conf") + if isfile(path): + with open(path) as f: + return json.load(f) + return { + # TODO - default cfg + "lang": "en-us" + } + + +def update_mycroft_config(config, path=None, bus=None): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.config import update_mycroft_config as _update + _update(config, path, bus) + except ImportError: + pass + # save in default user location + path = expanduser(f"~/.config/mycroft") + makedirs(path, exist_ok=True) + with open(f"{path}/mycroft.conf", "w") as f: + json.dump(config, f, indent=2) + + +def set_default_config(*args, **kwargs): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.meta import set_default_config as _set + _set(*args, **kwargs) + except: + pass + + +def save_ovos_core_config(*args, **kwargs): + LOG.warning("configuration moved to the `ovos_config` package. This submodule " + "will be removed in ovos_utils 0.1.0") + try: + from ovos_config.meta import save_ovos_config as _set + _set(*args, **kwargs) + except: + pass + + +try: + from ovos_config.models import ( + LocalConf, + ReadOnlyConfig, + MycroftUserConfig, + MycroftDefaultConfig, + MycroftSystemConfig, + MycroftXDGConfig + ) +except ImportError: + LOG.warning("configuration classes moved to the `ovos_config.models` package. " + "This submodule will be removed in ovos_utils 0.1.0") + from combo_lock import NamedLock + import yaml + from ovos_utils.json_helper import load_commented_json, merge_dict + + + class LocalConf(dict): + """Config dictionary from file.""" + allow_overwrite = True + # lock is shared among all subclasses, + # regardless of what file is being edited only one file should change at a time + # this ensure orderly behaviour in anything monitoring changes, + # eg FileWatcher util, configuration.patch bus handlers + __lock = NamedLock("ovos_config") + + def __init__(self, path): + super().__init__(self) + self.path = path + if path: + self.load_local(path) + + def _get_file_format(self, path=None): + """The config file format + supported file extensions: + - json (.json) + - commented json (.conf) + - yaml (.yaml/.yml) + + returns "yaml" or "json" + """ + path = path or self.path + if not path: + return "dict" + if path.endswith(".yml") or path.endswith(".yaml"): + return "yaml" + else: + return "json" + + def load_local(self, path=None): + """Load local json file into self. + + Args: + path (str): file to load + """ + path = path or self.path + if not path: + LOG.error(f"in memory configuration, nothing to load") + return + if exists(path) and isfile(path): + with self.__lock: + try: + if self._get_file_format(path) == "yaml": + with open(path) as f: + config = yaml.safe_load(f) + else: + config = load_commented_json(path) + if config: + for key in config: + self.__setitem__(key, config[key]) + LOG.debug(f"Configuration {path} loaded") + else: + LOG.debug(f"Empty config found at: {path}") + except Exception as e: + LOG.exception(f"Error loading configuration '{path}'") + else: + LOG.debug(f"Configuration '{path}' not defined, skipping") + + def reload(self): + self.load_local(self.path) + + def store(self, path=None): + path = path or self.path + if not path: + LOG.error(f"in memory configuration, no save location") + return + with self.__lock: + if self._get_file_format(path) == "yaml": + with open(path, 'w+') as f: + yaml.dump(dict(self), f, allow_unicode=True, + default_flow_style=False, sort_keys=False) + else: + with open(path, 'w+') as f: + json.dump(self, f, indent=2) + + def merge(self, conf): + merge_dict(self, conf) + + + class ReadOnlyConfig(LocalConf): + """ read only """ + + def __init__(self, path, allow_overwrite=False): + super().__init__(path) + self.allow_overwrite = allow_overwrite + + def reload(self): + old = self.allow_overwrite + self.allow_overwrite = True + super().reload() + self.allow_overwrite = old + + def __setitem__(self, key, value): + if not self.allow_overwrite: + raise PermissionError(f"{self.path} is read only! it can not be modified at runtime") + super().__setitem__(key, value) + + def merge(self, *args, **kwargs): + if not self.allow_overwrite: + raise PermissionError(f"{self.path} is read only! it can not be modified at runtime") + super().merge(*args, **kwargs) + + def store(self, path=None): + if not self.allow_overwrite: + raise PermissionError(f"{self.path} is read only! it can not be modified at runtime") + super().store(path) + + + class MycroftDefaultConfig(ReadOnlyConfig): + def __init__(self): + super().__init__(join(get_xdg_config_save_path(), get_config_filename())) + + def set_root_config_path(self, root_config): + # in case we got it wrong / non standard + self.path = root_config + self.reload() + + + class MycroftSystemConfig(ReadOnlyConfig): + def __init__(self, allow_overwrite=False): + super().__init__("/etc/mycroft/mycroft.conf", allow_overwrite) + + + class RemoteConf(LocalConf): + def __init__(self, cache=get_webcache_location()): + super(RemoteConf, self).__init__(cache) + + + MycroftXDGConfig = MycroftUserConfig = MycroftDefaultConfig diff --git a/ovos_utils/device_input.py b/ovos_utils/device_input.py index 5b6d54af..17e9b21b 100644 --- a/ovos_utils/device_input.py +++ b/ovos_utils/device_input.py @@ -11,7 +11,7 @@ def __init__(self) -> None: if not find_executable("libinput") and not find_executable("xinput"): LOG.warning("Could not find libinput, input device detection will be inaccurate") - # ToDo: add support for discovring the input device based of a connected + # ToDo: add support for discovering the input device based of a connected # monitors, currently linux only supports input listing directly from the # system def _build_linput_devices_list(self): @@ -138,8 +138,3 @@ def can_use_touch_mouse(): def can_use_keyboard(): return InputDeviceHelper().can_use_keyboard() - - -if __name__ == "__main__": - - can_use_touch_mouse() diff --git a/ovos_utils/dialog.py b/ovos_utils/dialog.py index cfc0fd1a..2b0500fd 100644 --- a/ovos_utils/dialog.py +++ b/ovos_utils/dialog.py @@ -3,9 +3,9 @@ import re from os.path import join from pathlib import Path +from typing import Optional from ovos_utils.bracket_expansion import expand_options -from ovos_config.config import Configuration from ovos_utils.file_utils import resolve_resource_file from ovos_utils.lang import translate_word from ovos_utils.log import LOG @@ -104,12 +104,15 @@ def render(self, template_name, context=None, index=None): return line -def load_dialogs(dialog_dir, renderer=None): - """Load all dialog files within the specified directory. +def load_dialogs(dialog_dir: str, + renderer: Optional[MustacheDialogRenderer] = None) -> \ + MustacheDialogRenderer: + """ + Load all dialog files within the specified directory. Args: dialog_dir (str): directory that contains dialog files - + renderer (MustacheDialogRenderer): instance to load files with Returns: a loaded instance of a dialog renderer """ @@ -129,8 +132,10 @@ def load_dialogs(dialog_dir, renderer=None): return renderer -def get_dialog(phrase, lang=None, context=None): - """Looks up a resource file for the given phrase. +def get_dialog(phrase: str, lang: str = None, + context: Optional[dict] = None) -> str: + """ + Looks up a resource file for the given phrase in the specified language. If no file is found, the requested phrase is returned as the string. This will use the default language for translations. @@ -145,10 +150,17 @@ def get_dialog(phrase, lang=None, context=None): """ if not lang: + LOG.warning(f"Expected a string lang and got None. This config" + f"fallback behavior will be deprecated in a future release") try: - conf = Configuration() + from ovos_config.config import read_mycroft_config + conf = read_mycroft_config() lang = conf.get('lang') + except ImportError: + LOG.warning("Config not provided and ovos_config not available") + lang = "en-us" except FileNotFoundError: + LOG.warning("Configuration file not found, default lang to 'en-us'") lang = "en-us" filename = join('text', lang.lower(), phrase + '.dialog') @@ -164,8 +176,10 @@ def get_dialog(phrase, lang=None, context=None): return stache.render('template', context) -def join_list(items, connector, sep=None, lang=''): - """ Join a list into a phrase using the given connector word +def join_list(items: list, connector: str, sep: Optional[str] = None, + lang: Optional[str] = '') -> str: + """ + Join a list into a phrase using the given connector word Examples: join_list([1,2,3], "and") -> "1, 2 and 3" join_list([1,2,3], "and", ";") -> "1; 2 and 3" diff --git a/ovos_utils/enclosure/__init__.py b/ovos_utils/enclosure/__init__.py index 7d6ca782..f9947a44 100644 --- a/ovos_utils/enclosure/__init__.py +++ b/ovos_utils/enclosure/__init__.py @@ -2,9 +2,12 @@ from ovos_utils.fingerprinting import detect_platform, MycroftPlatform from enum import Enum from os.path import exists +from typing import Optional +from ovos_utils.log import LOG class MycroftEnclosures(str, Enum): + # TODO: Deprecate in 0.1.0 PICROFT = "picroft" BIGSCREEN = "kde" OVOS = "OpenVoiceOS" @@ -17,7 +20,15 @@ class MycroftEnclosures(str, Enum): OTHER = "unknown" -def enclosure2rootdir(enclosure=None): +def enclosure2rootdir(enclosure: MycroftEnclosures = None) -> Optional[str]: + """ + Find the default installed core location for a specific platform. + @param enclosure: MycroftEnclosures object to get root path for + @return: string default root path + """ + # TODO: Deprecate in 0.1.0 + LOG.warning("This method is deprecated. Code should import from the current" + "namespace; other system paths are irrelevant.") enclosure = enclosure or detect_enclosure() if enclosure == MycroftEnclosures.OLD_MARK1: return MycroftRootLocations.OLD_MARK1 @@ -34,7 +45,14 @@ def enclosure2rootdir(enclosure=None): return None -def detect_enclosure(): +def detect_enclosure() -> MycroftEnclosures: + """ + Determine which enclosure is present on this file system. + @return: MycroftEnclosures object detected + """ + # TODO: Deprecate in 0.1.0 + LOG.warning("This method is deprecated. Platform-specific code should" + "use ovos_utils.fingerprinting.detect_platform directly") platform = detect_platform() if platform == MycroftPlatform.MARK1: if exists(MycroftRootLocations.OLD_MARK1): diff --git a/ovos_utils/enclosure/api.py b/ovos_utils/enclosure/api.py index 117186ba..91936b05 100644 --- a/ovos_utils/enclosure/api.py +++ b/ovos_utils/enclosure/api.py @@ -1,4 +1,4 @@ -from ovos_utils.messagebus import Message +from ovos_utils.messagebus import FakeMessage as Message class EnclosureAPI: diff --git a/ovos_utils/events.py b/ovos_utils/events.py index 0258f252..3663fd37 100644 --- a/ovos_utils/events.py +++ b/ovos_utils/events.py @@ -4,11 +4,12 @@ from ovos_utils.intents.intent_service_interface import to_alnum from ovos_utils.log import LOG -from ovos_utils.messagebus import Message, FakeBus +from ovos_utils.messagebus import FakeBus, FakeMessage as Message -def unmunge_message(message, skill_id): - """Restore message keywords by removing the Letterified skill ID. +def unmunge_message(message: Message, skill_id: str) -> Message: + """ + Restore message keywords by removing the Letterified skill ID. Args: message (Message): Intent result message skill_id (str): skill identifier @@ -26,8 +27,9 @@ def unmunge_message(message, skill_id): return message -def get_handler_name(handler): - """Name (including class if available) of handler function. +def get_handler_name(handler) -> str: + """ + Name (including class if available) of handler function. Args: handler (function): Function to be named @@ -41,8 +43,9 @@ def get_handler_name(handler): return handler.__name__ -def create_wrapper(handler, skill_id, on_start, on_end, on_error): - """Create the default skill handler wrapper. +def create_wrapper(handler, skill_id, on_start, on_end, on_error) -> callable: + """ + Create the default skill handler wrapper. This wrapper handles things like metrics, reporting handler start/stop and errors. @@ -77,7 +80,7 @@ def wrapper(message): return wrapper -def create_basic_wrapper(handler, on_error=None): +def create_basic_wrapper(handler, on_error=None) -> callable: """Create the default skill handler wrapper. This wrapper handles things like metrics, reporting handler start/stop diff --git a/ovos_utils/file_utils.py b/ovos_utils/file_utils.py index 26e5aab0..f64f5e17 100644 --- a/ovos_utils/file_utils.py +++ b/ovos_utils/file_utils.py @@ -1,22 +1,26 @@ import collections import csv -import re import os -from os import walk -from os.path import splitext, join, dirname +import re import tempfile -from ovos_utils.bracket_expansion import expand_options -from ovos_utils.log import LOG -from ovos_utils.system import search_mycroft_core_location +from typing import Optional, List + import time +from os import walk from os.path import dirname +from os.path import splitext, join -from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler +from watchdog.observers import Observer + +from ovos_utils.bracket_expansion import expand_options +from ovos_utils.log import LOG +from ovos_utils.system import search_mycroft_core_location -def get_temp_path(*args): - """Generate a valid path in the system temp directory. +def get_temp_path(*args) -> str: + """ + Generate a valid path in the system temp directory. This method accepts one or more strings as arguments. The arguments are joined and returned as a complete path inside the systems temp directory. @@ -39,9 +43,13 @@ def get_temp_path(*args): return path -def get_cache_directory(folder): - # optional import to use ram for cache - # does not work in windows! +def get_cache_directory(folder: str) -> str: + """ + Get a temporary cache directory, preferably in RAM. + Note that Windows will not use RAM. + @param folder: base path to use for cache + @return: valid cache path + """ path = get_temp_path(folder) if os.name != 'nt': try: @@ -55,8 +63,9 @@ def get_cache_directory(folder): return path -def resolve_ovos_resource_file(res_name): - """Convert a resource into an absolute filename. +def resolve_ovos_resource_file(res_name: str) -> Optional[str]: + """ + Convert a resource into an absolute filename. used internally for ovos resources """ # First look for fully qualified file (e.g. a user setting) @@ -77,8 +86,10 @@ def resolve_ovos_resource_file(res_name): return None # Resource cannot be resolved -def resolve_resource_file(res_name, root_path=None, config=None): - """Convert a resource into an absolute filename. +def resolve_resource_file(res_name: str, root_path: Optional[str] = None, + config: dict = None) -> Optional[str]: + """ + Convert a resource into an absolute filename. Resource names are in the form: 'filename.ext' or 'path/filename.ext' @@ -98,13 +109,20 @@ def resolve_resource_file(res_name, root_path=None, config=None): Args: res_name (str): a resource path/name + root_path: Optional root path to check config (dict): mycroft.conf, to read data directory from Returns: str: path to resource or None if no resource found """ if config is None: - from ovos_config.config import Configuration - config = Configuration() + LOG.warning(f"Expected a dict config and got None. This config" + f"fallback behavior will be deprecated in a future release") + try: + from ovos_config.config import read_mycroft_config + config = read_mycroft_config() + except ImportError: + LOG.warning("Config not provided and ovos_config not available") + config = dict() # First look for fully qualified file (e.g. a user setting) if os.path.isfile(res_name): @@ -129,18 +147,19 @@ def resolve_resource_file(res_name, root_path=None, config=None): return None # Resource cannot be resolved -def read_vocab_file(path): - """ Read voc file. +def read_vocab_file(path: str) -> List[List[str]]: + """ + Read voc file. - This reads a .voc file, stripping out empty lines comments and expand - parentheses. It returns each line as a list of all expanded - alternatives. + This reads a .voc file, stripping out empty lines comments and expand + parentheses. It returns each line as a list of all expanded + alternatives. - Args: - path (str): path to vocab file. + Args: + path (str): path to vocab file. - Returns: - List of Lists of strings. + Returns: + List of Lists of strings. """ vocab = [] with open(path, 'r', encoding='utf8') as voc_file: @@ -151,8 +170,9 @@ def read_vocab_file(path): return vocab -def load_regex_from_file(path, skill_id): - """Load regex from file +def load_regex_from_file(path: str, skill_id: str) -> List[str]: + """ + Load regex from file The regex is sent to the intent handler using the message bus Args: @@ -180,8 +200,9 @@ def load_regex_from_file(path, skill_id): return regexes -def load_vocabulary(basedir, skill_id): - """Load vocabulary from all files in the specified directory. +def load_vocabulary(basedir: str, skill_id: str) -> dict: + """ + Load vocabulary from all files in the specified directory. Args: basedir (str): path of directory to load from (will recurse) @@ -202,13 +223,12 @@ def load_vocabulary(basedir, skill_id): return vocabs -def load_regex(basedir, skill_id): - """Load regex from all files in the specified directory. +def load_regex(basedir: str, skill_id: str) -> List[List[str]]: + """ + Load regex from all files in the specified directory. Args: basedir (str): path of directory to load from - bus (messagebus emitter): messagebus instance used to send the vocab to - the intent service skill_id (str): skill identifier """ regexes = [] @@ -219,8 +239,9 @@ def load_regex(basedir, skill_id): return regexes -def read_value_file(filename, delim): - """Read value file. +def read_value_file(filename: str, delim: str) -> collections.OrderedDict: + """ + Read value file. The value file is a simple csv structure with a key and value. @@ -247,8 +268,9 @@ def read_value_file(filename, delim): return result -def read_translated_file(filename, data): - """Read a file inserting data. +def read_translated_file(filename: str, data: dict) -> Optional[List[str]]: + """ + Read a file inserting data. Args: filename (str): file to read diff --git a/ovos_utils/fingerprinting.py b/ovos_utils/fingerprinting.py index 63a64fd1..83f4e468 100644 --- a/ovos_utils/fingerprinting.py +++ b/ovos_utils/fingerprinting.py @@ -2,6 +2,7 @@ import socket from enum import Enum from os.path import join, isfile +from ovos_utils.log import LOG from ovos_utils.system import is_installed, is_running_from_module, has_screen, \ get_desktop_environment, search_mycroft_core_location, is_process_running @@ -21,14 +22,22 @@ class MycroftPlatform(str, Enum): def detect_platform(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") return max(((k, v) for k, v in classify_fingerprint().items()), key=lambda k: k[1])[0] def get_config_fingerprint(config=None): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") if not config: - from ovos_config.config import Configuration - config = Configuration() + try: + from ovos_config.config import read_mycroft_config + config = read_mycroft_config() + except ImportError: + LOG.warning("Config not provided and ovos_config not available") + config = dict() conf = config listener_conf = conf.get("listener", {}) skills_conf = conf.get("skills", {}) @@ -46,6 +55,8 @@ def get_config_fingerprint(config=None): def get_platform_fingerprint(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") return { "hostname": socket.gethostname(), "platform": platform.platform(), @@ -73,16 +84,22 @@ def get_platform_fingerprint(): def get_fingerprint(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") finger = get_platform_fingerprint() finger["configuration"] = get_config_fingerprint() return finger def core_supports_xdg(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") return True # no longer optional def get_mycroft_version(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") try: # ovos from mycroft.version import OVOS_VERSION_STR return OVOS_VERSION_STR @@ -123,6 +140,8 @@ def get_mycroft_version(): def is_chatterbox_core(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") try: import chatterbox return True @@ -131,6 +150,8 @@ def is_chatterbox_core(): def is_neon_core(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") try: import neon_core return True @@ -139,29 +160,42 @@ def is_neon_core(): def is_mycroft_core(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") try: import mycroft return True except ImportError: return False + def is_vanilla_mycroft_core(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") return is_mycroft_core() and not is_ovos() def is_holmes(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") return "HolmesV" in (get_mycroft_version() or "") or is_mycroft_lib() def is_mycroft_lib(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") return "mycroft-lib" in (get_mycroft_version() or "") def is_ovos(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") return is_running_from_module("ovos-core") def classify_platform_print(fingerprint=None): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") fingerprint = fingerprint or get_platform_fingerprint() # key, val pairs that indicate a certain platform fingerprints = { @@ -321,6 +355,8 @@ def classify_platform_print(fingerprint=None): def classify_config_print(fingerprint=None): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") fingerprint = fingerprint or get_config_fingerprint() # key, val pairs that indicate a certain platform @@ -441,6 +477,8 @@ def classify_config_print(fingerprint=None): def classify_fingerprint(): + LOG.warning("fingerprinting utils are deprecated. This submodule " + "will be removed in ovos_utils 0.1.0") plat = classify_platform_print() conf = classify_config_print() for k, v in conf.items(): diff --git a/ovos_utils/gui.py b/ovos_utils/gui.py index 7301f1bf..954ad33b 100644 --- a/ovos_utils/gui.py +++ b/ovos_utils/gui.py @@ -1,37 +1,55 @@ +from typing import List, Union + import time from collections import namedtuple from enum import IntEnum from os.path import join +# from ovos_bus_client import MessageBusClient from ovos_utils import resolve_ovos_resource_file, resolve_resource_file from ovos_utils.log import LOG from ovos_utils.messagebus import wait_for_reply, get_mycroft_bus, Message from ovos_utils.system import is_installed, has_screen, is_process_running -from ovos_config import Configuration -def can_display(): - return has_screen() +_default_gui_apps = ( + "mycroft-gui-app", + "ovos-shell", + "mycroft-embedded-shell", + "plasmashell" +) -def is_gui_installed(): - return is_installed("mycroft-gui-app") or \ - is_installed("ovos-shell") or \ - is_installed("mycroft-embedded-shell") or \ - is_installed("plasmashell") +def can_display() -> bool: + """ + Return true if a display is available + """ + return bool(has_screen()) -def is_gui_running(): - return is_process_running("mycroft-gui-app") or \ - is_process_running("ovos-shell") or \ - is_process_running("mycroft-embedded-shell") or \ - is_process_running("plasmashell") +def is_gui_installed(applications: List[str] = _default_gui_apps) -> bool: + """ + Return true if a GUI application is installed + @param applications: list of applications to check for + """ + return any((is_installed(app) for app in applications)) + + +def is_gui_running(applications: List[str] = _default_gui_apps) -> bool: + """ + Return true if a GUI application is running + @param applications: list of applications to check for + """ + return any((is_process_running(app) for app in applications)) -def is_gui_connected(bus=None): - # bus api for https://github.com/MycroftAI/mycroft-core/pull/2682 - # send "gui.status.request" - # receive "gui.status.request.response" +def is_gui_connected(bus=None) -> bool: + """ + Check if a GUI is connected to the MessageBus. + sends "gui.status.request" and waits for "gui.status.request.response" + @param bus: MessageBusClient to use for query + @return: True if GUI is connected + """ response = wait_for_reply("gui.status.request", "gui.status.request.response", bus=bus) if response: @@ -39,24 +57,44 @@ def is_gui_connected(bus=None): return False -def can_use_local_gui(): +def can_use_local_gui() -> bool: + """ + Returns True if a local GUI is installed and running (does not check if the + GUI is connected to an accessible GUI service). + """ if can_display() and is_gui_installed() and is_gui_running(): return True return False -def can_use_gui(bus=None, local=False): +def can_use_gui(bus=None, + local: bool = False) -> bool: + """ + Check if a GUI is available to connect to + @param bus: MessageBusClient to use for query + @param local: If True, only check for a GUI on the local host + @return: True if a GUI is available + """ if local: return can_use_local_gui() return can_use_local_gui() or is_gui_connected(bus) -def extend_about_data(about_data, bus=None): + +def extend_about_data(about_data: Union[list, dict], + bus=None): + """ + Add more information to the "About" section in the GUI. + @param about_data: list of dict key, val information to add to the GUI + @param bus: MessageBusClient object to emit update on + """ bus = bus or get_mycroft_bus() if isinstance(about_data, list): - bus.emit(Message("smartspeaker.extension.extend.about", {"display_list": about_data})) + bus.emit(Message("smartspeaker.extension.extend.about", + {"display_list": about_data})) elif isinstance(about_data, dict): display_list = [about_data] - bus.emit(Message("smartspeaker.extension.extend.about", {"display_list": display_list})) + bus.emit(Message("smartspeaker.extension.extend.about", + {"display_list": display_list})) else: LOG.error("about_data is not a list or dictionary") @@ -436,8 +474,10 @@ def _on_show_idle(self, message): class _GUIDict(dict): - """ this is an helper dictionay subclass, it ensures that value changed - in it are propagated to the GUI service real time""" + """ + This is a helper dictionary subclass. It ensures that values changed + in it are propagated to the GUI service in real time. + """ def __init__(self, gui, **kwargs): self.gui = gui super().__init__(**kwargs) @@ -461,7 +501,17 @@ class GUIInterface: """ def __init__(self, skill_id, bus=None, remote_server=None, config=None): - self.config = config or Configuration().get("gui", {}) + if not config: + LOG.warning(f"Expected a dict config and got None. This config" + f"fallback behavior will be deprecated in a future " + f"release") + try: + from ovos_config.config import read_mycroft_config + config = read_mycroft_config().get("gui", {}) + except ImportError: + LOG.warning("Config not provided and ovos_config not available") + config = dict() + self.config = config if remote_server: self.config["remote-server"] = remote_server self._bus = bus @@ -914,6 +964,32 @@ def show_url(self, url, override_idle=None, self.show_page("SYSTEM_UrlFrame.qml", override_idle, override_animations) + def show_input_box(self, title=None, placeholder=None, + confirm_text=None, exit_text=None, + override_idle=None, override_animations=None): + self["title"] = title + self["placeholder"] = placeholder + self["skill_id_handler"] = self.skill_id + if not confirm_text: + self["confirm_text"] = "Confirm" + else: + self["confirm_text"] = confirm_text + + if not exit_text: + self["exit_text"] = "Exit" + else: + self["exit_text"] = exit_text + + self.show_page("SYSTEM_InputBox.qml", override_idle, + override_animations) + + def remove_input_box(self): + LOG.info(f"GUI pages length {len(self.pages)}") + if len(self.pages) > 1: + self.remove_page("SYSTEM_InputBox.qml") + else: + self.release() + def release(self): """Signal that this skill is no longer using the GUI, allow different platforms to properly handle this event. @@ -934,11 +1010,3 @@ def shutdown(self): self.release() for event, handler in self._events: self.bus.remove(event, handler) - - -if __name__ == "__main__": - from ovos_utils import wait_for_exit_signal - - LOG.set_level("DEBUG") - g = GUITracker() - wait_for_exit_signal() diff --git a/ovos_utils/intents/converse.py b/ovos_utils/intents/converse.py index 79705859..0d232cb1 100644 --- a/ovos_utils/intents/converse.py +++ b/ovos_utils/intents/converse.py @@ -2,7 +2,7 @@ from ovos_utils.intents.intent_service_interface import IntentQueryApi from ovos_utils.log import LOG -from ovos_utils.messagebus import Message +from ovos_utils.messagebus import FakeMessage as Message class ConverseTracker: diff --git a/ovos_utils/intents/intent_service_interface.py b/ovos_utils/intents/intent_service_interface.py index e5407202..9baa9cd0 100644 --- a/ovos_utils/intents/intent_service_interface.py +++ b/ovos_utils/intents/intent_service_interface.py @@ -1,16 +1,14 @@ from os.path import exists, isfile -from mycroft_bus_client import MessageBusClient -from mycroft_bus_client.message import Message, dig_for_message -from ovos_utils import create_daemon +from ovos_utils.messagebus import get_mycroft_bus, Message, dig_for_message from ovos_utils.log import LOG -def to_alnum(skill_id): - """Convert a skill id to only alphanumeric characters - - Non alpha-numeric characters are converted to "_" +def to_alnum(skill_id: str) -> str: + """ + Convert a skill id to only alphanumeric characters + Non-alphanumeric characters are converted to "_" Args: skill_id (str): identifier to be converted @@ -20,8 +18,9 @@ def to_alnum(skill_id): return ''.join(c if c.isalnum() else '_' for c in str(skill_id)) -def munge_regex(regex, skill_id): - """Insert skill id as letters into match groups. +def munge_regex(regex: str, skill_id: str) -> str: + """ + Insert skill id as letters into match groups. Args: regex (str): regex string @@ -34,7 +33,8 @@ def munge_regex(regex, skill_id): def munge_intent_parser(intent_parser, name, skill_id): - """Rename intent keywords to make them skill exclusive + """ + Rename intent keywords to make them skill exclusive This gives the intent parser an exclusive name in the format :. The keywords are given unique names in the format . @@ -327,8 +327,7 @@ class IntentQueryApi: def __init__(self, bus=None, timeout=5): if bus is None: - bus = MessageBusClient() - create_daemon(bus.run_forever) + bus = get_mycroft_bus() self.bus = bus self.timeout = timeout @@ -536,7 +535,9 @@ def get_keywords_manifest(self): def open_intent_envelope(message): - """Convert dictionary received over messagebus to Intent.""" + """ + Convert dictionary received over messagebus to Intent. + """ # TODO can this method be fully removed from ovos_utils ? from adapt.intent import Intent diff --git a/ovos_utils/intents/layers.py b/ovos_utils/intents/layers.py index feabdd59..7e7bfb76 100644 --- a/ovos_utils/intents/layers.py +++ b/ovos_utils/intents/layers.py @@ -1,12 +1,12 @@ -from ovos_utils.messagebus import Message, get_mycroft_bus +from ovos_utils.messagebus import get_mycroft_bus, FakeMessage as Message from ovos_utils.log import LOG from time import sleep -LOG.error(f"This module is deprecated, import from `ovos_workshop.skills.layers") - class IntentLayers: def __init__(self, bus=None, layers=None): + # TODO: Deprecate in 0.1.0 + LOG.error(f"This module is deprecated, import from `ovos_workshop.skills.layers") layers = layers or [] self.bus = bus or get_mycroft_bus() # make intent levels for N layers diff --git a/ovos_utils/json_helper.py b/ovos_utils/json_helper.py index b2f18a35..c54ba9b5 100644 --- a/ovos_utils/json_helper.py +++ b/ovos_utils/json_helper.py @@ -1,5 +1,6 @@ import json from copy import copy +# TODO: Deprecate unused imports from json_database.utils import is_jsonifiable, get_key_recursively, \ get_key_recursively_fuzzy, get_value_recursively_fuzzy, \ get_value_recursively, jsonify_recursively diff --git a/ovos_utils/log.py b/ovos_utils/log.py index a3c7ba5b..83dcc171 100644 --- a/ovos_utils/log.py +++ b/ovos_utils/log.py @@ -17,8 +17,6 @@ from logging.handlers import RotatingFileHandler from os.path import join -from mycroft_bus_client.message import dig_for_message - class LOG: """ @@ -50,16 +48,20 @@ def __init__(cls, name='OVOS'): @classmethod def init(cls, config=None): - from ovos_config.meta import get_xdg_base + try: + from ovos_config.meta import get_xdg_base + default_base = get_xdg_base() + except ImportError: + default_base = "mycroft" from ovos_utils.xdg_utils import xdg_state_home config = config or {} - cls.base_path = config.get("path") or f"{xdg_state_home()}/{get_xdg_base()}" + cls.base_path = config.get("path") or \ + f"{xdg_state_home()}/{default_base}" cls.max_bytes = config.get("max_bytes", 50000000) cls.backup_count = config.get("backup_count", 3) cls.level = config.get("level", "INFO") cls.diagnostic_mode = config.get("diagnostic", False) - os.makedirs(cls.base_path, exist_ok=True) @classmethod def create_logger(cls, name, tostdout=True): @@ -74,6 +76,7 @@ def create_logger(cls, name, tostdout=True): logger.addHandler(stdout_handler) # log to file if cls.base_path != "stdout": + os.makedirs(cls.base_path, exist_ok=True) path = join(cls.base_path, cls.name.lower().strip() + ".log") handler = RotatingFileHandler(path, maxBytes=cls.max_bytes, @@ -115,9 +118,13 @@ def _get_real_logger(cls): logger = cls.create_logger(name, tostdout=True) if cls.diagnostic_mode: - msg = dig_for_message() - if msg: - logger.debug(f"DIAGNOSTIC - source bus message {msg.serialize()}") + try: + from ovos_bus_client.message import dig_for_message + msg = dig_for_message() + if msg: + logger.debug(f"DIAGNOSTIC - source bus message {msg.serialize()}") + except ImportError: + pass return logger @classmethod @@ -145,9 +152,12 @@ def init_service_logger(service_name): # this is makes all logs from this service be configured to write to service_name.log file # if this is not called in every __main__.py entrypoint logs will be written # to a generic OVOS.log file shared across all services - from ovos_config import Configuration - - _cfg = Configuration() + try: + from ovos_config.config import read_mycroft_config + _cfg = read_mycroft_config() + except ImportError: + LOG.warning("ovos_config not available. Falling back to defaults") + _cfg = dict() _log_level = _cfg.get("log_level", "INFO") _logs_conf = _cfg.get("logs") or {} _logs_conf["level"] = _log_level diff --git a/ovos_utils/messagebus.py b/ovos_utils/messagebus.py index 03e284b4..c1339663 100644 --- a/ovos_utils/messagebus.py +++ b/ovos_utils/messagebus.py @@ -1,12 +1,10 @@ import json import time +from copy import deepcopy from inspect import signature from threading import Event -from mycroft_bus_client import MessageBusClient -from mycroft_bus_client.message import dig_for_message, Message -from ovos_config.config import Configuration -from ovos_config.locale import get_default_lang +# from ovos_utils.configuration import read_mycroft_config, get_default_lang from pyee import BaseEventEmitter from ovos_utils import create_loop @@ -20,6 +18,15 @@ "ssl": False} +def dig_for_message(): + try: + from ovos_bus_client.message import dig_for_message as _dig + return _dig() + except ImportError: + pass + return None + + class FakeBus: def __init__(self, *args, **kwargs): self.started_running = False @@ -120,6 +127,190 @@ def close(self): self.on_close() +class _MutableMessage(type): + """ To override isinstance checks we need to use a metaclass """ + + def __instancecheck__(self, instance): + try: + from ovos_bus_client.message import Message as _MycroftMessage + if isinstance(instance, _MycroftMessage): + return True + except: + pass + try: + from mycroft_bus_client.message import Message as _MycroftMessage + if isinstance(instance, _MycroftMessage): + return True + except: + pass + return super().__instancecheck__(instance) + + +# fake Message object to allow usage without ovos-bus-client installed +class FakeMessage(metaclass=_MutableMessage): + """ fake Message object to allow usage with FakeBus without ovos-bus-client installed""" + + def __new__(cls, *args, **kwargs): + try: # most common case + from ovos_bus_client import Message as _M + return _M(*args, **kwargs) + except: + pass + try: # some old install that upgraded during migration period + from mycroft_bus_client import Message as _M + return _M(*args, **kwargs) + except: # FakeMessage + return super().__new__(cls, *args, **kwargs) + + def __init__(self, msg_type, data=None, context=None): + """Used to construct a message object + + Message objects will be used to send information back and forth + between processes of mycroft service, voice, skill and cli + """ + self.msg_type = msg_type + self.data = data or {} + self.context = context or {} + + def __eq__(self, other): + try: + return other.msg_type == self.msg_type and \ + other.data == self.data and \ + other.context == self.context + except: + return False + + def serialize(self): + """This returns a string of the message info. + + This makes it easy to send over a websocket. This uses + json dumps to generate the string with type, data and context + + Returns: + str: a json string representation of the message. + """ + return json.dumps({'type': self.msg_type, + 'data': self.data, + 'context': self.context}) + + @staticmethod + def deserialize(value): + """This takes a string and constructs a message object. + + This makes it easy to take strings from the websocket and create + a message object. This uses json loads to get the info and generate + the message object. + + Args: + value(str): This is the json string received from the websocket + + Returns: + FakeMessage: message object constructed from the json string passed + int the function. + value(str): This is the string received from the websocket + """ + obj = json.loads(value) + return FakeMessage(obj.get('type') or '', + obj.get('data') or {}, + obj.get('context') or {}) + + def forward(self, msg_type, data=None): + """ Keep context and forward message + + This will take the same parameters as a message object but use + the current message object as a reference. It will copy the context + from the existing message object. + + Args: + msg_type (str): type of message + data (dict): data for message + + Returns: + FakeMessage: Message object to be used on the reply to the message + """ + data = data or {} + return FakeMessage(msg_type, data, context=self.context) + + def reply(self, msg_type, data=None, context=None): + """Construct a reply message for a given message + + This will take the same parameters as a message object but use + the current message object as a reference. It will copy the context + from the existing message object and add any context passed in to + the function. Check for a destination passed in to the function from + the data object and add that to the context as a destination. If the + context has a source then that will be swapped with the destination + in the context. The new message will then have data passed in plus the + new context generated. + + Args: + msg_type (str): type of message + data (dict): data for message + context: intended context for new message + + Returns: + FakeMessage: Message object to be used on the reply to the message + """ + data = deepcopy(data) or {} + context = context or {} + + new_context = deepcopy(self.context) + for key in context: + new_context[key] = context[key] + if 'destination' in data: + new_context['destination'] = data['destination'] + if 'source' in new_context and 'destination' in new_context: + s = new_context['destination'] + new_context['destination'] = new_context['source'] + new_context['source'] = s + return FakeMessage(msg_type, data, context=new_context) + + def response(self, data=None, context=None): + """Construct a response message for the message + + Constructs a reply with the data and appends the expected + ".response" to the message + + Args: + data (dict): message data + context (dict): message context + Returns + (Message) message with the type modified to match default response + """ + return self.reply(self.msg_type + '.response', data, context) + + def publish(self, msg_type, data, context=None): + """ + Copy the original context and add passed in context. Delete + any target in the new context. Return a new message object with + passed in data and new context. Type remains unchanged. + + Args: + msg_type (str): type of message + data (dict): date to send with message + context: context added to existing context + + Returns: + FakeMessage: Message object to publish + """ + context = context or {} + new_context = self.context.copy() + for key in context: + new_context[key] = context[key] + + if 'target' in new_context: + del new_context['target'] + + return FakeMessage(msg_type, data, context=new_context) + + +class Message(FakeMessage): + def __int__(self, *args, **kwargs): + LOG.warning(f"This reference is deprecated, import from " + f"`ovos_bus_client.message` directly") + FakeMessage.__init__(self, *args, **kwargs) + + def get_message_lang(message=None): """Get the language from the message or the default language. Args: @@ -127,8 +318,13 @@ def get_message_lang(message=None): Returns: The language code from the message or the default language. """ + try: + from ovos_config.locale import get_default_lang + default_lang = get_default_lang() + except ImportError: + LOG.warning("ovos_config not available. Using default lang en-us") + default_lang = "en-us" message = message or dig_for_message() - default_lang = get_default_lang() if not message: return default_lang lang = message.data.get("lang") or message.context.get("lang") or default_lang @@ -139,6 +335,8 @@ def get_websocket(host, port, route='/', ssl=False, threaded=True): """ Returns a connection to a websocket """ + from ovos_bus_client import MessageBusClient + client = MessageBusClient(host, port, route, ssl) if threaded: client.run_in_thread() @@ -150,7 +348,12 @@ def get_mycroft_bus(host: str = None, port: int = None, route: str = None, """ Returns a connection to the mycroft messagebus """ - config = Configuration().get('websocket') or dict() + try: + from ovos_config.config import read_mycroft_config + config = read_mycroft_config().get('websocket') or dict() + except ImportError: + LOG.warning("ovos_config not available. Falling back to default WS") + config = dict() host = host or config.get('host') or _DEFAULT_WS_CONFIG['host'] port = port or config.get('port') or _DEFAULT_WS_CONFIG['port'] route = route or config.get('route') or _DEFAULT_WS_CONFIG['route'] @@ -191,7 +394,7 @@ def wait_for_reply(message, reply_type=None, timeout=3.0, bus=None): """Send a message and wait for a response. Args: - message (Message or str or dict): message object or type to send + message (FakeMessage or str or dict): message object or type to send reply_type (str): the message type of the expected reply. Defaults to ".response". timeout: seconds to wait before timeout, defaults to 3 @@ -206,12 +409,12 @@ def wait_for_reply(message, reply_type=None, timeout=3.0, bus=None): except: pass if isinstance(message, str): - message = Message(message) + message = FakeMessage(message) elif isinstance(message, dict): - message = Message(message["type"], - message.get("data"), - message.get("context")) - elif not isinstance(message, Message): + message = FakeMessage(message["type"], + message.get("data"), + message.get("context")) + elif not isinstance(message, FakeMessage): raise ValueError response = bus.wait_for_response(message, reply_type, timeout) if auto_close: @@ -224,17 +427,17 @@ def send_message(message, data=None, context=None, bus=None): bus = bus or get_mycroft_bus() if isinstance(message, str): if isinstance(data, dict) or isinstance(context, dict): - message = Message(message, data, context) + message = FakeMessage(message, data, context) else: try: message = json.loads(message) except: - message = Message(message) + message = FakeMessage(message) if isinstance(message, dict): - message = Message(message["type"], - message.get("data"), - message.get("context")) - if not isinstance(message, Message): + message = FakeMessage(message["type"], + message.get("data"), + message.get("context")) + if not isinstance(message, FakeMessage): raise ValueError bus.emit(message) if auto_close: @@ -294,7 +497,7 @@ def to_alnum(skill_id): def unmunge_message(message, skill_id): """Restore message keywords by removing the Letterified skill ID. Args: - message (Message): Intent result message + message (FakeMessage): Intent result message skill_id (str): skill identifier Returns: Message without clear keywords @@ -552,7 +755,17 @@ def __init__(self, trigger_message, name=None, bus=None, config=None): name(str): name identifier for .conf settings bus (WebsocketClient): mycroft messagebus websocket """ - config = config or Configuration() + if not config: + LOG.warning(f"Expected a dict config and got None. This config" + f"fallback behavior will be deprecated in a future " + f"release") + try: + from ovos_config.config import read_mycroft_config + config = read_mycroft_config() + except ImportError: + LOG.warning("ovos_config not available. Falling back to " + "default configuration") + config = dict() self.trigger_message = trigger_message self.name = name or self.__class__.__name__ self.bus = bus or get_mycroft_bus() @@ -572,13 +785,14 @@ def set_data_gatherer(self, callback, default_data=None, daemonic=False, interva """ prepare responder for sending, register answers """ + self.bus.remove_all_listeners(self.trigger_message) if ".request" in self.trigger_message: response_type = self.trigger_message.replace(".request", ".reply") else: response_type = self.trigger_message + ".reply" - response = Message(response_type, default_data) + response = FakeMessage(response_type, default_data) self.service = BusService(response, bus=self.bus) self.callback = callback self.bus.on(self.trigger_message, self._respond) @@ -625,7 +839,7 @@ class BusQuery: def __init__(self, message, bus=None): self.bus = bus or get_mycroft_bus() self._waiting = False - self.response = Message(None, None, None) + self.response = FakeMessage(None, None, None) self.query = message self.valid_response_types = [] @@ -649,7 +863,7 @@ def _wait_response(self, timeout): self._waiting = False def send(self, response_type=None, timeout=10): - self.response = Message(None, None, None) + self.response = FakeMessage(None, None, None) if response_type is None: response_type = self.query.type + ".reply" self.add_response_type(response_type) @@ -694,7 +908,13 @@ def __init__(self, query_message, name=None, timeout=5, bus=None, self.query_message.context["source"] = self.name self.name = name or self.__class__.__name__ self.bus = bus or get_mycroft_bus() - config = config or Configuration() + if not config: + try: + from ovos_config.config import read_mycroft_config + config = read_mycroft_config() + except ImportError: + LOG.warning("Config not provided and ovos_config not available") + config = dict() self.config = config.get(self.name, {}) self.timeout = timeout self.query = None diff --git a/ovos_utils/network_utils.py b/ovos_utils/network_utils.py index ecac01f3..b1a3e1da 100644 --- a/ovos_utils/network_utils.py +++ b/ovos_utils/network_utils.py @@ -1,10 +1,8 @@ import socket +from typing import Optional import requests -# backwards compat - was only out there for a couple alpha version -# TODO - remove next stable -from ovos_utils.process_utils import RuntimeRequirements as NetworkRequirements from ovos_utils.log import LOG @@ -22,13 +20,20 @@ def get_network_tests_config(): """Get network_tests object from mycroft.configuration.""" - from ovos_config import Configuration - config = Configuration() + try: + from ovos_config.config import read_mycroft_config + config = read_mycroft_config() + except ImportError: + LOG.warning("ovos_config not available. Falling back to default config") + config = dict() return config.get("network_tests", _DEFAULT_TEST_CONFIG) -def get_ip(): - # taken from https://stackoverflow.com/a/28950776/13703283 +def get_ip() -> str: + """ + Get the local IPv4 address of this device + taken from https://stackoverflow.com/a/28950776/13703283 + """ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: # doesn't even have to be reachable @@ -42,12 +47,18 @@ def get_ip(): def get_external_ip(): + """ + Get the public IPv4 address of this device + """ cfg = get_network_tests_config() - return requests.get(cfg.get("ip_url") or _DEFAULT_TEST_CONFIG['ip_url']).text + return requests.get(cfg.get("ip_url") or + _DEFAULT_TEST_CONFIG['ip_url']).text -def is_connected_dns(host=None, port=53, timeout=3): - """Check internet connection by connecting to DNS servers +def is_connected_dns(host: Optional[str] = None, port: int = 53, + timeout: int = 3) -> bool: + """ + Check internet connection by connecting to DNS servers Returns: True if internet connection can be detected """ @@ -68,15 +79,18 @@ def is_connected_dns(host=None, port=53, timeout=3): return False -def is_connected_http(host=None): - """Check internet connection by connecting to some website +def is_connected_http(host: Optional[str] = None) -> bool: + """ + Check internet connection by connecting to some website Returns: True if connection attempt succeeded """ if host is None: cfg = get_network_tests_config() - return is_connected_http(cfg.get("web_url") or _DEFAULT_TEST_CONFIG['web_url']) or \ - is_connected_http(cfg.get("web_url_secondary") or _DEFAULT_TEST_CONFIG['web_url_secondary']) + return is_connected_http(cfg.get("web_url") or + _DEFAULT_TEST_CONFIG['web_url']) or \ + is_connected_http(cfg.get("web_url_secondary") or + _DEFAULT_TEST_CONFIG['web_url_secondary']) try: status = requests.head(host).status_code @@ -86,12 +100,18 @@ def is_connected_http(host=None): return False -def is_connected(): +def is_connected() -> bool: + """ + Return True if any connection check (DNS or HTTP) is True + """ return any((is_connected_dns(), is_connected_http())) -def check_captive_portal(host=None, expected_text=None) -> bool: - """Returns True if a captive portal page is detected""" +def check_captive_portal(host: Optional[str] = None, + expected_text: Optional[str] = None) -> bool: + """ + Returns True if a captive portal page is detected + """ captive_portal = False if not host or not expected_text: diff --git a/ovos_utils/ovos_service_api.py b/ovos_utils/ovos_service_api.py index daaa6575..1552645a 100644 --- a/ovos_utils/ovos_service_api.py +++ b/ovos_utils/ovos_service_api.py @@ -1,10 +1,14 @@ import requests from json_database import JsonStorageXDG +from ovos_utils.log import LOG +# TODO: This will be deprecated in v0.1 class OVOSApiService: def __init__(self) -> None: + LOG.warning(f"ovos_utils.ovos_service_api is deprecated. " + f"Import from ovos-backend-client") self.uuid_storage = JsonStorageXDG("ovos_api_uuid") self.token_storage = JsonStorageXDG("ovos_api_token") @@ -172,8 +176,11 @@ def search_movie(self, query): r = requests.post(url, data=reqdata, headers=self.headers) return r.json() + class OvosGeolocate: def __init__(self): + LOG.warning(f"This reference is deprecated. " + f"Import from ovos-backend-client") pass def geolocate_ip(self, ip): @@ -194,6 +201,7 @@ def geolocate_location_config(self, address): r = requests.post(url, data=reqdata) return r.json() + class OvosSendMail: def __init__(self): self.api = OVOSApiService() diff --git a/ovos_utils/process_utils.py b/ovos_utils/process_utils.py index 876a972b..7cda68a8 100644 --- a/ovos_utils/process_utils.py +++ b/ovos_utils/process_utils.py @@ -24,6 +24,7 @@ from ovos_utils.log import LOG +from ovos_utils.file_utils import get_temp_path @dataclass @@ -291,14 +292,18 @@ class PIDLock: # python 3+ 'class Lock' """ @classmethod def init(cls): - from ovos_config.meta import get_xdg_base - from ovos_utils.file_utils import get_temp_path - cls.DIRECTORY = cls.DIRECTORY or get_temp_path(get_xdg_base()) + try: + from ovos_config.meta import get_xdg_base + base_dir = get_xdg_base() + except ImportError: + LOG.warning("ovos-config not available, using default " + "'mycroft' basedir") + base_dir = "mycroft" + cls.DIRECTORY = cls.DIRECTORY or get_temp_path(base_dir) # # Class constants DIRECTORY = None FILE = '/{}.pid' - LOG.info(f"Create PIDLock in: {DIRECTORY}") # # Constructor diff --git a/ovos_utils/scripts.py b/ovos_utils/scripts.py deleted file mode 100644 index fa693ae9..00000000 --- a/ovos_utils/scripts.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -# each method here is a console_script defined in setup.py -# each corresponds to a cli util -from mycroft_bus_client import MessageBusClient, Message -from ovos_config import Configuration -import sys - - -def ovos_speak(): - if (args_count := len(sys.argv)) == 2: - utt = sys.argv[1] - lang = Configuration().get("lang", "en-us") - elif args_count == 3: - utt = sys.argv[1] - lang = sys.argv[2] - else: - print("USAGE: ovos-speak {utterance} [lang]") - raise SystemExit(2) - client = MessageBusClient() - client.run_in_thread() - client.emit(Message("speak", {"utterance": utt, "lang": lang})) - client.close() - - -def ovos_say_to(): - if (args_count := len(sys.argv)) == 2: - utt = sys.argv[1] - lang = Configuration().get("lang", "en-us") - elif args_count == 3: - utt = sys.argv[1] - lang = sys.argv[2] - else: - print("USAGE: ovos-say-to {utterance} [lang]") - raise SystemExit(2) - client = MessageBusClient() - client.run_in_thread() - client.emit(Message("recognizer_loop:utterance", {"utterances": [utt], "lang": lang})) - client.close() - - -def ovos_listen(): - client = MessageBusClient() - client.run_in_thread() - client.emit(Message("mycroft.mic.listen")) - client.close() diff --git a/ovos_utils/signal.py b/ovos_utils/signal.py index e08789c7..3bb97da6 100644 --- a/ovos_utils/signal.py +++ b/ovos_utils/signal.py @@ -23,8 +23,12 @@ def get_ipc_directory(domain=None, config=None): str: a path to the IPC directory """ if config is None: - from ovos_config.config import Configuration - config = Configuration() + try: + from ovos_config.config import read_mycroft_config + config = read_mycroft_config() + except ImportError: + LOG.warning("Config not provided and ovos_config not available") + config = dict() path = config.get("ipc_path") if not path: # If not defined, use /tmp/mycroft/ipc diff --git a/ovos_utils/skills/__init__.py b/ovos_utils/skills/__init__.py index 3ad39830..5d71ad1d 100644 --- a/ovos_utils/skills/__init__.py +++ b/ovos_utils/skills/__init__.py @@ -1,4 +1,4 @@ -from ovos_config.config import Configuration, update_mycroft_config +from ovos_config.config import read_mycroft_config, update_mycroft_config from ovos_utils.messagebus import wait_for_reply from ovos_utils.skills.locations import get_default_skills_directory, get_installed_skill_ids from ovos_utils.log import LOG @@ -39,7 +39,7 @@ def skills_loaded(bus=None): def blacklist_skill(skill, config=None): - config = config or Configuration() + config = config or read_mycroft_config() skills_config = config.get("skills", {}) blacklisted_skills = skills_config.get("blacklisted_skills", []) if skill not in blacklisted_skills: @@ -55,7 +55,7 @@ def blacklist_skill(skill, config=None): def whitelist_skill(skill, config=None): - config = config or Configuration() + config = config or read_mycroft_config() skills_config = config.get("skills", {}) blacklisted_skills = skills_config.get("blacklisted_skills", []) if skill in blacklisted_skills: @@ -71,18 +71,21 @@ def whitelist_skill(skill, config=None): def make_priority_skill(skill, config=None): - config = config or Configuration() - skills_config = config.get("skills", {}) - priority_skills = skills_config.get("priority_skills", []) - if skill not in priority_skills: - priority_skills.append(skill) - conf = { - "skills": { - "priority_skills": priority_skills - } - } - update_mycroft_config(conf) - return True + # TODO: Deprecate in 0.1.0 + LOG.warning("This method is deprecated. Skills are now loaded based on " + "`runtime_requirements`") + # config = config or read_mycroft_config() + # skills_config = config.get("skills", {}) + # priority_skills = skills_config.get("priority_skills", []) + # if skill not in priority_skills: + # priority_skills.append(skill) + # conf = { + # "skills": { + # "priority_skills": priority_skills + # } + # } + # update_mycroft_config(conf) + # return True return False diff --git a/ovos_utils/skills/audioservice.py b/ovos_utils/skills/audioservice.py index fb0c4089..fb658373 100644 --- a/ovos_utils/skills/audioservice.py +++ b/ovos_utils/skills/audioservice.py @@ -13,18 +13,22 @@ # limitations under the License. # +from datetime import timedelta # This file is directly copied from HolmesV, it's a simple utility to # interface with the AudioService via messagebus outside core from os.path import abspath -from datetime import timedelta -from ovos_utils.messagebus import Message, get_mycroft_bus +from ovos_utils.log import LOG +from ovos_utils.messagebus import get_mycroft_bus, dig_for_message, \ + FakeMessage as Message -def ensure_uri(s): - """Interprete paths as file:// uri's. + +def ensure_uri(s: str): + """ + Interpret paths as file:// uri's. Args: - s: string to be checked + s: string path to be checked Returns: if s is uri, s is returned otherwise file:// is prepended @@ -34,7 +38,7 @@ def ensure_uri(s): return 'file://' + abspath(s) else: return s - elif isinstance(s, (tuple, list)): + elif isinstance(s, (tuple, list)): # Handle (mime, uri) arg if '://' not in s[0]: return 'file://' + abspath(s[0]), s[1] else: @@ -43,9 +47,12 @@ def ensure_uri(s): raise ValueError('Invalid track') -class AudioServiceInterface: +class ClassicAudioServiceInterface: """AudioService class for interacting with the audio subsystem + Audio is managed by OCP in the default implementation, + usually this class should not be directly used, see OCPInterface instead + Args: bus: Mycroft messagebus connection """ @@ -208,3 +215,237 @@ def available_backends(self): def is_playing(self): """True if the audioservice is playing, else False.""" return self.track_info() != {} + + +class AudioServiceInterface(ClassicAudioServiceInterface): + """ClassicAudioService compatible class for interacting with OCP subsystem + Args: + bus: Mycroft messagebus connection + """ + + def __init__(self, bus=None): + super().__init__(bus) + LOG.warning("AudioServiceInterface has been deprecated, compatibility layer in use\n" + "please move to OCPInterface") + + @staticmethod + def _uri2meta(uri): + if isinstance(uri, list): + uri = uri[0] + try: + from ovos_ocp_files_plugin.plugin import OCPFilesMetadataExtractor + return OCPFilesMetadataExtractor.extract_metadata(uri) + except: + meta = {"uri": uri, + "skill_id": "mycroft.audio_interface", + "playback": 2, # PlaybackType.AUDIO, # TODO mime type check + "status": 33 # TrackState.QUEUED_AUDIO + } + meta["skill_id"] = "mycroft.audio_interface" + return meta + + def _format_msg(self, msg_type, msg_data=None): + # this method ensures all skills are .forward from the utterance + # that triggered the skill, this ensures proper routing and metadata + msg_data = msg_data or {} + msg = dig_for_message() + if msg: + msg = msg.forward(msg_type, msg_data) + else: + msg = Message(msg_type, msg_data) + # at this stage source == skills, lets indicate audio service took over + sauce = msg.context.get("source") + if sauce == "skills": + msg.context["source"] = "audio_service" + return msg + + # OCP bus api + def queue(self, tracks=None): + """Queue up a track to playing playlist. + TODO allow rich metadata for OCP: + - support dicts in tracks + NOTE: skills using this won't be compatible with mycroft-core ... + Args: + tracks: track uri or list of track uri's + Each track can be added as a tuple with (uri, mime) + to give a hint of the mime type to the system + """ + tracks = tracks or [] + if isinstance(tracks, (str, tuple)): + tracks = [tracks] + elif not isinstance(tracks, list): + raise ValueError + tracks = [self._uri2meta(t) for t in tracks] + msg = self._format_msg('ovos.common_play.playlist.queue', + {'tracks': tracks}) + self.bus.emit(msg) + + def play(self, tracks=None, utterance=None, repeat=None): + """Start playback. + Args: + tracks: track uri or list of track uri's + Each track can be added as a tuple with (uri, mime) + to give a hint of the mime type to the system + utterance: forward utterance for further processing by the + audio service. + repeat: if the playback should be looped + """ + repeat = repeat or False + tracks = tracks or [] + utterance = utterance or '' + if isinstance(tracks, (str, tuple)): + tracks = [tracks] + elif not isinstance(tracks, list): + raise ValueError + tracks = [self._uri2meta(t) for t in tracks] + + msg = self._format_msg('ovos.common_play.play', + {"media": tracks[0], + "playlist": tracks, + "utterance": utterance, + 'repeat': repeat}) + self.bus.emit(msg) + + def stop(self): + """Stop the track.""" + msg = self._format_msg("ovos.common_play.stop") + self.bus.emit(msg) + + def next(self): + """Change to next track.""" + msg = self._format_msg("ovos.common_play.next") + self.bus.emit(msg) + + def prev(self): + """Change to previous track.""" + msg = self._format_msg("ovos.common_play.previous") + self.bus.emit(msg) + + def pause(self): + """Pause playback.""" + msg = self._format_msg("ovos.common_play.pause") + self.bus.emit(msg) + + def resume(self): + """Resume paused playback.""" + msg = self._format_msg("ovos.common_play.resume") + self.bus.emit(msg) + + def seek_forward(self, seconds=1): + """Skip ahead X seconds. + Args: + seconds (int): number of seconds to skip + """ + if isinstance(seconds, timedelta): + seconds = seconds.total_seconds() + msg = self._format_msg('ovos.common_play.seek', + {"seconds": seconds}) + self.bus.emit(msg) + + def seek_backward(self, seconds=1): + """Rewind X seconds + Args: + seconds (int): number of seconds to rewind + """ + if isinstance(seconds, timedelta): + seconds = seconds.total_seconds() + msg = self._format_msg('ovos.common_play.seek', + {"seconds": seconds * -1}) + self.bus.emit(msg) + + def get_track_length(self): + """ + getting the duration of the audio in miliseconds + """ + length = 0 + msg = self._format_msg('ovos.common_play.get_track_length') + info = self.bus.wait_for_response(msg, timeout=1) + if info: + length = info.data.get("length", 0) + return length + + def get_track_position(self): + """ + get current position in miliseconds + """ + pos = 0 + msg = self._format_msg('ovos.common_play.get_track_position') + info = self.bus.wait_for_response(msg, timeout=1) + if info: + pos = info.data.get("position", 0) + return pos + + def set_track_position(self, miliseconds): + """Go to X position. + Arguments: + miliseconds (int): position to go to in miliseconds + """ + msg = self._format_msg('ovos.common_play.set_track_position', + {"position": miliseconds}) + self.bus.emit(msg) + + def track_info(self): + """Request information of current playing track. + Returns: + Dict with track info. + """ + msg = self._format_msg('ovos.common_play.track_info') + response = self.bus.wait_for_response(msg) + return response.data if response else {} + + def available_backends(self): + """Return available audio backends. + Returns: + dict with backend names as keys + """ + msg = self._format_msg('ovos.common_play.list_backends') + response = self.bus.wait_for_response(msg) + return response.data if response else {} + + +class OCPInterface(AudioServiceInterface): + """bus api interface for OCP subsystem + Args: + bus: Mycroft messagebus connection + """ + + def __init__(self, bus=None): + self.bus = bus or get_mycroft_bus() + + # OCP bus api + def queue(self, tracks): + """Queue up a track to OCP playing playlist. + + Args: + tracks: track dict or list of track dicts (OCP result style) + """ + + assert isinstance(tracks, list) + assert all(isinstance(t, dict) for t in tracks) + + msg = self._format_msg('ovos.common_play.playlist.queue', + {'tracks': tracks}) + self.bus.emit(msg) + + def play(self, tracks, utterance=None): + """Start playback. + Args: + tracks: track dict or list of track dicts (OCP result style) + utterance: forward utterance for further processing by OCP + + TODO handle utterance: + - allow services to register .voc files + - match utterance against vocs in OCP + - select audio service based on parsing + eg. "play X in spotify" + """ + assert isinstance(tracks, list) + assert all(isinstance(t, dict) for t in tracks) + + utterance = utterance or '' + + msg = self._format_msg('ovos.common_play.play', + {"media": tracks[0], + "playlist": tracks, + "utterance": utterance}) + self.bus.emit(msg) diff --git a/ovos_utils/skills/locations.py b/ovos_utils/skills/locations.py index 55a402d0..1ec620e7 100644 --- a/ovos_utils/skills/locations.py +++ b/ovos_utils/skills/locations.py @@ -1,8 +1,8 @@ from os.path import join, isdir, dirname, expanduser, isfile from os import makedirs, listdir from typing import List, Optional +from ovos_config.config import read_mycroft_config from ovos_config.locations import get_xdg_data_save_path, get_xdg_data_dirs -from ovos_config.config import Configuration from ovos_utils.log import LOG @@ -54,7 +54,7 @@ def get_skill_directories(conf: Optional[dict] = None) -> List[str]: # the contents of each skills directory must be individual skill folders # we are still dependent on the mycroft-core structure of skill_id/__init__.py - conf = conf or Configuration() + conf = conf or read_mycroft_config() # load all valid XDG paths # NOTE: skills are actually code, but treated as user data! @@ -101,7 +101,7 @@ def get_default_skills_directory(conf: Optional[dict] = None) -> str: Returns: Absolute path to default skills directory """ - conf = conf or Configuration() + conf = conf or read_mycroft_config() path_override = conf["skills"].get("directory_override") # if .conf wants to use a specific path, use it! diff --git a/ovos_utils/smtp_utils.py b/ovos_utils/smtp_utils.py index 5d24c3c8..cd61b43c 100644 --- a/ovos_utils/smtp_utils.py +++ b/ovos_utils/smtp_utils.py @@ -2,7 +2,7 @@ from email.mime.text import MIMEText from smtplib import SMTP_SSL -from ovos_config import Configuration +from ovos_utils.log import LOG def send_smtp(user, pswd, sender, @@ -19,7 +19,13 @@ def send_smtp(user, pswd, sender, def send_email(subject, body, recipient=None): - mail_config = Configuration.get("email") or {} + try: + from ovos_config.config import read_mycroft_config + config = read_mycroft_config() + except ImportError: + LOG.warning("Config not provided and ovos_config not available") + config = dict() + mail_config = config.get("email") or {} if not mail_config: raise KeyError("email configuration not set") @@ -35,20 +41,3 @@ def send_email(subject, body, recipient=None): user, recipient, subject, body, host, port) - - -if __name__ == "__main__": - USER = "JarbasAI" - YOUR_EMAIL_ADDRESS = "jarbasai@mailfence.com" - DESTINATARY_ADDRESS = "casimiro@jarbasai.online" - YOUR_PASSWORD = "a very very strong Password1!" - HOST = "smtp.mailfence.com" - PORT = 465 - - subject = 'test again' - body = 'this is a test bruh' - - send_email(USER, YOUR_PASSWORD, - YOUR_EMAIL_ADDRESS, DESTINATARY_ADDRESS, - subject, body, - HOST, PORT) diff --git a/ovos_utils/sound/__init__.py b/ovos_utils/sound/__init__.py index cd26e83d..c03b9285 100644 --- a/ovos_utils/sound/__init__.py +++ b/ovos_utils/sound/__init__.py @@ -1,15 +1,21 @@ import os import subprocess import time + from copy import deepcopy from distutils.spawn import find_executable - -from ovos_config import Configuration - from ovos_utils.file_utils import resolve_resource_file from ovos_utils.log import LOG from ovos_utils.signal import check_for_signal +try: + from ovos_config.config import read_mycroft_config +except ImportError: + LOG.warning("Config not provided and ovos_config not available") + + def read_mycroft_config(): + return dict() + # Create a custom environment to use that can be ducked by a phone role. # This is kept separate from the normal os.environ to ensure that the TTS # role isn't affected and that any thirdparty software launched through @@ -35,7 +41,7 @@ def play_acknowledge_sound(): to the user that their request was handled successfully. """ audio_file = resolve_resource_file( - Configuration().get('sounds', {}).get('acknowledge')) + read_mycroft_config().get('sounds', {}).get('acknowledge')) if not audio_file: LOG.warning("Could not find 'acknowledge' audio file!") @@ -50,7 +56,7 @@ def play_acknowledge_sound(): def play_listening_sound(): """Audibly indicate speech recording started.""" audio_file = resolve_resource_file( - Configuration().get('sounds', {}).get('start_listening')) + read_mycroft_config().get('sounds', {}).get('start_listening')) if not audio_file: LOG.warning("Could not find 'start_listening' audio file!") @@ -65,7 +71,7 @@ def play_listening_sound(): def play_end_listening_sound(): """Audibly indicate speech recording is no longer happening.""" audio_file = resolve_resource_file( - Configuration().get('sounds', {}).get('end_listening')) + read_mycroft_config().get('sounds', {}).get('end_listening')) if not audio_file: LOG.debug("Could not find 'end_listening' audio file!") @@ -85,7 +91,7 @@ def play_error_sound(): to the user that their request was NOT handled successfully. """ audio_file = resolve_resource_file( - Configuration().get('sounds', {}).get('error')) + read_mycroft_config().get('sounds', {}).get('error')) if not audio_file: LOG.warning("Could not find 'error' audio file!") @@ -141,7 +147,7 @@ def play_audio(uri, play_cmd=None, environment=None): Returns: subprocess.Popen object. None if the format is not supported or an error occurs playing the file. """ - config = Configuration() + config = read_mycroft_config() environment = environment or _get_pulse_environment(config) # NOTE: some urls like youtube streams will cause extension detection to fail @@ -186,7 +192,7 @@ def play_wav(uri, play_cmd=None, environment=None): Returns: subprocess.Popen object """ - config = Configuration() + config = read_mycroft_config() environment = environment or _get_pulse_environment(config) play_cmd = play_cmd or config.get("play_wav_cmdline") or "paplay %1" play_wav_cmd = str(play_cmd).split(" ") @@ -206,7 +212,7 @@ def play_mp3(uri, play_cmd=None, environment=None): Returns: subprocess.Popen object """ - config = Configuration() + config = read_mycroft_config() environment = environment or _get_pulse_environment(config) play_cmd = play_cmd or config.get("play_mp3_cmdline") or "mpg123 %1" play_mp3_cmd = str(play_cmd).split(" ") @@ -226,7 +232,7 @@ def play_ogg(uri, play_cmd=None, environment=None): Returns: subprocess.Popen object """ - config = Configuration() + config = read_mycroft_config() environment = environment or _get_pulse_environment(config) play_cmd = play_cmd or config.get("play_ogg_cmdline") or "ogg123 -q %1" play_ogg_cmd = str(play_cmd).split(" ") diff --git a/ovos_utils/sound/alsa.py b/ovos_utils/sound/alsa.py index b879c065..3acb0c03 100644 --- a/ovos_utils/sound/alsa.py +++ b/ovos_utils/sound/alsa.py @@ -9,6 +9,9 @@ class AlsaControl: _mixer = None def __init__(self, control=None): + # TODO: Deprecate class in 0.1 + LOG.warning(f"This class is deprecated! Controls moved to" + f"ovos_phal_plugin_alsa.AlsaVolumeControlPlugin") if alsaaudio is None: LOG.error("pyalsaaudio not installed") LOG.info("Run pip install pyalsaaudio==0.8.2") @@ -98,29 +101,3 @@ def get_volume(self): def get_volume_percent(self): return self.get_volume() - - -if __name__ == "__main__": - from time import sleep - a = AlsaControl() - a.set_volume(100) - sleep(2) - print(a.is_muted()) - a.mute() - print(a.is_muted()) - sleep(2) - a.unmute() - print(a.is_muted()) - print(a.get_volume()) - sleep(2) - a.set_volume(50) - print(a.get_volume()) - sleep(2) - a.set_volume(70) - print(a.get_volume()) - sleep(2) - a.set_volume(10) - print(a.get_volume()) - sleep(2) - a.set_volume(80) - print(a.get_volume()) diff --git a/ovos_utils/sound/pulse.py b/ovos_utils/sound/pulse.py index a377c34c..a5cc4665 100644 --- a/ovos_utils/sound/pulse.py +++ b/ovos_utils/sound/pulse.py @@ -1,6 +1,7 @@ import subprocess import re import collections +from ovos_utils.log import LOG class PulseAudio: @@ -8,6 +9,9 @@ class PulseAudio: mute_re = re.compile('^set-sink-mute ([^ ]+) ((?:yes)|(?:no))') def __init__(self): + # TODO: Deprecate class in 0.1 + LOG.warning(f"This class is deprecated! Controls moved to" + f"ovos_phal_plugin_pulseaudio.PulseAudioVolumeControlPlugin") self._mute = collections.OrderedDict() self._volume = collections.OrderedDict() self.update() @@ -155,8 +159,3 @@ def decrease_volume(self, percent): elif volume > 100: volume = 100 self.set_all_volumes_percent(volume) - - -if __name__ == "__main__": - p = PulseAudio() - print(p.list_sources()) \ No newline at end of file diff --git a/ovos_utils/system.py b/ovos_utils/system.py index 2696048f..a3f6009e 100644 --- a/ovos_utils/system.py +++ b/ovos_utils/system.py @@ -11,6 +11,7 @@ from ovos_utils.log import LOG +# TODO: Deprecate MycroftRootLocations in 0.1.0 class MycroftRootLocations(str, Enum): PICROFT = "/home/pi/mycroft-core" BIGSCREEN = "/home/mycroft/mycroft-core" diff --git a/ovos_utils/version.py b/ovos_utils/version.py index 1e605472..eafc500e 100644 --- a/ovos_utils/version.py +++ b/ovos_utils/version.py @@ -2,6 +2,6 @@ # START_VERSION_BLOCK VERSION_MAJOR = 0 VERSION_MINOR = 0 -VERSION_BUILD = 30 +VERSION_BUILD = 31 VERSION_ALPHA = 0 # END_VERSION_BLOCK diff --git a/requirements/extras.txt b/requirements/extras.txt index da2939a2..4f726695 100644 --- a/requirements/extras.txt +++ b/requirements/extras.txt @@ -1 +1,3 @@ -rapidfuzz~=1.0 \ No newline at end of file +rapidfuzz~=2.0 +ovos-bus-client < 0.1.0 +ovos-config < 0.1.0 \ No newline at end of file diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 678653f9..44c870bc 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,7 +1,6 @@ -mycroft-messagebus-client~=0.9,!=0.9.2,!=0.9.3 pexpect~=4.8 requests~=2.26 json_database~=0.7 kthread~=0.2 -ovos_config~=0.0,>=0.0.7 watchdog +pyee \ No newline at end of file diff --git a/scripts/bump_alpha.py b/scripts/bump_alpha.py deleted file mode 100644 index cf2a9716..00000000 --- a/scripts/bump_alpha.py +++ /dev/null @@ -1,18 +0,0 @@ -import fileinput -from os.path import join, dirname - - -version_file = join(dirname(dirname(__file__)), "ovos_utils", "version.py") -version_var_name = "VERSION_ALPHA" - -with open(version_file, "r", encoding="utf-8") as v: - for line in v.readlines(): - if line.startswith(version_var_name): - version = int(line.split("=")[-1]) - new_version = int(version) + 1 - -for line in fileinput.input(version_file, inplace=True): - if line.startswith(version_var_name): - print(f"{version_var_name} = {new_version}") - else: - print(line.rstrip('\n')) diff --git a/scripts/bump_build.py b/scripts/bump_build.py deleted file mode 100644 index f4cfcfde..00000000 --- a/scripts/bump_build.py +++ /dev/null @@ -1,21 +0,0 @@ -import fileinput -from os.path import join, dirname - - -version_file = join(dirname(dirname(__file__)), "ovos_utils", "version.py") -version_var_name = "VERSION_BUILD" -alpha_var_name = "VERSION_ALPHA" - -with open(version_file, "r", encoding="utf-8") as v: - for line in v.readlines(): - if line.startswith(version_var_name): - version = int(line.split("=")[-1]) - new_version = int(version) + 1 - -for line in fileinput.input(version_file, inplace=True): - if line.startswith(version_var_name): - print(f"{version_var_name} = {new_version}") - elif line.startswith(alpha_var_name): - print(f"{alpha_var_name} = 0") - else: - print(line.rstrip('\n')) diff --git a/scripts/bump_major.py b/scripts/bump_major.py deleted file mode 100644 index 2f365774..00000000 --- a/scripts/bump_major.py +++ /dev/null @@ -1,27 +0,0 @@ -import fileinput -from os.path import join, dirname - - -version_file = join(dirname(dirname(__file__)), "ovos_utils", "version.py") -version_var_name = "VERSION_MAJOR" -minor_var_name = "VERSION_MINOR" -build_var_name = "VERSION_BUILD" -alpha_var_name = "VERSION_ALPHA" - -with open(version_file, "r", encoding="utf-8") as v: - for line in v.readlines(): - if line.startswith(version_var_name): - version = int(line.split("=")[-1]) - new_version = int(version) + 1 - -for line in fileinput.input(version_file, inplace=True): - if line.startswith(version_var_name): - print(f"{version_var_name} = {new_version}") - elif line.startswith(minor_var_name): - print(f"{minor_var_name} = 0") - elif line.startswith(build_var_name): - print(f"{build_var_name} = 0") - elif line.startswith(alpha_var_name): - print(f"{alpha_var_name} = 0") - else: - print(line.rstrip('\n')) diff --git a/scripts/bump_minor.py b/scripts/bump_minor.py deleted file mode 100644 index 2fe8194f..00000000 --- a/scripts/bump_minor.py +++ /dev/null @@ -1,24 +0,0 @@ -import fileinput -from os.path import join, dirname - - -version_file = join(dirname(dirname(__file__)), "ovos_utils", "version.py") -version_var_name = "VERSION_MINOR" -build_var_name = "VERSION_BUILD" -alpha_var_name = "VERSION_ALPHA" - -with open(version_file, "r", encoding="utf-8") as v: - for line in v.readlines(): - if line.startswith(version_var_name): - version = int(line.split("=")[-1]) - new_version = int(version) + 1 - -for line in fileinput.input(version_file, inplace=True): - if line.startswith(version_var_name): - print(f"{version_var_name} = {new_version}") - elif line.startswith(build_var_name): - print(f"{build_var_name} = 0") - elif line.startswith(alpha_var_name): - print(f"{alpha_var_name} = 0") - else: - print(line.rstrip('\n')) diff --git a/scripts/remove_alpha.py b/scripts/remove_alpha.py deleted file mode 100644 index ee94ee9f..00000000 --- a/scripts/remove_alpha.py +++ /dev/null @@ -1,13 +0,0 @@ -import fileinput -from os.path import join, dirname - - -version_file = join(dirname(dirname(__file__)), "ovos_utils", "version.py") - -alpha_var_name = "VERSION_ALPHA" - -for line in fileinput.input(version_file, inplace=True): - if line.startswith(alpha_var_name): - print(f"{alpha_var_name} = 0") - else: - print(line.rstrip('\n')) diff --git a/setup.py b/setup.py index a5a5fa06..c8e7aa5e 100644 --- a/setup.py +++ b/setup.py @@ -70,12 +70,5 @@ def required(requirements_file): license='Apache', author='jarbasAI', author_email='jarbasai@mailfence.com', - description='collection of simple utilities for use across the mycroft ecosystem', - entry_points={ - 'console_scripts': [ - 'ovos-listen=ovos_utils.scripts:ovos_listen', - 'ovos-speak=ovos_utils.scripts:ovos_speak', - 'ovos-say-to=ovos_utils.scripts:ovos_say_to', - ] - } + description='collection of simple utilities for use across the mycroft ecosystem' ) diff --git a/test/unittests/scripts/wait_for_exit.py b/test/unittests/scripts/wait_for_exit.py new file mode 100644 index 00000000..f9dce617 --- /dev/null +++ b/test/unittests/scripts/wait_for_exit.py @@ -0,0 +1,2 @@ +from ovos_utils import wait_for_exit_signal +wait_for_exit_signal() diff --git a/test/unittests/test_audio_utils.py b/test/unittests/test_audio_utils.py index 84513a8f..f247979f 100644 --- a/test/unittests/test_audio_utils.py +++ b/test/unittests/test_audio_utils.py @@ -22,7 +22,7 @@ def __eq__(self, other): return True -@mock.patch('ovos_utils.sound.Configuration') +@mock.patch('ovos_utils.sound.read_mycroft_config') @mock.patch('ovos_utils.sound.subprocess') class TestPlaySounds(TestCase): def test_play_ogg(self, mock_subprocess, mock_conf): diff --git a/test/unittests/test_bracket_expansion.py b/test/unittests/test_bracket_expansion.py new file mode 100644 index 00000000..8e1c31e1 --- /dev/null +++ b/test/unittests/test_bracket_expansion.py @@ -0,0 +1,31 @@ +import unittest + + +class TestBracketExpansion(unittest.TestCase): + def test_expand_parentheses(self): + from ovos_utils.bracket_expansion import expand_parentheses + # TODO + + def test_expand_options(self): + from ovos_utils.bracket_expansion import expand_options + # TODO + + def test_fragment(self): + from ovos_utils.bracket_expansion import Fragment + # TODO + + def test_word(self): + from ovos_utils.bracket_expansion import Word + # TODO + + def test_sentence(self): + from ovos_utils.bracket_expansion import Sentence + # TODO + + def test_options(self): + from ovos_utils.bracket_expansion import Options + # TODO + + def test_sentence_tree_parser(self): + from ovos_utils.bracket_expansion import SentenceTreeParser + # TODO diff --git a/test/unittests/test_device_input.py b/test/unittests/test_device_input.py new file mode 100644 index 00000000..96d06a57 --- /dev/null +++ b/test/unittests/test_device_input.py @@ -0,0 +1,63 @@ +import unittest +from unittest import mock +from unittest.mock import Mock + + +class TestDeviceInput(unittest.TestCase): + def test_input_device_helper(self): + # TODO + pass + + @mock.patch('distutils.spawn.find_executable') + def test_can_use_touch_mouse(self, find_exec): + from ovos_utils.device_input import InputDeviceHelper + find_exec.return_value = True + dev_input = InputDeviceHelper() + + dev_input._build_linput_devices_list = Mock() + dev_input._build_xinput_devices_list = Mock() + + dev_input.libinput_devices_list = [{'Device': 'Mock', + 'Capabilities': ['mouse']}, + {'Device': "Mock 1", + 'Capabilities': ['touch']} + ] + self.assertTrue(dev_input.can_use_touch_mouse()) + + dev_input.libinput_devices_list.pop() + self.assertTrue(dev_input.can_use_touch_mouse()) + dev_input.libinput_devices_list.pop() + self.assertFalse(dev_input.can_use_touch_mouse()) + dev_input.xinput_devices_list = [{'Device': 'xinput', + 'Capabilities': ['tablet'] + }] + self.assertTrue(dev_input.can_use_touch_mouse()) + dev_input.xinput_devices_list.pop() + self.assertFalse(dev_input.can_use_touch_mouse()) + + @mock.patch('distutils.spawn.find_executable') + def test_can_use_keyboard(self, find_exec): + from ovos_utils.device_input import InputDeviceHelper + find_exec.return_value = True + dev_input = InputDeviceHelper() + + dev_input._build_linput_devices_list = Mock() + dev_input._build_xinput_devices_list = Mock() + + dev_input.libinput_devices_list = [{'Device': 'Mock', + 'Capabilities': ['keyboard']}, + {'Device': "Mock 1", + 'Capabilities': ['touch']} + ] + self.assertTrue(dev_input.can_use_keyboard()) + + dev_input.libinput_devices_list.pop() + self.assertTrue(dev_input.can_use_keyboard()) + dev_input.libinput_devices_list.pop() + self.assertFalse(dev_input.can_use_keyboard()) + dev_input.xinput_devices_list = [{'Device': 'xinput', + 'Capabilities': ['keyboard'] + }] + self.assertTrue(dev_input.can_use_keyboard()) + dev_input.xinput_devices_list.pop() + self.assertFalse(dev_input.can_use_keyboard()) diff --git a/test/unittests/test_dialog.py b/test/unittests/test_dialog.py new file mode 100644 index 00000000..a29acbcc --- /dev/null +++ b/test/unittests/test_dialog.py @@ -0,0 +1,19 @@ +import unittest + + +class TestDialog(unittest.TestCase): + def test_mustache_dialog_renderer(self): + from ovos_utils.dialog import MustacheDialogRenderer + # TODO + + def test_load_dialogs(self): + from ovos_utils.dialog import load_dialogs + # TODO + + def test_get_dialog(self): + from ovos_utils.dialog import get_dialog + # TODO + + def test_join_list(self): + from ovos_utils.dialog import join_list + # TODO diff --git a/test/unittests/test_enclosure.py b/test/unittests/test_enclosure.py new file mode 100644 index 00000000..50f91aab --- /dev/null +++ b/test/unittests/test_enclosure.py @@ -0,0 +1,16 @@ +import unittest + +from mock import patch +from ovos_utils.messagebus import FakeBus + + +class TestEnclosureAPI(unittest.TestCase): + from ovos_utils.enclosure.api import EnclosureAPI + bus = FakeBus() + api = EnclosureAPI(bus) + # TODO: Test api methods + + +class TestMark1(unittest.TestCase): + # TODO Implement tests or move to separate PHAL plugin + pass diff --git a/test/unittests/test_events.py b/test/unittests/test_events.py new file mode 100644 index 00000000..6b374457 --- /dev/null +++ b/test/unittests/test_events.py @@ -0,0 +1,27 @@ +import unittest + + +class TestEvents(unittest.TestCase): + def test_unmunge_message(self): + from ovos_utils.events import unmunge_message + # TODO + + def test_get_handler_name(self): + from ovos_utils.events import get_handler_name + # TODO + + def test_create_wrapper(self): + from ovos_utils.events import create_wrapper + # TODO + + def test_create_basic_wrapper(self): + from ovos_utils.events import create_basic_wrapper + # TODO + + def test_event_container(self): + from ovos_utils.events import EventContainer + # TODO + + def test_event_scheduler_interface(self): + from ovos_utils.events import EventSchedulerInterface + # TODO diff --git a/test/unittests/test_file_utils.py b/test/unittests/test_file_utils.py new file mode 100644 index 00000000..787ec25f --- /dev/null +++ b/test/unittests/test_file_utils.py @@ -0,0 +1,57 @@ +import unittest +from os.path import isdir, isfile + + +class TestFileUtils(unittest.TestCase): + def test_get_temp_path(self): + from ovos_utils.file_utils import get_temp_path + self.assertTrue(isdir(get_temp_path())) + self.assertIsInstance(get_temp_path("test"), str) + self.assertIsInstance(get_temp_path("test/1/2.test"), str) + + def test_get_cache_directory(self): + from ovos_utils.file_utils import get_cache_directory + self.assertTrue(isdir(get_cache_directory("test"))) + self.assertTrue(isdir(get_cache_directory("test/another/test"))) + + def test_resolve_ovos_resource_file(self): + from ovos_utils.file_utils import resolve_ovos_resource_file + invalid = resolve_ovos_resource_file("not_real.file") + self.assertIsNone(invalid) + # TODO: Test valid case + + def test_resolve_resource_file(self): + from ovos_utils.file_utils import resolve_resource_file + # TODO + + def test_read_vocab_file(self): + from ovos_utils.file_utils import read_vocab_file + # TODO + + def test_load_regex_from_file(self): + from ovos_utils.file_utils import load_regex_from_file + # TODO + + def test_load_vocabulary(self): + from ovos_utils.file_utils import load_vocabulary + # TODO + + def test_load_regex(self): + from ovos_utils.file_utils import load_regex + # TODO + + def test_read_value_file(self): + from ovos_utils.file_utils import read_value_file + # TODO + + def test_read_translated_file(self): + from ovos_utils.file_utils import read_translated_file + # TODO + + def test_filewatcher(self): + from ovos_utils.file_utils import FileWatcher + # TODO + + def test_file_event_handler(self): + from ovos_utils.file_utils import FileEventHandler + # TODO diff --git a/test/unittests/test_fingerprinting.py b/test/unittests/test_fingerprinting.py new file mode 100644 index 00000000..df79264a --- /dev/null +++ b/test/unittests/test_fingerprinting.py @@ -0,0 +1,6 @@ +import unittest + + +class TestFingerprinting(unittest.TestCase): + # TODO: determine if any of these methods should be deprecated + pass diff --git a/test/unittests/test_gui.py b/test/unittests/test_gui.py new file mode 100644 index 00000000..f57ae04b --- /dev/null +++ b/test/unittests/test_gui.py @@ -0,0 +1,80 @@ +import unittest +from mock import patch, call + + +class TestGui(unittest.TestCase): + @patch("ovos_utils.gui.has_screen") + def test_can_display(self, has_screen): + from ovos_utils.gui import can_display + has_screen.return_value = False + self.assertFalse(can_display()) + has_screen.return_value = True + self.assertTrue(can_display()) + has_screen.return_value = None + self.assertFalse(can_display()) + + @patch("ovos_utils.gui.is_installed") + def test_is_gui_installed(self, is_installed): + from ovos_utils.gui import is_gui_installed + is_installed.return_value = False + self.assertFalse(is_gui_installed()) + is_installed.assert_has_calls([call("mycroft-gui-app"), + call("ovos-shell"), + call("mycroft-embedded-shell"), + call("plasmashell")]) + is_installed.return_value = True + self.assertTrue(is_gui_installed()) + is_installed.assert_called_with("mycroft-gui-app") + + # Test passed applications + self.assertTrue(is_gui_installed(["test"])) + is_installed.assert_called_with("test") + + @patch("ovos_utils.gui.is_process_running") + def test_is_gui_running(self, is_running): + from ovos_utils.gui import is_gui_running + is_running.return_value = False + self.assertFalse(is_gui_running()) + is_running.assert_has_calls([call("mycroft-gui-app"), + call("ovos-shell"), + call("mycroft-embedded-shell"), + call("plasmashell")]) + is_running.return_value = True + self.assertTrue(is_gui_running()) + is_running.assert_called_with("mycroft-gui-app") + + # Test passed applications + self.assertTrue(is_gui_running(["test"])) + is_running.assert_called_with("test") + + def test_is_gui_connected(self): + from ovos_utils.gui import is_gui_connected + # TODO + + def test_can_use_local_gui(self): + from ovos_utils.gui import can_use_local_gui + # TODO + + def test_can_use_gui(self): + from ovos_utils.gui import can_use_gui + # TODO + + def test_extend_about_data(self): + from ovos_utils.gui import extend_about_data + # TODO + + def test_gui_widgets(self): + from ovos_utils.gui import GUIWidgets + # TODO + + def test_gui_tracker(self): + from ovos_utils.gui import GUITracker + # TODO + + def test_gui_dict(self): + from ovos_utils.gui import _GUIDict + # TODO + + def test_gui_interface(self): + from ovos_utils.gui import GUIInterface + # TODO diff --git a/test/unittests/test_intents.py b/test/unittests/test_intents.py new file mode 100644 index 00000000..59f5fbff --- /dev/null +++ b/test/unittests/test_intents.py @@ -0,0 +1,65 @@ +import unittest + +from mock import mock, patch +from ovos_utils.messagebus import FakeBus + + +class TestIntent(unittest.TestCase): + from ovos_utils.intents import Intent + # TODO + + +class TestIntentBuilder(unittest.TestCase): + from ovos_utils.intents import IntentBuilder + # TODO + + +class TestAdaptIntent(unittest.TestCase): + from ovos_utils.intents import AdaptIntent + # TODO + pass + + +class TestConverse(unittest.TestCase): + from ovos_utils.intents.converse import ConverseTracker + # TODO + pass + + +class TestIntentServiceInterface(unittest.TestCase): + def test_to_alnum(self): + from ovos_utils.intents.intent_service_interface import to_alnum + test_alnum = "test_skill123" + self.assertEqual(test_alnum, to_alnum(test_alnum)) + test_dash = "test-skill123" + self.assertEqual(test_alnum, to_alnum(test_dash)) + test_slash = "test/skill123" + self.assertEqual(test_alnum, to_alnum(test_slash)) + + def test_munge_regex(self): + from ovos_utils.intents.intent_service_interface import munge_regex + skill_id = "test_skill" + non_regex = "just a string with no entity" + with_regex = "a string with this (?P.*)" + munged_regex = f"a string with this (?P<{skill_id}entity>.*)" + + self.assertEqual(non_regex, munge_regex(non_regex, skill_id)) + self.assertEqual(munged_regex, munge_regex(with_regex, skill_id)) + + def test_munge_intent_parser(self): + from ovos_utils.intents.intent_service_interface import \ + munge_intent_parser + # TODO + + def test_intent_service_interface(self): + from ovos_utils.intents.intent_service_interface import \ + IntentServiceInterface + # TODO + + def test_intent_query_api(self): + from ovos_utils.intents.intent_service_interface import IntentQueryApi + # TODO + + def test_open_intent_envelope(self): + pass + # TODO: Deprecated? diff --git a/test/unittests/test_json_helpers.py b/test/unittests/test_json_helpers.py index 0ba6a081..1d4ea913 100644 --- a/test/unittests/test_json_helpers.py +++ b/test/unittests/test_json_helpers.py @@ -6,12 +6,18 @@ class TestJsonHelpers(unittest.TestCase): - @classmethod - def setUpClass(self): - self.base_dict = {"one": 1, "two": 2, "three": 3, - "four": ["foo", "bar", "baz"], "five": 50} - self.delta_dict = {"two": 2, "three": 30, - "four": [4, 5, 6, "foo"], "five": None} + base_dict = {"one": 1, "two": 2, "three": 3, + "four": ["foo", "bar", "baz"], "five": 50} + delta_dict = {"two": 2, "three": 30, + "four": [4, 5, 6, "foo"], "five": None} + + def test_load_commented_json(self): + from ovos_utils.json_helper import load_commented_json + # TODO + + def test_uncomment_json(self): + from ovos_utils.json_helper import uncomment_json + # TODO def test_invert_dict(self): dct = {'a': 1, 'b': "d"} diff --git a/test/unittests/test_lang.py b/test/unittests/test_lang.py new file mode 100644 index 00000000..356c1fb8 --- /dev/null +++ b/test/unittests/test_lang.py @@ -0,0 +1,24 @@ +import unittest + + +class TestLang(unittest.TestCase): + def test_get_language_dir(self): + from ovos_utils.lang import get_language_dir + # TODO + + def test_translate_word(self): + from ovos_utils.lang import translate_word + # TODO + + def test_phonemes(self): + from ovos_utils.lang.phonemes import arpabet2ipa, ipa2arpabet + for key, val in arpabet2ipa.items(): + self.assertIsInstance(key, str) + self.assertIsInstance(val, str) + self.assertEqual(ipa2arpabet[val], key) + + def test_visemes(self): + from ovos_utils.lang.visimes import VISIMES + for key, val in VISIMES.items(): + self.assertIsInstance(key, str) + self.assertIsInstance(val, str) \ No newline at end of file diff --git a/test/unittests/test_log.py b/test/unittests/test_log.py new file mode 100644 index 00000000..4f2f3b32 --- /dev/null +++ b/test/unittests/test_log.py @@ -0,0 +1,11 @@ +import unittest + + +class TestLog(unittest.TestCase): + def test_log(self): + from ovos_utils.log import LOG + # TODO + + def test_init_service_logger(self): + from ovos_utils.log import init_service_logger + # TODO diff --git a/test/unittests/test_messagebus.py b/test/unittests/test_messagebus.py new file mode 100644 index 00000000..8a01676c --- /dev/null +++ b/test/unittests/test_messagebus.py @@ -0,0 +1,6 @@ +import unittest + + +class TestMessagebus(unittest.TestCase): + # TODO: Implement tests or move utils to `ovos-bus-client` package + pass \ No newline at end of file diff --git a/test/test_metrics.py b/test/unittests/test_metrics.py similarity index 100% rename from test/test_metrics.py rename to test/unittests/test_metrics.py diff --git a/test/unittests/test_network_utils.py b/test/unittests/test_network_utils.py new file mode 100644 index 00000000..41c61fb5 --- /dev/null +++ b/test/unittests/test_network_utils.py @@ -0,0 +1,44 @@ + +import unittest +from time import sleep + + +class TestNetworkUtils(unittest.TestCase): + def test_get_network_tests_config(self): + from ovos_utils.network_utils import get_network_tests_config + # self.assertEqual(set(get_network_tests_config().keys()), + # {'ip_url', 'dns_primary', 'dns_secondary', 'web_url', + # 'web_url_secondary', 'captive_portal_url', + # 'captive_portal_text'}) + # TODO: Validate config in helper method + + def test_get_ip(self): + from ovos_utils.network_utils import get_ip + ip_addr = get_ip() + self.assertIsInstance(ip_addr, str) + self.assertEqual(len(ip_addr.split('.')), 4) + + def test_get_external_ip(self): + from ovos_utils.network_utils import get_external_ip + ip_addr = get_external_ip() + self.assertIsInstance(ip_addr, str) + self.assertEqual(len(ip_addr.split('.')), 4) + + def test_is_connected_dns(self): + from ovos_utils.network_utils import is_connected_dns + self.assertIsInstance(is_connected_dns(), bool) + # TODO + + def test_is_connected_http(self): + from ovos_utils.network_utils import is_connected_http + self.assertIsInstance(is_connected_http(), bool) + # TODO + + def test_is_connected(self): + from ovos_utils.network_utils import is_connected + self.assertIsInstance(is_connected(), bool) + # TODO + + def test_check_captive_portal(self): + from ovos_utils.network_utils import check_captive_portal + # TODO diff --git a/test/unittests/test_parse.py b/test/unittests/test_parse.py new file mode 100644 index 00000000..71cf16ac --- /dev/null +++ b/test/unittests/test_parse.py @@ -0,0 +1,23 @@ +import unittest + + +class TestParse(unittest.TestCase): + def test_validate_matching_strategy(self): + from ovos_utils.parse import _validate_matching_strategy + # TODO + + def test_fuzzy_match(self): + from ovos_utils.parse import fuzzy_match + # TODO + + def test_match_one(self): + from ovos_utils.parse import match_one + # TODO + + def test_match_all(self): + from ovos_utils.parse import match_all + # TODO + + def test_remove_parentheses(self): + from ovos_utils.parse import remove_parentheses + # TODO diff --git a/test/unittests/test_process_utils.py b/test/unittests/test_process_utils.py new file mode 100644 index 00000000..d293b5d0 --- /dev/null +++ b/test/unittests/test_process_utils.py @@ -0,0 +1,6 @@ +import unittest + + +class TestProcessUtils(unittest.TestCase): + # TODO: Implement unit tests for process_utils + pass diff --git a/test/unittests/test_security.py b/test/unittests/test_security.py new file mode 100644 index 00000000..e5ff0abd --- /dev/null +++ b/test/unittests/test_security.py @@ -0,0 +1,6 @@ +import unittest + + +class TestSecurity(unittest.TestCase): + # TODO: Implement unit tests for security + pass diff --git a/test/unittests/test_signal.py b/test/unittests/test_signal.py new file mode 100644 index 00000000..6d65b01a --- /dev/null +++ b/test/unittests/test_signal.py @@ -0,0 +1,6 @@ +import unittest + + +class TestSignal(unittest.TestCase): + # TODO: Implement unit tests for signal + pass diff --git a/test/unittests/test_skills.py b/test/unittests/test_skills.py index ce245df2..e6baa40b 100644 --- a/test/unittests/test_skills.py +++ b/test/unittests/test_skills.py @@ -2,6 +2,59 @@ from os import environ from os.path import isdir, join, dirname from unittest.mock import patch +from ovos_utils.skills.locations import get_skill_directories +from ovos_utils.skills.locations import get_default_skills_directory +from ovos_utils.skills.locations import get_installed_skill_ids +from ovos_utils.skills.locations import get_plugin_skills +try: + import ovos_config +except ImportError: + ovos_config = None + + +class TestSkills(unittest.TestCase): + def test_get_non_properties(self): + from ovos_utils.skills import get_non_properties + # TODO + + def test_skills_loaded(self): + from ovos_utils.skills import skills_loaded + # TODO + + @patch("ovos_utils.skills.update_mycroft_config") + def test_blacklist_skill(self, update_config): + from ovos_utils.skills import blacklist_skill + # TODO + + @patch("ovos_utils.skills.update_mycroft_config") + def test_whitelist_skill(self, update_config): + from ovos_utils.skills import whitelist_skill + # TODO + + +class TestAudioservice(unittest.TestCase): + def test_ensure_uri(self): + from ovos_utils.skills.audioservice import ensure_uri + valid_uri = "file:///test" + non_uri = "/test" + # rel_uri = "test" + self.assertEqual(ensure_uri(valid_uri), valid_uri) + self.assertEqual(ensure_uri(non_uri), valid_uri) + # TODO: Relative path is relative to method and not caller? + # self.assertEqual(ensure_uri(rel_uri), + # f"file://{join(dirname(__file__), 'test')}") + + def test_classic_audio_service_interface(self): + from ovos_utils.skills.audioservice import ClassicAudioServiceInterface + # TODO + + def test_audio_service_interface(self): + from ovos_utils.skills.audioservice import AudioServiceInterface + # TODO + + def test_ocp_interface(self): + from ovos_utils.skills.audioservice import OCPInterface + # TODO class TestLocations(unittest.TestCase): @@ -9,7 +62,6 @@ class TestLocations(unittest.TestCase): def test_get_installed_skill_ids(self, plugins): plugins.return_value = (['plugin_dir', 'plugin_dir_2'], ['plugin_id', 'plugin_id_2']) - from ovos_utils.skills.locations import get_installed_skill_ids environ["XDG_DATA_DIRS"] = join(dirname(__file__), "test_skills_xdg") config = {"skills": { "extra_directories": [join(dirname(__file__), "test_skills_dir")] @@ -20,7 +72,8 @@ def test_get_installed_skill_ids(self, plugins): "skill-test-2.openvoiceos"}) def test_get_skill_directories(self): - from ovos_utils.skills.locations import get_skill_directories + if not ovos_config: + return # skip test since ovos.conf isn't taken into account # Default behavior, only one valid XDG path environ["XDG_DATA_DIRS"] = environ["XDG_DATA_HOME"] = \ @@ -41,13 +94,15 @@ def test_get_skill_directories(self): self.assertEqual(get_skill_directories(config), [default_dir, extra_dir]) + # Define invalid directories in extra_directories config['skills']['extra_directories'] += ["/not/a/directory"] self.assertEqual(get_skill_directories(config), [default_dir, extra_dir]) def test_get_default_skills_directory(self): - from ovos_utils.skills.locations import get_default_skills_directory + if not ovos_config: + return # skip test since ovos.conf isn't taken into account test_skills_dir = join(dirname(__file__), "test_skills_dir") # Configured override (legacy) @@ -70,7 +125,6 @@ def test_get_default_skills_directory(self): self.assertEqual(get_default_skills_directory(config), xdg_skills_dir) def test_get_plugin_skills(self): - from ovos_utils.skills.locations import get_plugin_skills dirs, ids = get_plugin_skills() for d in dirs: self.assertTrue(isdir(d)) diff --git a/test/unittests/test_smtp_utils.py b/test/unittests/test_smtp_utils.py new file mode 100644 index 00000000..aa791907 --- /dev/null +++ b/test/unittests/test_smtp_utils.py @@ -0,0 +1,6 @@ +import unittest + + +class TestSMTPUtils(unittest.TestCase): + # TODO: Implement unit tests for smtp_utils + pass diff --git a/test/unittests/test_sound.py b/test/unittests/test_sound.py new file mode 100644 index 00000000..b95e25c4 --- /dev/null +++ b/test/unittests/test_sound.py @@ -0,0 +1,57 @@ +import unittest +from time import sleep + + +class TestSound(unittest.TestCase): + # TODO: Some tests already implemented in `test_sound` + def test_get_pulse_environment(self): + from ovos_utils.sound import _get_pulse_environment + # TODO + + def test_play_acknowledge_sound(self): + from ovos_utils.sound import play_acknowledge_sound + # TODO + + def test_play_listening_sound(self): + from ovos_utils.sound import play_listening_sound + # TODO + + def test_play_end_listening_sound(self): + from ovos_utils.sound import play_end_listening_sound + # TODO + + def test_play_error_sound(self): + from ovos_utils.sound import play_error_sound + # TODO + + def test_find_player(self): + from ovos_utils.sound import _find_player + # TODO + + def test_play_audio(self): + from ovos_utils.sound import play_audio + # TODO + + def test_play_wav(self): + from ovos_utils.sound import play_wav + # TODO + + def test_play_mp3(self): + from ovos_utils.sound import play_wav + # TODO + + def test_play_ogg(self): + from ovos_utils.sound import play_ogg + # TODO + + def test_record(self): + from ovos_utils.sound import record + # TODO + + def test_is_speaking(self): + from ovos_utils.sound import is_speaking + # TODO + + def test_wait_while_speaking(self): + from ovos_utils.sound import wait_while_speaking + # TODO diff --git a/test/unittests/test_ssml.py b/test/unittests/test_ssml.py index b972022e..e7d9e9d9 100644 --- a/test/unittests/test_ssml.py +++ b/test/unittests/test_ssml.py @@ -3,10 +3,8 @@ class TestSSMLhelpers(unittest.TestCase): - @classmethod - def setUpClass(self): - self.base_utterance = "this is a test of Open Voice OS SSML utils" - self.base_utterance2 = "creating ssml for usage with text to speech" + base_utterance = "this is a test of Open Voice OS SSML utils" + base_utterance2 = "creating ssml for usage with text to speech" def test_init_flags(self): self.assertEqual( diff --git a/test/unittests/test_utils.py b/test/unittests/test_utils.py index 188c84ef..fe2646ca 100644 --- a/test/unittests/test_utils.py +++ b/test/unittests/test_utils.py @@ -1,21 +1,61 @@ +import signal import unittest - -from ovos_utils import rotate_list, camel_case_split, get_handler_name, flatten_list +from os.path import join, dirname +from sys import executable +from subprocess import Popen, TimeoutExpired class TestHelpers(unittest.TestCase): - def test_utils(self): + def test_classproperty(self): + # TODO + pass + + def test_timed_lru_cache(self): + # TODO + pass + + def test_create_killable_daemon(self): + # TODO + pass + + def test_create_daemon(self): + # TODO + pass + + def test_create_loop(self): + # TODO + pass + + def test_wait_for_exit_signal(self): + test_file = join(dirname(__file__), "scripts", "wait_for_exit.py") + wait_thread = Popen([executable, test_file]) + + # No return + with self.assertRaises(TimeoutExpired): + wait_thread.communicate(timeout=1) + with self.assertRaises(TimeoutExpired): + wait_thread.communicate(timeout=1) + + # Send interrupt and get returncode 0 + wait_thread.send_signal(signal.SIGINT) + self.assertEqual(wait_thread.wait(1), 0) + + def test_get_handler_name(self): + from ovos_utils import get_handler_name + def some_function(): return self.assertEqual(get_handler_name(some_function), "some_function") - self.assertEqual(get_handler_name(self.test_utils), "test_utils") + def test_camel_case_split(self): + from ovos_utils import camel_case_split self.assertEqual(camel_case_split("MyAwesomeSkill"), "My Awesome Skill") - def test_list_utils(self): + def test_rotate_list(self): + from ovos_utils import rotate_list self.assertEqual(rotate_list([1, 2, 3]), [2, 3, 1]) self.assertEqual(rotate_list([1, 2, 3], 2), [3, 1, 2]) self.assertEqual(rotate_list([1, 2, 3], 3), [1, 2, 3]) @@ -24,6 +64,8 @@ def test_list_utils(self): self.assertEqual(rotate_list([1, 2, 3], -2), [2, 3, 1]) self.assertEqual(rotate_list([1, 2, 3], -3), [1, 2, 3]) + def test_flatten_list(self): + from ovos_utils import flatten_list self.assertEqual( flatten_list([["A", "B"], ["C"]]), ["A", "B", "C"] ) @@ -34,3 +76,7 @@ def test_list_utils(self): flatten_list([("A", "B"), ["C"], [["D", ["E", ["F"]]]]]), ["A", "B", "C", "D", "E", "F"] ) + + def test_datestr2ts(self): + # TODO + pass diff --git a/test/unittests/test_xdg_utils.py b/test/unittests/test_xdg_utils.py new file mode 100644 index 00000000..fa12da47 --- /dev/null +++ b/test/unittests/test_xdg_utils.py @@ -0,0 +1,6 @@ +import unittest + + +class TestXDGUtils(unittest.TestCase): + # TODO: Implement unit tests for xdg_utils + pass diff --git a/test/unittests/test_xml_helper.py b/test/unittests/test_xml_helper.py new file mode 100644 index 00000000..2d1a9105 --- /dev/null +++ b/test/unittests/test_xml_helper.py @@ -0,0 +1,6 @@ +import unittest + + +class TestXMLHelper(unittest.TestCase): + # TODO: Implement unit tests for xml_helper + pass