diff --git a/eng/pylintrc b/eng/pylintrc index 0a685059adfc..2e3a44236e89 100644 --- a/eng/pylintrc +++ b/eng/pylintrc @@ -21,7 +21,7 @@ load-plugins=pylint_guidelines_checker # Let's black deal with bad-continuation # Added disables from super-with-arguments -disable=useless-object-inheritance,missing-docstring,locally-disabled,fixme,cyclic-import,too-many-arguments,invalid-name,duplicate-code,too-few-public-methods,consider-using-f-string,super-with-arguments,redefined-builtin,import-outside-toplevel,client-suffix-needed,unnecessary-dunder-call,unnecessary-ellipsis,client-paging-methods-use-list,consider-using-max-builtin,too-many-lines,possibly-used-before-assignment,do-not-hardcode-dedent, do-not-log-raised-errors, do-not-log-exceptions +disable=useless-object-inheritance,missing-docstring,locally-disabled,fixme,cyclic-import,too-many-arguments,invalid-name,duplicate-code,too-few-public-methods,consider-using-f-string,super-with-arguments,redefined-builtin,import-outside-toplevel,client-suffix-needed,unnecessary-dunder-call,unnecessary-ellipsis,client-paging-methods-use-list,consider-using-max-builtin,too-many-lines,possibly-used-before-assignment,do-not-hardcode-dedent, [FORMAT] diff --git a/eng/tox/tox.ini b/eng/tox/tox.ini index f8e3702cdfd4..b4e5afcb5a37 100644 --- a/eng/tox/tox.ini +++ b/eng/tox/tox.ini @@ -102,7 +102,7 @@ commands = [testenv:next-pylint] description=Lints a package with pylint (version {[testenv:next-pylint]pylint_version}) -pylint_version=3.2.7 +pylint_version=3.3.6 skipsdist = true skip_install = true usedevelop = false @@ -117,7 +117,7 @@ deps = PyGitHub>=1.59.0 commands = python -m pip install pylint=={[testenv:next-pylint]pylint_version} - python -m pip install azure-pylint-guidelines-checker==0.5.4 --index-url="https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/" + python -m pip install {repository_root}/../azure-sdk-tools/tools/pylint-extensions/azure-pylint-guidelines-checker python {repository_root}/eng/tox/create_package_and_install.py \ -d {envtmpdir}/dist \ -p {tox_root} \ diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/.pylintrc b/tools/azure-sdk-tools/linting_tools/lint_test_bench/.pylintrc new file mode 100644 index 000000000000..455e627e2deb --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/.pylintrc @@ -0,0 +1,65 @@ +[MASTER] +reports=no +init-hook='import sys; sys.path.append(".");' + +# PYLINT DIRECTORY BLACKLIST. +ignore-paths= + azure\\mixedreality\\remoterendering\\_api_version.py, + azure/mixedreality/remoterendering/_api_version.py, + (?:.*[/\\]|^)projects/(models/_models.py|_model_base.py|operations/_operations.py|aio/operations/_operations.py)$, + # Exclude any path that contains the following directory names + (?:.*[/\\]|^)(?:_vendor|_generated|_restclient|samples|examples|test|tests|doc|\.tox)(?:[/\\]|$) + +load-plugins=pylint_guidelines_checker + +[MESSAGES CONTROL] +# For all codes, run 'pylint --list-msgs' or go to 'https://pylint.pycqa.org/en/latest/technical_reference/features.html' +# locally-disabled: Warning locally suppressed using disable-msg +# cyclic-import: because of https://github.com/PyCQA/pylint/issues/850 +# too-many-arguments: Due to the nature of the CLI many commands have large arguments set which reflect in large arguments set in corresponding methods. +# Let's black deal with bad-continuation + +# Added disables from super-with-arguments +disable=useless-object-inheritance,missing-docstring,locally-disabled,fixme,cyclic-import,too-many-arguments,invalid-name,duplicate-code,too-few-public-methods,consider-using-f-string,super-with-arguments,redefined-builtin,import-outside-toplevel,client-suffix-needed,unnecessary-dunder-call,unnecessary-ellipsis,client-paging-methods-use-list,consider-using-max-builtin,too-many-lines,possibly-used-before-assignment, protected-access + + +[FORMAT] +max-line-length=120 + +[VARIABLES] +# Tells whether we should check for unused import in __init__ files. +init-import=yes + +[DESIGN] +# Maximum number of locals for function / method body +max-locals=25 +# Maximum number of branch for function / method body +max-branches=20 +# Maximum number of instance attributes for class +max-attributes=10 +# Maximum number of ancestors +max-parents=15 + +[SIMILARITIES] +min-similarity-lines=10 + +[BASIC] +# Naming hints based on PEP 8 (https://www.python.org/dev/peps/pep-0008/#naming-conventions). +# Consider these guidelines and not hard rules. Read PEP 8 for more details. + +# The invalid-name checker must be **enabled** for these hints to be used. +include-naming-hint=yes + +module-naming-style=snake_case +const-naming-style=UPPER_CASE +class-naming-style=PascalCase +class-attribute-naming-style=snake_case +attr-naming-style=snake_case +method-naming-style=snake_case +function-naming-style=snake_case +argument-naming-style=snake_case +variable-naming-style=snake_case +inlinevar-naming-style=snake_case + +[TYPECHECK] +generated-members=js.* \ No newline at end of file diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/README.md b/tools/azure-sdk-tools/linting_tools/lint_test_bench/README.md new file mode 100644 index 000000000000..c1b3a4d9a8a6 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/README.md @@ -0,0 +1,27 @@ +# README + +## Initial Setup + + +Install the required packages using pip: + +```bash +pip install -r requirements.txt +``` + +## Set the following environment variables: +```bash +export AZURE_OPENAI_ENDPOINT="" +export AZURE_OPENAI_MODEL="" +export AZURE_OPENAI_VERSION="" +export AZURE_OPENAI_KEY="" +``` + + +## Run the test bench +```bash +python pylint_test_bench.py +``` + +### Test Bench Output +The test bench will run pylint on the files in the /test_files directory. The final output will be saved under the /output-logs directory. To see the logging for a specific file, you can check the corresponding log file in the directory you are running in. The log files are named after the files they correspond to, with a .log extension. The output fixed pylint code will be saved under the /fixed_files directory. The fixed files are named after the original files, with a timestamp added to the name as they are iterated over. The output will also be printed to the console. \ No newline at end of file diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/pylint_test_bench.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/pylint_test_bench.py new file mode 100644 index 000000000000..dbbe470a4101 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/pylint_test_bench.py @@ -0,0 +1,291 @@ +from azure.identity import DefaultAzureCredential +from openai import AzureOpenAI +import os +import logging +import sys +import uuid +import azure.core +from azure.identity import get_bearer_token_provider +from pathlib import Path +from datetime import datetime +from azure.ai.evaluation import GroundednessEvaluator, TaskAdherenceEvaluator +from pylint.lint import Run + +token_provider = get_bearer_token_provider( + DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default" +) +# Initialize Azure OpenAI client +client = AzureOpenAI( + api_version=os.environ["AZURE_OPENAI_VERSION"], # Update this to the latest API version + azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"], + azure_ad_token_provider=token_provider, +) + +EVAL_RESULTS = {} +PYLINT_SCORES = {} + +FILE_PROMPT = """ +# Pylint + +## Running Pylint + +## Fixing Pylint Warnings + +### Dos and Don'ts +- DO use the table in https://github.com/Azure/azure-sdk-tools/blob/main/tools/pylint-extensions/azure-pylint-guidelines-checker/README.md and the code examples as a guide on how to fix each rule. +- DO refer to the pylint documentation: https://pylint.readthedocs.io/en/stable/user_guide/checkers/features.html. + + +- DO NOT solve a pylint warning if you are not 100% confident about the answer. If you think your approach might not be the best, stop trying to fix the warning and leave it as is. +- DO NOT create a new file when solving a pylint error, all solutions must remain in the current file. +- DO NOT import a module or modules that do not exist to solve a pylint warning. +- DO NOT add new dependencies or imports to the project to solve a pylint warning. +- DO NOT make larger changes where a smaller change would fix the issue. +- DO NOT change the code style or formatting of the code unless it is necessary to fix a pylint warning. +- DO NOT delete code or files unless it is necessary to fix a warning. + +YOU MUST output ONLY the fixed code, and nothing else. no comments, no explanations, no additional text. +""" + +def my_custom_logger(logger_name, level=logging.DEBUG): + """ + Method to return a custom logger with the given name and level + """ + logger = logging.getLogger(logger_name) + logger.setLevel(level) + format_string = ("%(asctime)s — %(name)s — %(levelname)s — %(funcName)s:" + "%(lineno)d — %(message)s") + log_format = logging.Formatter(format_string) + + # Creating and adding the console handler + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setFormatter(log_format) + logger.addHandler(console_handler) + + # Creating and adding the file handler + file_handler = logging.FileHandler(logger_name, mode='a') + file_handler.setFormatter(log_format) + logger.addHandler(file_handler) + + return logger + +def ensure_directories(): + """Create necessary directories for file organization.""" + base_dir = Path(__file__).parent + dirs = ['test_files', 'fixed_files', 'backup_files', 'output_logs'] + for dir_name in dirs: + (base_dir / dir_name).mkdir(exist_ok=True) + return base_dir + +def get_test_cases(): + """ + Read test cases from local test_files directory. + Returns: + list: A list of dictionaries containing file name and content + """ + test_cases = [] + test_files_dir = Path(__file__).parent / 'test_files' + + # Create test_files directory if it doesn't exist + test_files_dir.mkdir(exist_ok=True) + + # Read all .py files from test_files directory + for test_file in test_files_dir.glob('*.py'): + with open(test_file, 'r', encoding='utf-8') as f: + test_cases.append({ + 'name': test_file.name, + 'content': f + }) + + if not test_cases: + print("Warning: No test files found in test_files directory") + + return test_cases + +def fix_file(file_path: str, logger) -> dict: + """ + Fix pylint issues in the given file and save the changes. + Args: + file_path: Path to the file that needs fixing + Returns: + dict: Status of the operation including original and fixed content + """ + try: + base_dir = ensure_directories() + file_name = Path(file_path).name + + # Define output paths + fixed_path = base_dir / 'fixed_files' / f"{file_name}_fixed_.py" if "_fixed" not in file_name else base_dir / 'fixed_files' / file_name + + # Read the original content + with open(file_path, 'r', encoding='utf-8') as f: + original_content = f.read() + + logger.debug(f"MODEL: {os.environ['AZURE_OPENAI_MODEL']}") + + + # Get fixed content from Azure OpenAI + response = client.chat.completions.create( + model=os.environ["AZURE_OPENAI_MODEL"], + messages=[ + {"role": "system", "content": FILE_PROMPT}, + {"role": "user", "content": f"Fix pylint issues in this code:\n\n{original_content}"} + ], + ) + + fixed_content = response.choices[0].message.content.strip() + + # Remove any markdown code blocks if they exist + fixed_content = fixed_content.replace('```python', '').replace('```', '').strip() + + logger.debug(f"FIXED CONTENT: {fixed_content}") + + # Write fixed content to fixed_files directory + with open(fixed_path, 'w', encoding='utf-8') as f: + f.write(fixed_content) + f.write("\n") # Ensure a newline at the end of the file + + return { + 'status': 'success', + 'original_file': file_path, + 'fixed_file': fixed_path, + 'original_content': original_content, + 'fixed_content': fixed_content + } + + except Exception as e: + print(f"Error while fixing file: {e}") + return { + 'status': 'error', + 'error': str(e), + 'file': file_path + } + +def run_pylint(file_path: str, logger) -> dict: + """ + Run pylint on the given file using the project's pylintrc configuration. + Args: + file_path: Path to the file to analyze + Returns: + dict: Pylint analysis results including score and messages + """ + try: + + + # Get path to pylintrc + pylintrc_path = Path(__file__).parent / '.pylintrc' + + if not pylintrc_path.exists(): + raise FileNotFoundError(f"pylintrc not found at {pylintrc_path}") + + # Run pylint with configuration + results = Run( + [ + str(file_path), + f'--rcfile={pylintrc_path}', + '--output-format=json' + ], + exit=False + ) + + messages = results.linter.reporter.messages + logger.debug(f"PYLINT MESSAGES: {messages}") + + logger.debug(f"PYLINT SCORE: {results.linter.stats.global_note}") + pylint_name = file_path.split("/")[-1].split(".py")[0] + try: + PYLINT_SCORES[pylint_name].append(results.linter.stats.global_note) + except: + PYLINT_SCORES[pylint_name] = [results.linter.stats.global_note] + + return results.linter.stats.global_note + + except Exception as e: + return { + 'status': 'error', + 'error': str(e), + 'file': file_path + } + +def evaluate_fixes(file_path, original_content, fixed_content): + """ + Use azure-ai-evaluation to compare the original and fixed content. + Args: + file_path (str): The path of the file being evaluated. + original_content (str): The original file content. + fixed_content (str): The fixed file content. + Returns: + dict: Evaluation results. + """ + + model_config = { + "azure_endpoint": os.environ["AZURE_OPENAI_ENDPOINT"], + "azure_deployment": os.environ['AZURE_OPENAI_MODEL'], + "api_version": os.environ["AZURE_OPENAI_VERSION"], + "api_key": os.environ.get("AZURE_OPENAI_KEY"), + } + + pylint_name = file_path.split("/")[-1].split(".py")[0] + + # Use the evaluate function with the JSONL file + eval = GroundednessEvaluator(model_config=model_config) + adehere = TaskAdherenceEvaluator(model_config=model_config, threshold=3) + + + evaluation_result = eval(context=FILE_PROMPT + f"Fix pylint issues in this code:\n\n{original_content}", response=fixed_content) + adherence_result = adehere(query=FILE_PROMPT + f"Fix pylint issues in this code:\n\n{original_content}", response=fixed_content) + EVAL_RESULTS[pylint_name] = evaluation_result , adherence_result + +# Example usage +if __name__ == "__main__": + ensure_directories() + test_cases = get_test_cases() + next = uuid.uuid4() + try: + for test_case in test_cases: + logger = my_custom_logger(f"Logger_{test_case['name']}_{next}.log") + logger.debug(test_case['name']) + file_path = Path(__file__).parent / 'test_files' / test_case['name'] + + print(f"Processing file: {file_path}") + + # Run pylint on both original file + logger.debug("Running pylint on original file...") + original_pylint = run_pylint(str(file_path), logger) + logger.debug("-----"*80) + + fixed_pylint = original_pylint + counter = 0 + while fixed_pylint != 10 and counter < 3: + # Fix the file and get results + logger.debug("Fixing file...") + result = fix_file(str(file_path), logger) + logger.debug("Running pylint on fixed file...") + fixed_pylint = run_pylint(str(result['fixed_file']), logger) + + # Evaluate the fixes + evaluate_fixes(str(result['fixed_file']), result['original_content'], result['fixed_content']) + + # Update file_path for next iteration + logger.debug("-----"*80) + file_path = str(result['fixed_file']) + counter += 1 + logger.debug("-----"*80) + logger.debug("\n\n\n") + base_dir = Path(__file__).parent + with open(f"{base_dir}/output_logs/final_{next}", 'w', encoding='utf-8') as f: + for key, value in PYLINT_SCORES.items(): + f.write(f"{key}: {value}") + f.write("\n") + # Add corresponding evaluation scores if available + if key in EVAL_RESULTS: + f.write(f"Evaluation Results for {key}:") + f.write("\n") + f.write(f"Evaluation Score: {EVAL_RESULTS[key][0]}") + f.write("\n") + f.write(f"Evaluation Score: {EVAL_RESULTS[key][1]}") + f.write("\n") + f.write("-----"*80) + f.write("\n") + except Exception as e: + raise e diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/requirements.txt b/tools/azure-sdk-tools/linting_tools/lint_test_bench/requirements.txt new file mode 100644 index 000000000000..d1f21c3e34d3 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/requirements.txt @@ -0,0 +1,5 @@ +azure-identity +openai +pylint==3.2.7 +azure-core +../../../../../azure-sdk-tools/tools/pylint-extensions/azure-pylint-guidelines-checker/ -e . \ No newline at end of file diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/setup.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/setup.py new file mode 100644 index 000000000000..b4b9a882095a --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/setup.py @@ -0,0 +1,25 @@ +"""Setup script for pylintBot package.""" + +import os +from setuptools import setup, find_packages + +setup( + name="pylintbot", + version="0.1.0", + packages=find_packages(), + install_requires=[ + "azure-core>=1.29.5", + "openai>=1.12.0", + "azure-identity>=1.12.0" + ], + author="Microsoft Corporation", + author_email="azpysdkhelp@microsoft.com", + url="https://github.com/Azure/azure-sdk-for-python", + description="Azure SDK Linting Test Bench using OpenAI SDK", + python_requires=">=3.9", + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], +) \ No newline at end of file diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_admonition_docstring.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_admonition_docstring.py new file mode 100644 index 000000000000..cf581bb7ac74 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_admonition_docstring.py @@ -0,0 +1,21 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# This code violates docstring-admonition-needs-newline +class DeadClient(): + def get_function(self, x: int) -> int: + """ + :param x: This is a parameter + :type x: int + :return: int y + :rtype: int + docstring + .. admonition:: Example: + This is Example content. + Should support multi-line. + Can also include file: + .. literalinclude:: ../samples/sample_detect_language.py + """ + y = x+1 + return y diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_api_version.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_api_version.py new file mode 100644 index 000000000000..1210c8ebc09a --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_api_version.py @@ -0,0 +1,16 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import Any +# This code violates check-api-version-kwarg +class ClassNameClient(): + def __init__(self, credential: str, **kwargs: Any) -> None: + """ + :param credential: The credential to use for authentication. + :type credential: str + :keyword eight: The eighth parameter. + :paramtype eight: str + """ + self.credential: str = credential + self.eight: str = kwargs.get("eight") diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_async_client_naming.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_async_client_naming.py new file mode 100644 index 000000000000..d45ba5410cbc --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_async_client_naming.py @@ -0,0 +1,47 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import Any +# This code violates async-client-bad-name +class AsyncSomeClient(): + def __init__(self, credential: str, *, api_version: str ="2018", **kwargs: Any) -> None: + """ + :param credential: The credential to use for authentication. + :type credential: str + :keyword api_version: The API version to use for the client. + :paramtype api_version: str + :keyword eight: The eighth parameter. + :paramtype eight: str + """ + self.credential: str = credential + self.eight: str = kwargs.get("eight") + self._api_version: str = api_version + +class _AsyncSomeClient(): + def __init__(self, credential: str, *, api_version: str ="2018", **kwargs: Any) -> None: + """ + :param credential: The credential to use for authentication. + :type credential: str + :keyword api_version: The API version to use for the client. + :paramtype api_version: str + :keyword eight: The eighth parameter. + :paramtype eight: str + """ + self.credential: str = credential + self.eight: str = kwargs.get("eight") + self._api_version: str = api_version + +class AsyncSomeClientBase(): + def __init__(self, credential: str, *, api_version: str ="2018", **kwargs: Any) -> None: + """ + :param credential: The credential to use for authentication. + :type credential: str + :keyword api_version: The API version to use for the client. + :paramtype api_version: str + :keyword eight: The eighth parameter. + :paramtype eight: str + """ + self.credential: str = credential + self.eight: str = kwargs.get("eight") + self._api_version: str = api_version diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_constructor_missing_kwargs.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_constructor_missing_kwargs.py new file mode 100644 index 000000000000..65471d593099 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_constructor_missing_kwargs.py @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +# This code violates missing-client-constructor-parameter-kwargs +class ClassNameClient(): + def __init__(self, credential: str, *, api_version:str = "2018") -> None: + """ + :param credential: The credential to use for authentication. + :type credential: str + :keyword api_version: The API version to use. + :paramtype api_version: str + """ + self.credential: str = credential + self.api_version: str = api_version diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_constructor_takes_credential.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_constructor_takes_credential.py new file mode 100644 index 000000000000..a4c1c7677236 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_constructor_takes_credential.py @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import Any +# This code violates missing-client-constructor-parameter-credential +class ClassNameClient(): + def __init__(self, *, api_version: str = "2018", **kwargs: Any) -> None: + """ + :keyword eight: The eighth parameter. + :paramtype eight: str + :keyword api_version: The API version to use. + :paramtype api_version: str + """ + self.api_version: str = api_version + self.eight: str = kwargs.get("eight") diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_has_approved_name_method_prefix.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_has_approved_name_method_prefix.py new file mode 100644 index 000000000000..9de55b80d90f --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_has_approved_name_method_prefix.py @@ -0,0 +1,58 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# This code violates unapproved-client-method-name-prefix + +class UnapprovedClient(): + def build_configuration(self) -> None: + # This method sets up a configuration for the client + pass + + def generate_thing(self) -> None: + # This method creates a new thing + pass + + def make_thing(self) -> None: + # This method also creates a new thing + pass + + def insert_thing(self) -> None: + # This method updates an existing thing + pass + + def put_thing(self) -> None: + # This method adds a new thing + pass + + def creates_configuration(self) -> None: + pass + + def gets_thing(self) -> None: + pass + + def lists_thing(self) -> None: + pass + + def upserts_thing(self) -> None: + pass + + def sets_thing(self) -> None: + pass + + def updates_thing(self) -> None: + pass + + def replaces_thing(self) -> None: + pass + + def appends_thing(self) -> None: + pass + + def adds_thing(self) -> None: + pass + + def deletes_thing(self) -> None: + pass + + def removes_thing(self) -> None: + pass diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_kwargs_for_config.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_kwargs_for_config.py new file mode 100644 index 000000000000..27aa09d8821f --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_kwargs_for_config.py @@ -0,0 +1,26 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from azure.core.configuration import Configuration +from azure.core.pipeline.policies import RedirectPolicy, ProxyPolicy + +# This code violates config-missing-kwargs-in-policy + +class TestFindsConfigPoliciesWithoutKwargs(): + def create_configuration(self, **kwargs): + config = Configuration(**kwargs) + config.headers_policy = kwargs.get('headers_policy') + config.user_agent_policy = kwargs.get('user_agent_policy') + config.retry_policy = kwargs.get('retry_policy') + config.redirect_policy = RedirectPolicy(**kwargs) + config.logging_policy = kwargs.get('logging_policy') + config.proxy_policy = ProxyPolicy() + return config + + @staticmethod + def create_config(credential, api_version="08", **kwargs): + config = Configuration(**kwargs) + config.credential = credential + config.api_version = api_version + return config diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_method_missing_tracing.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_method_missing_tracing.py new file mode 100644 index 000000000000..15bf7e016738 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_method_missing_tracing.py @@ -0,0 +1,12 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import List +from azure.core.tracing.decorator_async import distributed_trace_async +# This code violates client-method-has-tracing-decorator +class Some2Client(): + + @distributed_trace_async + def get_thing(self, **kwargs) -> List[str]: + return [] diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_methods_do_not_use_double_underscore.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_methods_do_not_use_double_underscore.py new file mode 100644 index 000000000000..4f6f05597132 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_client_methods_do_not_use_double_underscore.py @@ -0,0 +1,16 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +# This code violates client-method-name-no-double-underscore + +class Some1Client(): + async def __create_configuration(): + pass + + async def __get_thing(): + pass + + async def __list_thing(): + pass \ No newline at end of file diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_dedent.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_dedent.py new file mode 100644 index 000000000000..f2b2e818212b --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_dedent.py @@ -0,0 +1,30 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------- + +# This code violates do-not-hardcode-dedent + +def function_foo1(x, y, z): + """docstring + + :param x: x + :type x: int + :param y: y + :type y: int + :param z: z + :type z: int + :return: int sum + :rtype: int + .. admonition:: Example: + This is Example content. + Should support multi-line. + Can also include file: + + .. literalinclude:: ../samples/sample_authentication.py + :start-after: [START auth_from_connection_string] + :end-before: [END auth_from_connection_string] + :language: python + :dedent: 8 + """ + return x + y + z diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_delete_wrong_return_type.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_delete_wrong_return_type.py new file mode 100644 index 000000000000..9a124de00a81 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_delete_wrong_return_type.py @@ -0,0 +1,15 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from azure.core.tracing.decorator import distributed_trace + +# This code violates delete-operation-wrong-return-type + +class MyClient(): + @distributed_trace + def delete_some_function(self, **kwargs) -> str: + client = kwargs.get("some_client") + client.delete() + # Do something with the key + return self diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_do_not_hardcode_conn_verify.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_do_not_hardcode_conn_verify.py new file mode 100644 index 000000000000..380fed060a24 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_do_not_hardcode_conn_verify.py @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +# This code violates do-not-harcode-connection-verify + +from azure.core import PipelineClient +from azure.core.pipeline.transport import HttpRequest + +def test_do_not_hardcode_conn_verify(**kwargs): + client: PipelineClient = PipelineClient(base_url="endpoint", policies={}, **kwargs) + client._pipeline._transport.connection_verify = False + response = client._pipeline.run( + HttpRequest("GET", "https://example.com"), + **kwargs + ) + return response diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_do_not_import_asyncio.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_do_not_import_asyncio.py new file mode 100644 index 000000000000..b21cd892876e --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_do_not_import_asyncio.py @@ -0,0 +1,25 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from asyncio import sleep +from azure.core.pipeline import AsyncPipeline +from azure.core.pipeline.transport import HttpRequest as PipelineTransportHttpRequest +from azure.core.pipeline.policies import ( + UserAgentPolicy, + AsyncRedirectPolicy, +) +from azure.core.pipeline.transport import ( + AsyncHttpTransport, + HttpResponse, +) +# This code violates do-not-import-asyncio, no-legacy-azure-core-http-response-import + +async def main(): + port = 8080 + request = PipelineTransportHttpRequest("GET", "http://localhost:{}/basic/string".format(port)) + policies = [UserAgentPolicy("myusergant"), AsyncRedirectPolicy()] + async with AsyncPipeline(AsyncHttpTransport, policies=policies) as pipeline: + response: HttpResponse = await pipeline.run(request) + await sleep(0.1) + print(response.http_response.status_code) diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_enum.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_enum.py new file mode 100644 index 000000000000..49a50559a3f5 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_enum.py @@ -0,0 +1,17 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from enum import Enum +from azure.core import CaseInsensitiveEnumMeta + +# This code violates enum-must-be-uppercase and enum-must-inherit-case-insensitive-enum-meta + +class MyBadEnum2(str, Enum, metaclass=CaseInsensitiveEnumMeta): + One = "one" + +class MyGoodEnum2(str, Enum, metaclass=CaseInsensitiveEnumMeta): + ONE = "one" + +class MyBadEnum(str, Enum): + One = "one" diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_exception_log.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_exception_log.py new file mode 100644 index 000000000000..cba236390259 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_exception_log.py @@ -0,0 +1,27 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import logging + +# This code violates do-not-log-exceptions-if-not-debug + +def add(a, b): + """ + Add two numbers together. + :param a: The first number. + :type a: int or float + :param b: The second number. + :type b: int or float + :return: The sum of the two numbers. + """ + logging.debug("Adding %s and %s", a, b) + try: + return a + b + except TypeError as e: + logging.info( + "This is a TypeError: %s", + e, + ) + + diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_exception_raise_log.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_exception_raise_log.py new file mode 100644 index 000000000000..858e5ce466a5 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_exception_raise_log.py @@ -0,0 +1,26 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +import logging + +# This code violates do-not-log-raised-errors + +def add(a, b): + """ + Add two numbers together. + :param a: The first number. + :type a: int or float + :param b: The second number. + :type b: int or float + :return: The sum of the two numbers. + """ + logging.debug("Adding %s and %s", a, b) + try: + return a + b + except TypeError as e: + logging.debug( + "This is a TypeError: %s", + e, + ) + raise e diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_import_errors.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_import_errors.py new file mode 100644 index 000000000000..2aa40ff028f2 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_import_errors.py @@ -0,0 +1,15 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from azure.core.exceptions import raise_with_traceback +# This code violates no-raise-with-traceback + + +async def main(): + l = {} + try: + l["a"].append(1) + except TypeError: + print("An error occurred") + raise_with_traceback(ValueError("This is a test error")) diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_invalid_overload_use.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_invalid_overload_use.py new file mode 100644 index 000000000000..9e9f288282d5 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_invalid_overload_use.py @@ -0,0 +1,35 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import overload, Union + +# This code violates invalid-use-of-overload + +class TestingOverload: + @overload + def double(self, a: str): + ... + + @overload + def double(self, a: int): + ... + + async def double(self, a: Union[str, int]): + if isinstance(a, str): + return len(a)*2 + return a * 2 + + + @overload + async def doubleAgain(self, a: str) -> int: + ... + + @overload + def doubleAgain(self, a: int) -> int: + ... + + async def doubleAgain(self, a: Union[str, int]) -> int: + if isinstance(a, str): + return len(a)*2 + return a * 2 diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_legacy_typing.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_legacy_typing.py new file mode 100644 index 000000000000..a3f9c2691996 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_legacy_typing.py @@ -0,0 +1,20 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import Any +# This code violates do-not-use-legacy-typing +class ClassNameClient(): + def __init__(self, credential, *, api_version="2018", **kwargs): + # type: (str, Any, str, Any) -> None + """ + :param credential: The credential to use for authentication. + :type credential: str + :keyword api_version: The API version to use for the client. + :paramtype api_version: str + :keyword eight: The eighth parameter. + :paramtype eight: str + """ + self.credential: str = credential + self.api_version: str = api_version + self.eight: str = kwargs.get("eight") diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_lro_poller_uses_polling.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_lro_poller_uses_polling.py new file mode 100644 index 000000000000..d3bbcde0157a --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_lro_poller_uses_polling.py @@ -0,0 +1,21 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import Dict, List, Any +from azure.core.polling import LROPoller +from azure.core.tracing.decorator import distributed_trace + +# This code violates client-lro-methods-use-polling + +class Some1Client(): + @distributed_trace + def begin_thing(self, **kwargs) -> LROPoller: + return LROPoller(self, kwargs.get("some_key"), kwargs.get("some_other_key"), kwargs.get("some_third_key")) + + @distributed_trace + def begin_thing2(self, **kwargs) -> List: + return [] + + + diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_name_property_length.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_name_property_length.py new file mode 100644 index 000000000000..b4bb0f023402 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_name_property_length.py @@ -0,0 +1,61 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import Any, List +from azure.core.tracing.decorator import distributed_trace +# This code violates name-too-long +class ThisClassNameShouldEndUpBeingTooLongForAClient(): + def __init__(self, credential: str, *, api_version: str ="2018", **kwargs: Any) -> None: + """ + :param credential: The credential to use for authentication. + :type credential: str + :keyword api_version: The API version to use for the client. + :paramtype api_version: str + :keyword eight: The eighth parameter. + :paramtype eight: str + """ + self.credential: str = credential + self.eight: str = kwargs.get("eight") + self._api_version: str = api_version + + +class ClassNameGoodClient(): + @distributed_trace + def get_function_name_should_be_too_long_for_rule(self, **kwargs: Any) -> str: + seven = kwargs.get("seven") + return seven + + @distributed_trace + def get_function_good(self, **kwargs: Any) -> List: + this_lists_name_is_too_long_to_work_with_linter_rule = [] + six = kwargs.get("six") + six = six + "" + return this_lists_name_is_too_long_to_work_with_linter_rule + + def _this_function_is_private_but_over_length_reqs(self, **kwargs) -> str: + this_lists_name = [] + five = kwargs.get("five") + five = five + "" + return this_lists_name + + def __init__(self, + credential:str, + this_name_is_too_long_to_use_anymore_reqs: str, + *, api_version: str ="2018", **kwargs: Any) -> None: + """ + :param credential: The credential to use for authentication. + :type credential: str + :param this_name_is_too_long_to_use_anymore_reqs: The first parameter. + :paramtype this_name_is_too_long_to_use_anymore_reqs: str + :keyword api_version: The API version to use for the client. + :paramtype api_version: str + :keyword four: The fourth parameter. + :paramtype four: str + """ + self.credential: str = credential + self.this_name_is_too_long_to_use_anymore_reqs: str = this_name_is_too_long_to_use_anymore_reqs + self._api_version: str = api_version + self.four: str = kwargs.get("four") + + THIS_NAME_IS_TOO_LONG_TO_USE_ANYMORE_REQS = 10 diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_no_typing_under_type_checking.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_no_typing_under_type_checking.py new file mode 100644 index 000000000000..86e88c8e50dc --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_no_typing_under_type_checking.py @@ -0,0 +1,14 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import TYPE_CHECKING + +# This code violates no-typing-import-in-type-check + +if TYPE_CHECKING: + from typing import Any + + +def create_number() -> Any: + return 42 \ No newline at end of file diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_non_abstract_transport.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_non_abstract_transport.py new file mode 100644 index 000000000000..b6fa3482198d --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_non_abstract_transport.py @@ -0,0 +1,22 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from azure.core.pipeline import AsyncPipeline +from azure.core.pipeline.transport import HttpRequest +from azure.core.pipeline.policies import ( + UserAgentPolicy, + AsyncRedirectPolicy, +) +from azure.core.pipeline.transport import ( + RequestsTransport, +) +# This code violates do-not-import-asyncio + +async def main(): + port = 8080 + request = HttpRequest("GET", "http://localhost:{}/basic/string".format(port)) + policies = [UserAgentPolicy("myuseragent"), AsyncRedirectPolicy()] + async with AsyncPipeline(RequestsTransport(), policies=policies) as pipeline: + response = await pipeline.run(request) + print(response.http_response.status_code) diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_six.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_six.py new file mode 100644 index 000000000000..00f7b03f0b82 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_six.py @@ -0,0 +1,10 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +# Test violates do-not-import-legacy-six +import six + +def legacy_operation() -> range: + return six.moves.range(10) diff --git a/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_static_method.py b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_static_method.py new file mode 100644 index 000000000000..a48ecde23a46 --- /dev/null +++ b/tools/azure-sdk-tools/linting_tools/lint_test_bench/test_files/test_static_method.py @@ -0,0 +1,32 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from typing import Any +# This code violates client-does-not-use-static-methods +class SomeClient(): + def __init__(self, credential: str, *, api_version: str ="2018",**kwargs: Any) -> None: + """ + :param credential: The credential to use for authentication. + :type credential: str + :keyword api_version: The API version to use for the client. + :paramtype api_version: str + :keyword one: The first parameter. + :paramtype one: str + """ + self.credential: str = credential + self._api_version: str = api_version + self.one: str = kwargs.get("one") + + @staticmethod + def _private_method() -> None: + pass + + @staticmethod + def create_configuration2() -> None: + pass + +class SomethingElse(): + @staticmethod + def download_thing(some: str) -> None: + some = some + 1