├── .flake8 ├── .github ├── CODEOWNERS ├── renovate.json └── workflows │ └── release.yml ├── poetry.toml ├── .gitattributes ├── jira_amt ├── __main__.py ├── __init__.py ├── config.py ├── jira.py └── cli.py ├── tests ├── conftest.py └── test_jira.py ├── Makefile ├── LICENSE ├── pyproject.toml ├── .gitignore ├── README.md └── poetry.lock /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | extend-ignore = E501 -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hatamiarash7 -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /jira_amt/__main__.py: -------------------------------------------------------------------------------- 1 | """Jira entry point script.""" 2 | 3 | from jira_amt import cli, __app_name__ 4 | 5 | 6 | def main(): 7 | cli.app(prog_name=__app_name__) 8 | 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | 4 | 5 | @pytest.fixture(scope="session", autouse=True) 6 | def env_setup(): 7 | os.environ["JIRA_SERVER"] = "https://jira.example.com" 8 | os.environ["JIRA_PAT"] = "1234567890" 9 | -------------------------------------------------------------------------------- /jira_amt/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for Jira.""" 2 | 3 | __app_name__ = "jira-amt" 4 | __version__ = "1.3.1" 5 | __author__ = "Arash Hatami " 6 | 7 | ( 8 | SUCCESS, 9 | DIR_ERROR, 10 | FILE_ERROR, 11 | DB_READ_ERROR, 12 | DB_WRITE_ERROR, 13 | JSON_ERROR, 14 | ID_ERROR, 15 | ) = range(7) 16 | -------------------------------------------------------------------------------- /tests/test_jira.py: -------------------------------------------------------------------------------- 1 | from typer.testing import CliRunner 2 | from jira_amt import __app_name__, __version__, __author__, cli 3 | 4 | runner = CliRunner() 5 | 6 | 7 | def test_version(): 8 | result = runner.invoke(cli.app, ["--version"]) 9 | assert result.exit_code == 0 10 | assert f"{__app_name__} v{__version__}\n" in result.stdout 11 | 12 | 13 | def test_author(): 14 | result = runner.invoke(cli.app, ["--author"]) 15 | assert result.exit_code == 0 16 | assert f"{__author__}\n" in result.stdout 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean shell install lock build init test help 2 | .DEFAULT_GOAL := help 3 | 4 | clean: ## Clean build files 5 | rm -rf dist 6 | 7 | shell: ## Activate virtualenv 8 | poetry shell 9 | 10 | install: ## Install dependencies 11 | poetry install 12 | 13 | lock: ## Update poetry.lock 14 | poetry lock 15 | 16 | build: clean ## Build package 17 | poetry build 18 | 19 | init: ## Run the application - Initialize JIRA 20 | JIRA_SERVER="" JIRA_PAT="" jira-amt init 21 | 22 | test: ## Run tests 23 | poetry run pytest 24 | 25 | help: ## Show this help 26 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Arash Hatami 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | authors = [ 3 | "Arash Hatami ", 4 | ] 5 | classifiers = [ 6 | "Topic :: Software Development", 7 | "Topic :: Software Development :: Libraries", 8 | "Programming Language :: Python :: 3.11", 9 | "Programming Language :: Python :: 3.10", 10 | "Programming Language :: Python :: 3.9", 11 | "Programming Language :: Python :: 3.8", 12 | "Programming Language :: Python :: 3", 13 | "License :: OSI Approved :: MIT License", 14 | "Development Status :: 5 - Production/Stable", 15 | ] 16 | description = "Manage Jira assets." 17 | homepage = "https://arash-hatami.ir" 18 | keywords = [ 19 | "jira", 20 | "asset", 21 | ] 22 | license = "MIT" 23 | name = "jira-amt" 24 | packages = [ 25 | {include = "jira_amt"}, 26 | ] 27 | readme = "README.md" 28 | repository = "https://github.com/hatamiarash7/jira-asset-manager" 29 | version = "1.3.1" 30 | 31 | [tool.poetry.dependencies] 32 | python = "^3.8" 33 | requests = "^2" 34 | tomlkit = "^0.12.0" 35 | typer = {extras = [ 36 | "all", 37 | ], version = "^0.12.0"} 38 | urllib3 = "^1" 39 | 40 | [tool.poetry.group.test.dependencies] 41 | pytest = "^8.0.0" 42 | 43 | [tool.poetry.dev-dependencies] 44 | pytest = "^8.0.0" 45 | 46 | [tool.poetry.scripts] 47 | jira-amt = "jira_amt.cli:app" 48 | 49 | [tool.poetry.urls] 50 | "Bug Tracker" = "https://github.com/hatamiarash7/jira-asset-manager/issues" 51 | 52 | [build-system] 53 | build-backend = "poetry.core.masonry.api" 54 | requires = [ 55 | "poetry-core>=1.0.0", 56 | ] 57 | -------------------------------------------------------------------------------- /jira_amt/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tomlkit 3 | import typer 4 | 5 | JIRA_SERVER = os.getenv('JIRA_SERVER') 6 | JIRA_PAT = os.getenv('JIRA_PAT') 7 | JIRA_OBJECT = os.getenv('JIRA_OBJECT') 8 | WORK_DIR = os.path.expanduser('~') + '/.jira' 9 | 10 | 11 | def getConfig(type): 12 | with open( 13 | WORK_DIR+"/"+type+".toml", 14 | mode="r", 15 | encoding="utf-8" 16 | ) as file: 17 | return tomlkit.parse(file.read()) 18 | 19 | 20 | def getSchema(name: str) -> str: 21 | toml = getConfig('schemas') 22 | for schema in toml: 23 | if schema.split(':')[1] == name: 24 | return schema.split(':')[0] 25 | 26 | 27 | def getObject(schema: str, name: str) -> str: 28 | toml = getConfig('schemas') 29 | return toml[schema][name.lower()] 30 | 31 | 32 | def getAttributes(name: str) -> str: 33 | toml = getConfig('attributes') 34 | for attributes in toml: 35 | if attributes == name: 36 | return toml[attributes] 37 | 38 | 39 | def getAttribute(object: str, name: str) -> str: 40 | toml = getConfig('attributes') 41 | 42 | for attributes in toml: 43 | if attributes == object.lower(): 44 | for attribute in toml[attributes]: 45 | if attribute.split('.')[0] == name.lower(): 46 | return toml[attributes][attribute] 47 | 48 | 49 | def getAttributeValue(schema: str, name: str, value: str) -> str: 50 | toml = getConfig('attributes') 51 | 52 | for attributes in toml: 53 | if attributes == schema.lower(): 54 | for attribute in toml[attributes]: 55 | if attribute.split('.')[0] == name.lower(): 56 | if attribute.split('.')[1] == "7": 57 | return getStatus(value) 58 | 59 | return value 60 | 61 | 62 | def getStatus(name: str) -> str: 63 | toml = getConfig('status') 64 | 65 | for status in toml: 66 | if status == name.lower(): 67 | return toml[status] 68 | if isinstance(toml[status], tomlkit.items.Table): 69 | for key in toml[status]: 70 | if key == name.lower(): 71 | return toml[status][key] 72 | 73 | typer.secho(f"Invalid Type: {name}", fg=typer.colors.RED) 74 | raise typer.Exit() 75 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | init: 10 | name: 🚩 Initialize 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Cancel previous workflow 14 | uses: styfle/cancel-workflow-action@0.12.1 15 | with: 16 | access_token: ${{ github.token }} 17 | 18 | release: 19 | name: Release 20 | needs: init 21 | permissions: 22 | contents: write 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | python-version: ["3.8", "3.9", "3.10", "3.11"] 27 | runs-on: ubuntu-22.04 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 31 | 32 | - name: Get version 33 | run: | 34 | APP_VERSION=${GITHUB_REF##*/} 35 | echo "APP_VERSION=${APP_VERSION:1}" >> $GITHUB_ENV 36 | 37 | - name: Setup Python 38 | uses: actions/setup-python@v5 39 | with: 40 | python-version: ${{ matrix.python-version }} 41 | 42 | - name: Cache Poetry 43 | uses: actions/cache@v4 44 | with: 45 | path: ~/.local 46 | key: poetry-1.5-0 47 | 48 | - name: Install Poetry 49 | uses: snok/install-poetry@v1 50 | with: 51 | version: "1.5.0" 52 | virtualenvs-create: true 53 | virtualenvs-in-project: true 54 | 55 | - name: Cache dependencies 56 | id: cache-deps 57 | uses: actions/cache@v4 58 | with: 59 | path: .venv 60 | key: pydeps-${{ hashFiles('**/poetry.lock') }} 61 | 62 | - name: Install dependencies 63 | run: poetry install --no-interaction --no-root 64 | if: steps.cache-deps.outputs.cache-hit != 'true' 65 | 66 | - name: Install project 67 | run: poetry install --no-interaction 68 | 69 | - name: Test 70 | run: poetry run pytest 71 | 72 | - name: Build 73 | run: poetry build 74 | 75 | - name: Publish - Github 76 | uses: softprops/action-gh-release@v2 77 | if: ${{ matrix.python-version == '3.10' }} 78 | with: 79 | fail_on_unmatched_files: true 80 | files: | 81 | ./dist/jira_amt-${{ env.APP_VERSION }}-py3-none-any.whl 82 | ./dist/jira_amt-${{ env.APP_VERSION }}.tar.gz 83 | 84 | - name: Publish - PyPi 85 | uses: JRubics/poetry-publish@v2.0 86 | if: ${{ matrix.python-version == '3.10' }} 87 | with: 88 | pypi_token: ${{ secrets.PYPI_TOKEN }} 89 | ignore_dev_requirements: "yes" 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 105 | __pypackages__/ 106 | 107 | # Celery stuff 108 | celerybeat-schedule 109 | celerybeat.pid 110 | 111 | # SageMath parsed files 112 | *.sage.py 113 | 114 | # Environments 115 | .env 116 | .venv 117 | env/ 118 | venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | # pytype static type analyzer 142 | .pytype/ 143 | 144 | # Cython debug symbols 145 | cython_debug/ 146 | 147 | # IDE 148 | .idea/ 149 | .vscode 150 | 151 | # Other 152 | .DS_Store 153 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jira Asset Manager 2 | 3 | [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) ![GitHub release (release name instead of tag name)](https://img.shields.io/github/v/release/hatamiarash7/jira-asset-manager?sort=date) ![GitHub](https://img.shields.io/github/license/hatamiarash7/jira-asset-manager) 4 | 5 | Manage Jira assets in your code or CLI. 6 | 7 | - [Jira Asset Manager](#jira-asset-manager) 8 | - [Requirements](#requirements) 9 | - [Install](#install) 10 | - [How-to](#how-to) 11 | - [CLI](#cli) 12 | - [Create assets](#create-assets) 13 | - [Update assets](#update-assets) 14 | - [Add comment](#add-comment) 15 | - [Package](#package) 16 | - [Get asset](#get-asset) 17 | - [Create asset](#create-asset) 18 | - [Update asset](#update-asset) 19 | - [Add comment](#add-comment-1) 20 | - [Support 💛](#support-) 21 | - [Contributing 🤝](#contributing-) 22 | 23 | ## Requirements 24 | 25 | - Python 3.8+ 26 | 27 | ## Install 28 | 29 | ```bash 30 | pip install jira-amt 31 | ``` 32 | 33 | ## How-to 34 | 35 | You need to add these environment variables to use the CLI: 36 | 37 | | Variable | Description | 38 | | ----------- | -------------------------------------------------- | 39 | | JIRA_SERVER | Jira server address like `https://jira.domain.com` | 40 | | JIRA_PAT | Your personal access token | 41 | 42 | After setting these variables, you can configure the CLI: 43 | 44 | ```bash 45 | jira-amt init 46 | ``` 47 | 48 | This command will get everything from your Jira server and save them to `~/.jira` directory for later use. With this data, you don't need to know/use ID of each asset/attribute. 49 | 50 | ## CLI 51 | 52 | There is some commands to manage assets. Check them using the `--help` flag. 53 | 54 | ### Create assets 55 | 56 | The asset creation in CLI is not make sense, because you need to enter all attributes in command line. But you can use it in your code. 57 | 58 | ### Update assets 59 | 60 | You can update asset's attribute using it's name. The script will get the asset id from the name automatically. 61 | 62 | ```bash 63 | jira-amt attr 64 | jira-amt attr "ITSM" "Servers" "Server-1" "IP" "1.2.3.4" 65 | ``` 66 | 67 | ### Add comment 68 | 69 | You can add comment to an asset using it's name. The script will get the asset id from the name automatically. 70 | 71 | ```bash 72 | jira-amt comment 73 | jira-amt comment "ITSM" "Servers" "Server-1" "This is a comment" 74 | ``` 75 | 76 | --- 77 | 78 | ## Package 79 | 80 | You can use this package in your code to manage Jira assets. 81 | 82 | ```python 83 | from jira_amt.jira import JiraAssetHandler 84 | 85 | jira = JiraAssetHandler( 86 | server="https://jira.domain.com", 87 | pat="ABCD1234" 88 | ) 89 | ``` 90 | 91 | ### Get asset 92 | 93 | ```python 94 | asset = jira.get_asset("schema", "object", "asset's name") 95 | 96 | print(asset.text) 97 | ``` 98 | 99 | ### Create asset 100 | 101 | ```python 102 | input = { 103 | "Name": "Server-1", 104 | "Status": "Running", 105 | "Environment": "Production", 106 | "OS": "Debian", 107 | "IP": "1.2.3.4" 108 | } 109 | 110 | asset = jira.create_asset("schema", "object", input) 111 | ``` 112 | 113 | ### Update asset 114 | 115 | ```python 116 | asset = jira.get_asset("schema", "object", "asset's name") 117 | 118 | print(asset.text) 119 | ``` 120 | 121 | ### Add comment 122 | 123 | ```python 124 | result = jira.add_comment("schema", "object", "asset's name", "comment") 125 | 126 | print(result.status_code) 127 | ``` 128 | 129 | ## Support 💛 130 | 131 | [![Donate with Bitcoin](https://img.shields.io/badge/Bitcoin-bc1qmmh6vt366yzjt3grjxjjqynrrxs3frun8gnxrz-orange)](https://donatebadges.ir/donate/Bitcoin/bc1qmmh6vt366yzjt3grjxjjqynrrxs3frun8gnxrz) [![Donate with Ethereum](https://img.shields.io/badge/Ethereum-0x0831bD72Ea8904B38Be9D6185Da2f930d6078094-blueviolet)](https://donatebadges.ir/donate/Ethereum/0x0831bD72Ea8904B38Be9D6185Da2f930d6078094) 132 | 133 |
134 | 135 | ## Contributing 🤝 136 | 137 | Don't be shy and reach out to us if you want to contribute 😉 138 | 139 | 1. Fork it! 140 | 2. Create your feature branch: `git checkout -b my-new-feature` 141 | 3. Commit your changes: `git commit -am 'Add some feature'` 142 | 4. Push to the branch: `git push origin my-new-feature` 143 | -------------------------------------------------------------------------------- /jira_amt/jira.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import typer 4 | 5 | from jira_amt import config 6 | 7 | 8 | class JiraAssetHandler: 9 | def __init__(self, server, pat): 10 | self.url = server + '/rest/assets/1.0' 11 | self.headers = { 12 | "Accept": "application/json", 13 | "Content-Type": "application/json", 14 | "Authorization": "Bearer " + pat 15 | } 16 | 17 | # ------ Assets ------ 18 | 19 | def get_asset(self, schema, object, asset): 20 | schema_id = config.getSchema(schema) 21 | path = '/object/navlist/aql' 22 | data = { 23 | "objectTypeId": config.getObject(schema_id+":"+schema, object), 24 | "resultsPerPage": 1, 25 | "includeAttributes": "false", 26 | "objectSchemaId": schema_id, 27 | "qlQuery": f"Name = \"{asset}\"" 28 | } 29 | return self._make_api_call("POST", path, data) 30 | 31 | def create_asset(self, schema, object, attributes): 32 | schema_id = config.getSchema(schema) 33 | 34 | input = [] 35 | 36 | for attribute_name, value in attributes.items(): 37 | object_type_attribute_id = config.getAttribute( 38 | object+"."+schema, 39 | attribute_name 40 | ) 41 | 42 | if object_type_attribute_id is not None and isinstance(value, list): 43 | output_dict = { 44 | "objectTypeAttributeId": object_type_attribute_id, 45 | "objectAttributeValues": [{"value": config.getAttributeValue( 46 | object+"."+schema, 47 | attribute_name, 48 | v 49 | )} for v in value] 50 | } 51 | 52 | input.append(output_dict) 53 | elif object_type_attribute_id is not None: 54 | output_dict = { 55 | "objectTypeAttributeId": object_type_attribute_id, 56 | "objectAttributeValues": [{"value": config.getAttributeValue( 57 | object, 58 | attribute_name, 59 | value 60 | )}] 61 | } 62 | 63 | input.append(output_dict) 64 | 65 | path = '/object/create' 66 | data = { 67 | "objectTypeId": config.getObject(schema_id+":"+schema, object), 68 | "attributes": input 69 | } 70 | return self._make_api_call("POST", path, data) 71 | 72 | def update_asset(self, schema, object, asset_name, attr_name, attr_value): 73 | attr_value = config.getAttributeValue( 74 | object+"."+schema, 75 | attr_name, 76 | attr_value 77 | ) 78 | attr_name = config.getAttribute( 79 | object+"."+schema, 80 | attr_name 81 | ) 82 | 83 | print(attr_value) 84 | print(attr_name) 85 | 86 | object = self.get_asset(schema, object, asset_name) 87 | id = json.loads(object.text)['matchedFilterValues'][0]['objectId'] 88 | 89 | path = f"/object/{id}" 90 | data = { 91 | "objectTypeId": config.JIRA_OBJECT, 92 | "attributes": [ 93 | { 94 | "objectTypeAttributeId": attr_name, 95 | "objectAttributeValues": [{"value": attr_value}] 96 | } 97 | ] 98 | } 99 | return self._make_api_call("PUT", path, data) 100 | 101 | # ------ Comments ------ 102 | 103 | def add_comment(self, schema, object, asset_name, comment): 104 | object = self.get_asset(schema, object, asset_name) 105 | id = json.loads(object.text)['matchedFilterValues'][0]['objectId'] 106 | 107 | path = "/comment/create" 108 | data = { 109 | "comment": comment, 110 | "objectId": f"{id}", 111 | "role": 0 112 | } 113 | 114 | return self._make_api_call("POST", path, data) 115 | 116 | # ------ Objects ------ 117 | 118 | def get_schema(self): 119 | path = '/objectschema/list' 120 | return self._make_api_call("GET", path, {}) 121 | 122 | def get_objecttypes(self, id): 123 | path = f'/objectschema/{id}/objecttypes/flat' 124 | return self._make_api_call("GET", path, {}) 125 | 126 | def get_attributes(self, objectType): 127 | path = f'/objecttype/{objectType}/attributes' 128 | return self._make_api_call("GET", path, {}) 129 | 130 | # ------ Status ------ 131 | 132 | def get_global_statustypes(self): 133 | path = '/config/statustype' 134 | return self._make_api_call("GET", path, {}) 135 | 136 | def get_statustypes(self, id): 137 | path = f'/config/statustype?objectSchemaId={id}' 138 | return self._make_api_call("GET", path, {}) 139 | 140 | def _make_api_call(self, method, path, data): 141 | try: 142 | response = requests.request( 143 | method, 144 | self.url + path, 145 | headers=self.headers, 146 | data=json.dumps(data) 147 | ) 148 | return response 149 | except Exception as e: 150 | typer.secho(f"API call failed: {str(e)}", fg=typer.colors.YELLOW) 151 | return None 152 | -------------------------------------------------------------------------------- /jira_amt/cli.py: -------------------------------------------------------------------------------- 1 | """This module provides the Jira CLI.""" 2 | 3 | import json 4 | from pathlib import Path 5 | 6 | import tomlkit as toml 7 | from tomlkit import document, table, comment as cm, nl 8 | from typing import Optional 9 | import typer 10 | 11 | from jira_amt import ( 12 | __app_name__, __version__, __author__, config, jira 13 | ) 14 | 15 | app = typer.Typer() 16 | 17 | 18 | @app.command() 19 | def init() -> None: 20 | """Initialize Jira CLI.""" 21 | typer.secho("Initializing Jira CLI ...", fg=typer.colors.GREEN) 22 | 23 | # Create work directory 24 | Path(config.WORK_DIR).mkdir(parents=True, exist_ok=True) 25 | 26 | # Create schemas file 27 | schema_list = document() 28 | typer.secho(" -> Fetching schemas", fg=typer.colors.BRIGHT_GREEN) 29 | schema_list.add( 30 | cm(f"Jira Asset Management - Status list - v{__version__}") 31 | ) 32 | schemas = json.loads(get_jira().get_schema().text) 33 | objects = [] 34 | for schema in schemas['objectschemas']: 35 | title = f"{schema['id']}:{schema['name']}" 36 | schema_list.add(title, table()) 37 | objs = json.loads(get_jira().get_objecttypes(schema['id']).text) 38 | for object in objs: 39 | object['schema'] = schema['name'] 40 | objects.append(object) 41 | schema_list[title].add(object['name'].lower(), object['id']) 42 | with open( 43 | config.WORK_DIR+"/schemas.toml", 44 | mode="w", 45 | encoding="utf-8" 46 | ) as file: 47 | toml.dump(schema_list, file) 48 | 49 | # Create attributes file 50 | typer.secho(" -> Fetching attributes", fg=typer.colors.BRIGHT_GREEN) 51 | attr_list = document() 52 | attr_list.add( 53 | cm(f"Jira Asset Management - Status list - v{__version__}") 54 | ) 55 | for object in objects: 56 | result = json.loads(get_jira().get_attributes(object['id']).text) 57 | attributes = table() 58 | for attribute in result: 59 | attributes.add( 60 | attribute['name'].lower() + "." + str(attribute['type']), 61 | attribute['id'] 62 | ) 63 | attr_list.add( 64 | object['name'].lower() + "." + str(object['schema']).lower(), 65 | attributes 66 | ) 67 | with open( 68 | config.WORK_DIR+"/attributes.toml", 69 | mode="w", 70 | encoding="utf-8" 71 | ) as file: 72 | toml.dump(attr_list, file) 73 | 74 | # Create statuses file 75 | typer.secho(" -> Fetching statuses", fg=typer.colors.BRIGHT_GREEN) 76 | status_list = document() 77 | status_list.add( 78 | cm(f"Jira Asset Management - Status list - v{__version__}") 79 | ) 80 | status_list.add(nl()) 81 | globals = json.loads(get_jira().get_global_statustypes().text) 82 | for status in globals: 83 | status_list.add(status['name'].lower(), status['id']) 84 | for schema in schemas['objectschemas']: 85 | locals = json.loads( 86 | get_jira().get_statustypes(schema['id']).text 87 | ) 88 | temp = table() 89 | for status in locals: 90 | temp.add(status['name'].lower(), status['id']) 91 | status_list.add(schema['name'], temp) 92 | 93 | with open( 94 | config.WORK_DIR+"/status.toml", 95 | mode="w", 96 | encoding="utf-8" 97 | ) as file: 98 | toml.dump(status_list, file) 99 | 100 | typer.secho("Done!", fg=typer.colors.GREEN) 101 | 102 | 103 | @app.command() 104 | def comment( 105 | schema: str = typer.Argument( 106 | help="Your schema name" 107 | ), 108 | object: str = typer.Argument( 109 | help="Your object name" 110 | ), 111 | asset: str = typer.Argument( 112 | help="Asset's name" 113 | ), 114 | body: str = typer.Argument( 115 | help="Comment's body" 116 | ) 117 | ) -> None: 118 | """Set a comment for your asset.""" 119 | result = get_jira().add_comment(schema, object, asset, body) 120 | if result: 121 | typer.secho( 122 | f"Comment added to asset '{asset}' successfully.", 123 | fg=typer.colors.GREEN 124 | ) 125 | else: 126 | typer.secho( 127 | f"Error adding comment to asset '{asset}'.", 128 | fg=typer.colors.RED 129 | ) 130 | raise typer.Exit(1) 131 | 132 | 133 | @app.command() 134 | def attr( 135 | schema: str = typer.Argument( 136 | help="Your schema name" 137 | ), 138 | object: str = typer.Argument( 139 | help="Your object name" 140 | ), 141 | asset: str = typer.Argument( 142 | help="Asset's name" 143 | ), 144 | name: str = typer.Argument( 145 | help="Attribute name" 146 | ), 147 | value: str = typer.Argument( 148 | help="Attribute value" 149 | ) 150 | ) -> None: 151 | """Update an attribute of your asset.""" 152 | result = get_jira().update_asset(schema, object, asset, name, value) 153 | if result: 154 | typer.secho( 155 | f"Asset '{asset}' updated successfully.", 156 | fg=typer.colors.GREEN 157 | ) 158 | else: 159 | typer.secho( 160 | f"Error updating asset '{asset}'.", 161 | fg=typer.colors.RED 162 | ) 163 | raise typer.Exit(1) 164 | 165 | 166 | def get_jira() -> jira.JiraAssetHandler: 167 | if not config.JIRA_SERVER: 168 | typer.secho( 169 | "JIRA_SERVER environment variable not set.", 170 | fg=typer.colors.RED, 171 | ) 172 | raise typer.Exit(1) 173 | 174 | if not config.JIRA_PAT: 175 | typer.secho( 176 | "JIRA_PAT environment variable not set.", 177 | fg=typer.colors.RED, 178 | ) 179 | raise typer.Exit(1) 180 | 181 | return jira.JiraAssetHandler( 182 | config.JIRA_SERVER, 183 | config.JIRA_PAT 184 | ) 185 | 186 | 187 | def _version_callback(value: bool) -> None: 188 | if value: 189 | typer.secho(f"{__app_name__} v{__version__}", fg=typer.colors.CYAN) 190 | raise typer.Exit() 191 | 192 | 193 | def _author_callback(value: bool) -> None: 194 | if value: 195 | typer.secho(f"{__author__}", fg=typer.colors.BRIGHT_MAGENTA) 196 | raise typer.Exit() 197 | 198 | 199 | @app.callback() 200 | def main( 201 | version: Optional[bool] = typer.Option( 202 | None, 203 | "--version", 204 | "-v", 205 | help="Show the application's version.", 206 | callback=_version_callback, 207 | is_eager=True, 208 | ), 209 | author: Optional[bool] = typer.Option( 210 | None, 211 | "--author", 212 | "-a", 213 | help="Show the application's author information.", 214 | callback=_author_callback, 215 | is_eager=True, 216 | ) 217 | ) -> None: 218 | return 219 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "certifi" 5 | version = "2023.7.22" 6 | description = "Python package for providing Mozilla's CA Bundle." 7 | optional = false 8 | python-versions = ">=3.6" 9 | files = [ 10 | {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, 11 | {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, 12 | ] 13 | 14 | [[package]] 15 | name = "charset-normalizer" 16 | version = "3.3.0" 17 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 18 | optional = false 19 | python-versions = ">=3.7.0" 20 | files = [ 21 | {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, 22 | {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, 23 | {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, 24 | {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, 25 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, 26 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, 27 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, 28 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, 29 | {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, 30 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, 31 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, 32 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, 33 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, 34 | {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, 35 | {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, 36 | {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, 37 | {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, 38 | {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, 39 | {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, 40 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, 41 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, 42 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, 43 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, 44 | {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, 45 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, 46 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, 47 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, 48 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, 49 | {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, 50 | {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, 51 | {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, 52 | {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, 53 | {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, 54 | {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, 55 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, 56 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, 57 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, 58 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, 59 | {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, 60 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, 61 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, 62 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, 63 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, 64 | {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, 65 | {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, 66 | {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, 67 | {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, 68 | {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, 69 | {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, 70 | {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, 71 | {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, 72 | {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, 73 | {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, 74 | {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, 75 | {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, 76 | {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, 77 | {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, 78 | {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, 79 | {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, 80 | {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, 81 | {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, 82 | {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, 83 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, 84 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, 85 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, 86 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, 87 | {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, 88 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, 89 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, 90 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, 91 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, 92 | {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, 93 | {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, 94 | {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, 95 | {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, 96 | {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, 97 | {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, 98 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, 99 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, 100 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, 101 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, 102 | {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, 103 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, 104 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, 105 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, 106 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, 107 | {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, 108 | {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, 109 | {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, 110 | {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, 111 | ] 112 | 113 | [[package]] 114 | name = "click" 115 | version = "8.1.7" 116 | description = "Composable command line interface toolkit" 117 | optional = false 118 | python-versions = ">=3.7" 119 | files = [ 120 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 121 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 122 | ] 123 | 124 | [package.dependencies] 125 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 126 | 127 | [[package]] 128 | name = "colorama" 129 | version = "0.4.6" 130 | description = "Cross-platform colored terminal text." 131 | optional = false 132 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 133 | files = [ 134 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 135 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 136 | ] 137 | 138 | [[package]] 139 | name = "exceptiongroup" 140 | version = "1.1.3" 141 | description = "Backport of PEP 654 (exception groups)" 142 | optional = false 143 | python-versions = ">=3.7" 144 | files = [ 145 | {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, 146 | {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, 147 | ] 148 | 149 | [package.extras] 150 | test = ["pytest (>=6)"] 151 | 152 | [[package]] 153 | name = "idna" 154 | version = "3.7" 155 | description = "Internationalized Domain Names in Applications (IDNA)" 156 | optional = false 157 | python-versions = ">=3.5" 158 | files = [ 159 | {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, 160 | {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, 161 | ] 162 | 163 | [[package]] 164 | name = "iniconfig" 165 | version = "2.0.0" 166 | description = "brain-dead simple config-ini parsing" 167 | optional = false 168 | python-versions = ">=3.7" 169 | files = [ 170 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 171 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 172 | ] 173 | 174 | [[package]] 175 | name = "markdown-it-py" 176 | version = "3.0.0" 177 | description = "Python port of markdown-it. Markdown parsing, done right!" 178 | optional = false 179 | python-versions = ">=3.8" 180 | files = [ 181 | {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, 182 | {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, 183 | ] 184 | 185 | [package.dependencies] 186 | mdurl = ">=0.1,<1.0" 187 | 188 | [package.extras] 189 | benchmarking = ["psutil", "pytest", "pytest-benchmark"] 190 | code-style = ["pre-commit (>=3.0,<4.0)"] 191 | compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] 192 | linkify = ["linkify-it-py (>=1,<3)"] 193 | plugins = ["mdit-py-plugins"] 194 | profiling = ["gprof2dot"] 195 | rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] 196 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 197 | 198 | [[package]] 199 | name = "mdurl" 200 | version = "0.1.2" 201 | description = "Markdown URL utilities" 202 | optional = false 203 | python-versions = ">=3.7" 204 | files = [ 205 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 206 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 207 | ] 208 | 209 | [[package]] 210 | name = "packaging" 211 | version = "23.2" 212 | description = "Core utilities for Python packages" 213 | optional = false 214 | python-versions = ">=3.7" 215 | files = [ 216 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 217 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 218 | ] 219 | 220 | [[package]] 221 | name = "pluggy" 222 | version = "1.4.0" 223 | description = "plugin and hook calling mechanisms for python" 224 | optional = false 225 | python-versions = ">=3.8" 226 | files = [ 227 | {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, 228 | {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, 229 | ] 230 | 231 | [package.extras] 232 | dev = ["pre-commit", "tox"] 233 | testing = ["pytest", "pytest-benchmark"] 234 | 235 | [[package]] 236 | name = "pygments" 237 | version = "2.16.1" 238 | description = "Pygments is a syntax highlighting package written in Python." 239 | optional = false 240 | python-versions = ">=3.7" 241 | files = [ 242 | {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, 243 | {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, 244 | ] 245 | 246 | [package.extras] 247 | plugins = ["importlib-metadata"] 248 | 249 | [[package]] 250 | name = "pytest" 251 | version = "8.1.2" 252 | description = "pytest: simple powerful testing with Python" 253 | optional = false 254 | python-versions = ">=3.8" 255 | files = [ 256 | {file = "pytest-8.1.2-py3-none-any.whl", hash = "sha256:6c06dc309ff46a05721e6fd48e492a775ed8165d2ecdf57f156a80c7e95bb142"}, 257 | {file = "pytest-8.1.2.tar.gz", hash = "sha256:f3c45d1d5eed96b01a2aea70dee6a4a366d51d38f9957768083e4fecfc77f3ef"}, 258 | ] 259 | 260 | [package.dependencies] 261 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 262 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 263 | iniconfig = "*" 264 | packaging = "*" 265 | pluggy = ">=1.4,<2.0" 266 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 267 | 268 | [package.extras] 269 | testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 270 | 271 | [[package]] 272 | name = "requests" 273 | version = "2.31.0" 274 | description = "Python HTTP for Humans." 275 | optional = false 276 | python-versions = ">=3.7" 277 | files = [ 278 | {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, 279 | {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, 280 | ] 281 | 282 | [package.dependencies] 283 | certifi = ">=2017.4.17" 284 | charset-normalizer = ">=2,<4" 285 | idna = ">=2.5,<4" 286 | urllib3 = ">=1.21.1,<3" 287 | 288 | [package.extras] 289 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 290 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 291 | 292 | [[package]] 293 | name = "rich" 294 | version = "13.6.0" 295 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 296 | optional = false 297 | python-versions = ">=3.7.0" 298 | files = [ 299 | {file = "rich-13.6.0-py3-none-any.whl", hash = "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245"}, 300 | {file = "rich-13.6.0.tar.gz", hash = "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef"}, 301 | ] 302 | 303 | [package.dependencies] 304 | markdown-it-py = ">=2.2.0" 305 | pygments = ">=2.13.0,<3.0.0" 306 | typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} 307 | 308 | [package.extras] 309 | jupyter = ["ipywidgets (>=7.5.1,<9)"] 310 | 311 | [[package]] 312 | name = "shellingham" 313 | version = "1.5.3" 314 | description = "Tool to Detect Surrounding Shell" 315 | optional = false 316 | python-versions = ">=3.7" 317 | files = [ 318 | {file = "shellingham-1.5.3-py2.py3-none-any.whl", hash = "sha256:419c6a164770c9c7cfcaeddfacb3d31ac7a8db0b0f3e9c1287679359734107e9"}, 319 | {file = "shellingham-1.5.3.tar.gz", hash = "sha256:cb4a6fec583535bc6da17b647dd2330cf7ef30239e05d547d99ae3705fd0f7f8"}, 320 | ] 321 | 322 | [[package]] 323 | name = "tomli" 324 | version = "2.0.1" 325 | description = "A lil' TOML parser" 326 | optional = false 327 | python-versions = ">=3.7" 328 | files = [ 329 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 330 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 331 | ] 332 | 333 | [[package]] 334 | name = "tomlkit" 335 | version = "0.12.5" 336 | description = "Style preserving TOML library" 337 | optional = false 338 | python-versions = ">=3.7" 339 | files = [ 340 | {file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"}, 341 | {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, 342 | ] 343 | 344 | [[package]] 345 | name = "typer" 346 | version = "0.12.3" 347 | description = "Typer, build great CLIs. Easy to code. Based on Python type hints." 348 | optional = false 349 | python-versions = ">=3.7" 350 | files = [ 351 | {file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"}, 352 | {file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"}, 353 | ] 354 | 355 | [package.dependencies] 356 | click = ">=8.0.0" 357 | rich = ">=10.11.0" 358 | shellingham = ">=1.3.0" 359 | typing-extensions = ">=3.7.4.3" 360 | 361 | [[package]] 362 | name = "typing-extensions" 363 | version = "4.8.0" 364 | description = "Backported and Experimental Type Hints for Python 3.8+" 365 | optional = false 366 | python-versions = ">=3.8" 367 | files = [ 368 | {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, 369 | {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, 370 | ] 371 | 372 | [[package]] 373 | name = "urllib3" 374 | version = "1.26.18" 375 | description = "HTTP library with thread-safe connection pooling, file post, and more." 376 | optional = false 377 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 378 | files = [ 379 | {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, 380 | {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, 381 | ] 382 | 383 | [package.extras] 384 | brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 385 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 386 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 387 | 388 | [metadata] 389 | lock-version = "2.0" 390 | python-versions = "^3.8" 391 | content-hash = "e5b282a5d9f5f54ae642f0d0e64a8bf47316daae746cf3ecc81f9b8a56de98f3" 392 | --------------------------------------------------------------------------------