From 0407c60a29144d40536bcf9af84b7e746eb6fbaa Mon Sep 17 00:00:00 2001 From: Aki Ariga Date: Mon, 3 Nov 2025 22:27:55 -0800 Subject: [PATCH 1/3] More type hinting --- pytd/client.py | 7 +++- pytd/query_engine.py | 2 +- pytd/spark.py | 18 ++++++---- pytd/table.py | 6 ++-- pytd/writer.py | 80 +++++++++++++++++++++++++++----------------- 5 files changed, 70 insertions(+), 43 deletions(-) diff --git a/pytd/client.py b/pytd/client.py index d918792..1ce804c 100644 --- a/pytd/client.py +++ b/pytd/client.py @@ -370,7 +370,12 @@ def __exit__( self.close() def _fetch_query_engine( - self, engine: str, apikey: str, endpoint: str, database: str, header: str | bool + self, + engine: Literal["presto", "hive"], + apikey: str, + endpoint: str, + database: str, + header: str | bool, ) -> QueryEngine: if engine == "presto": return PrestoQueryEngine(apikey, endpoint, database, header) diff --git a/pytd/query_engine.py b/pytd/query_engine.py index 0c7adcc..a85cffc 100644 --- a/pytd/query_engine.py +++ b/pytd/query_engine.py @@ -355,7 +355,7 @@ def user_agent(self) -> str: ) @property - def presto_api_host(self): + def presto_api_host(self) -> str: """Presto API host obtained from ``TD_PRESTO_API`` env variable or inferred from Treasure Data REST API endpoint. """ diff --git a/pytd/spark.py b/pytd/spark.py index 23c4c33..4d31ec6 100644 --- a/pytd/spark.py +++ b/pytd/spark.py @@ -9,7 +9,11 @@ logger = logging.getLogger(__name__) -def download_td_spark(spark_binary_version="3.0.1", version="latest", destination=None): +def download_td_spark( + spark_binary_version: str = "3.0.1", + version: str = "latest", + destination: str | None = None, +) -> None: """Download a td-spark jar file from S3. Parameters @@ -51,12 +55,12 @@ def download_td_spark(spark_binary_version="3.0.1", version="latest", destinatio def fetch_td_spark_context( - apikey=None, - endpoint=None, - td_spark_path=None, - download_if_missing=True, - spark_configs=None, -): + apikey: str | None = None, + endpoint: str | None = None, + td_spark_path: str | None = None, + download_if_missing: bool = True, + spark_configs: dict[str, str] | None = None, +) -> "TDSparkContext": # type: ignore[name-defined] # noqa: F821 """Build TDSparkContext via td-pyspark. Parameters diff --git a/pytd/table.py b/pytd/table.py index 25e5cd9..7dbb352 100644 --- a/pytd/table.py +++ b/pytd/table.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, Literal import pandas as pd -import tdclient +from tdclient.errors import NotFoundError from .writer import Writer @@ -39,7 +39,7 @@ class Table: def __init__(self, client: "Client", database: str, table: str) -> None: try: client.api_client.database(database) - except tdclient.errors.NotFoundError as e: + except NotFoundError as e: raise ValueError( "failed to create pytd.table.Table instance for " f"`{database}.{table}`: {e}" @@ -59,7 +59,7 @@ def exists(self) -> bool: """ try: self.client.api_client.table(self.database, self.table) - except tdclient.errors.NotFoundError: + except NotFoundError: return False return True diff --git a/pytd/writer.py b/pytd/writer.py index 2660046..948dd94 100644 --- a/pytd/writer.py +++ b/pytd/writer.py @@ -9,7 +9,7 @@ from collections.abc import Callable from concurrent.futures import ThreadPoolExecutor from contextlib import ExitStack -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Any, BinaryIO, Literal import msgpack import numpy as np @@ -25,32 +25,32 @@ logger = logging.getLogger(__name__) -def _is_pd_na(x): +def _is_pd_na(x: Any) -> bool: is_na = pd.isna(x) return isinstance(is_na, bool) and is_na -def _is_np_nan(x): +def _is_np_nan(x: Any) -> bool: return isinstance(x, float) and np.isnan(x) -def _is_0d_ary(x): +def _is_0d_ary(x: Any) -> bool: return isinstance(x, np.ndarray) and len(x.shape) == 0 -def _is_0d_nan(x): +def _is_0d_nan(x: Any) -> bool: return _is_0d_ary(x) and x.dtype.kind == "f" and np.isnan(x) -def _isnull(x): +def _isnull(x: Any) -> bool: return x is None or _is_np_nan(x) or _is_pd_na(x) -def _isinstance_or_null(x, t): +def _isinstance_or_null(x: Any, t: type | tuple[type, ...]) -> bool: return _isnull(x) or isinstance(x, t) -def _replace_pd_na(dataframe): +def _replace_pd_na(dataframe: pd.DataFrame) -> None: """Replace np.nan and pd.NA to None to avoid Int64 conversion issue""" if dataframe.isnull().any().any(): # Replace both np.nan and pd.NA with None @@ -58,7 +58,7 @@ def _replace_pd_na(dataframe): dataframe.replace(replace_dict, inplace=True) -def _to_list(ary): +def _to_list(ary: Any) -> list[Any] | None: # Return None if None, np.nan, pd.NA, or np.nan in 0-d array given if ary is None or _is_np_nan(ary) or _is_0d_nan(ary) or _is_pd_na(ary): return None @@ -79,15 +79,17 @@ def _to_list(ary): return _ary.tolist() -def _convert_nullable_str(x, t, lower=False): +def _convert_nullable_str(x: Any, t: type, lower: bool = False) -> str | None: v = str(x).lower() if lower else str(x) return v if isinstance(x, t) else None -def _cast_dtypes(dataframe, inplace=True, keep_list=False): +def _cast_dtypes( + dataframe: pd.DataFrame, inplace: bool = True, keep_list: bool = False +) -> pd.DataFrame | None: """Convert dtypes into one of {int, float, str, list} type. - A character code (one of ‘biufcmMOSUV’) identifying the general kind of data. + A character code (one of 'biufcmMOSUV') identifying the general kind of data. b boolean i signed integer @@ -133,9 +135,10 @@ def _cast_dtypes(dataframe, inplace=True, keep_list=False): if not inplace: return df + return None -def _get_schema(dataframe): +def _get_schema(dataframe: pd.DataFrame) -> tuple[list[str], list[str]]: column_names, column_types = [], [] for c, t in zip(dataframe.columns, dataframe.dtypes, strict=False): # Compare nullable integer type by using pandas function because `t == @@ -206,7 +209,7 @@ class InsertIntoWriter(Writer): INSERT INTO query in Presto. """ - def _format_value_for_trino(self, value): + def _format_value_for_trino(self, value: Any) -> str: """Convert a value to appropriate string format for use in Presto queries. Parameters @@ -287,7 +290,14 @@ def write_dataframe( if_exists, ) - def _insert_into(self, table, list_of_tuple, column_names, column_types, if_exists): + def _insert_into( + self, + table: "Table", + list_of_tuple: list[tuple[Any, ...]], + column_names: list[str], + column_types: list[str], + if_exists: Literal["error", "overwrite", "append", "ignore"], + ) -> None: """Write a given lists to a Treasure Data table. Parameters @@ -338,7 +348,13 @@ def _insert_into(self, table, list_of_tuple, column_names, column_types, if_exis ) table.client.query(q_insert, engine="presto") - def _build_query(self, database, table, list_of_tuple, column_names): + def _build_query( + self, + database: str, + table: str, + list_of_tuple: list[tuple[Any, ...]], + column_names: list[str], + ) -> str: """Translates the given data into an ``INSERT INTO ... VALUES ...`` Presto query. @@ -602,17 +618,17 @@ def write_dataframe( def _bulk_import( self, - table, - file_likes, - if_exists, - fmt="csv", - max_workers=5, - show_progress=False, - bulk_import_name=None, - commit_timeout=None, - perform_timeout=None, - perform_wait_callback=None, - ): + table: "Table", + file_likes: list[BinaryIO], + if_exists: Literal["error", "overwrite", "append", "ignore"], + fmt: Literal["csv", "msgpack"] = "csv", + max_workers: int = 5, + show_progress: bool = False, + bulk_import_name: str | None = None, + commit_timeout: int | None = None, + perform_timeout: int | None = None, + perform_wait_callback: Callable[..., Any] | None = None, + ) -> None: """Write a specified CSV file to a Treasure Data table. This method uploads the file to Treasure Data via bulk import API. @@ -725,12 +741,12 @@ def _bulk_import( wait=True, timeout=perform_timeout, wait_callback=perform_wait_callback ) - if 0 < bulk_import.error_records: + if bulk_import.error_records and bulk_import.error_records > 0: logger.warning( f"[job id {job.id}] detected {bulk_import.error_records} error records." ) - if 0 < bulk_import.valid_records: + if bulk_import.valid_records and bulk_import.valid_records > 0: logger.info( f"[job id {job.id}] imported {bulk_import.valid_records} records." ) @@ -741,7 +757,9 @@ def _bulk_import( bulk_import.commit(wait=True, timeout=commit_timeout) bulk_import.delete() - def _write_msgpack_stream(self, items, stream): + def _write_msgpack_stream( + self, items: list[dict[str, Any]], stream: BinaryIO + ) -> BinaryIO: """Write MessagePack stream Parameters @@ -878,7 +896,7 @@ def write_dataframe( "and/or endpoint. Create and use a new SparkWriter instance." ) - from py4j.protocol import Py4JJavaError + from py4j.protocol import Py4JJavaError # type: ignore[import] _cast_dtypes(dataframe) _replace_pd_na(dataframe) From 226706ca1ba60a6a71afa16ea72b9fa9acd150b9 Mon Sep 17 00:00:00 2001 From: Aki Ariga Date: Mon, 3 Nov 2025 23:03:41 -0800 Subject: [PATCH 2/3] Type hinting with strict mode --- pyproject.toml | 2 +- pytd/client.py | 11 +++++++---- pytd/pandas_td/__init__.py | 16 ++++++---------- pytd/query_engine.py | 33 +++++++++++++++++---------------- pytd/writer.py | 10 +++++++--- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9462c38..92a0fb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,7 +107,7 @@ quote-style = "double" indent-style = "space" [tool.pyright] -typeCheckingMode = "basic" +typeCheckingMode = "strict" pythonVersion = "3.10" pythonPlatform = "All" diff --git a/pytd/client.py b/pytd/client.py index 1ce804c..d436c9b 100644 --- a/pytd/client.py +++ b/pytd/client.py @@ -5,6 +5,9 @@ import pandas as pd import tdclient +from tdclient.models import Database as TDClientDatabase +from tdclient.models import Job as TDClientJob +from tdclient.models import Table as TDClientTable from .query_engine import HiveQueryEngine, PrestoQueryEngine, QueryEngine, QueryResult from .table import Table @@ -119,7 +122,7 @@ def __init__( **kwargs, ) - def list_databases(self) -> list[tdclient.models.Database]: + def list_databases(self) -> list[TDClientDatabase]: """Get a list of td-client-python Database objects. Returns @@ -128,7 +131,7 @@ def list_databases(self) -> list[tdclient.models.Database]: """ return self.api_client.databases() - def list_tables(self, database: str | None = None) -> list[tdclient.models.Table]: + def list_tables(self, database: str | None = None) -> list[TDClientTable]: """Get a list of td-client-python Table objects. Parameters @@ -145,7 +148,7 @@ def list_tables(self, database: str | None = None) -> list[tdclient.models.Table database = self.database return self.api_client.tables(database) - def list_jobs(self) -> list[tdclient.models.Job]: + def list_jobs(self) -> list[TDClientJob]: """Get a list of td-client-python Job objects. Returns @@ -154,7 +157,7 @@ def list_jobs(self) -> list[tdclient.models.Job]: """ return self.api_client.jobs() - def get_job(self, job_id: int) -> tdclient.models.Job: + def get_job(self, job_id: int) -> TDClientJob: """Get a td-client-python Job object from ``job_id``. Parameters diff --git a/pytd/pandas_td/__init__.py b/pytd/pandas_td/__init__.py index 6f42863..d820766 100644 --- a/pytd/pandas_td/__init__.py +++ b/pytd/pandas_td/__init__.py @@ -199,7 +199,7 @@ def read_td_query( params = {} params["force_tdclient"] = True - if isinstance(engine, PrestoQueryEngine) and distributed_join is not None: + if isinstance(engine, PrestoQueryEngine): header = engine.create_header( [ "read_td_query", @@ -337,7 +337,7 @@ def read_td_table( return _to_dataframe(engine.execute(query), index_col, parse_dates) -def _convert_time(time): +def _convert_time(time: Any) -> str: if time is None: return "NULL" elif isinstance(time, int): @@ -351,7 +351,7 @@ def _convert_time(time): return f"'{t.replace(microsecond=0)}'" -def _to_dataframe(dic, index_col, parse_dates): +def _to_dataframe(dic: Any, index_col: Any, parse_dates: Any) -> pd.DataFrame: frame = pd.DataFrame(**dic) if parse_dates is not None: frame = _parse_dates(frame, parse_dates) @@ -360,7 +360,7 @@ def _to_dataframe(dic, index_col, parse_dates): return frame -def _parse_dates(frame, parse_dates): +def _parse_dates(frame: pd.DataFrame, parse_dates: Any) -> pd.DataFrame: for name in parse_dates: if type(parse_dates) is list: frame[name] = pd.to_datetime(frame[name]) @@ -509,8 +509,6 @@ def _convert_time_column( frame["time"] = col.astype("int64") // (10**9) elif time_index is not None: # Use 'time_index' as time column - if type(time_index) is bool or not isinstance(time_index, int): - raise TypeError("invalid type for time_index") if isinstance(frame.index, pd.MultiIndex): idx = frame.index.levels[time_index] else: @@ -533,8 +531,6 @@ def _convert_index_column( index: bool | None = None, index_label: str | list[str] | None = None, ) -> pd.DataFrame: - if index is not None and not isinstance(index, bool): - raise TypeError("index must be boolean") if index: if isinstance(frame.index, pd.MultiIndex): if index_label is None: @@ -555,9 +551,9 @@ def _convert_date_format( ) -> pd.DataFrame: if date_format is not None: - def _convert(col): + def _convert(col: pd.Series) -> pd.Series: if col.dtype.name == "datetime64[ns]": - return col.apply(lambda x: x.strftime(date_format)) + return col.apply(lambda x: x.strftime(date_format)) # type: ignore[return-value, misc] return col frame = frame.apply(_convert) # type: ignore[assignment] diff --git a/pytd/query_engine.py b/pytd/query_engine.py index a85cffc..516a7b1 100644 --- a/pytd/query_engine.py +++ b/pytd/query_engine.py @@ -6,8 +6,9 @@ from urllib.parse import urlparse import tdclient -import tdclient.cursor import trino +from tdclient.connection import Connection as TDClientConnection +from tdclient.cursor import Cursor as TDClientCursor __version__ = importlib.metadata.version("pytd") @@ -45,7 +46,7 @@ def execute( "params must be a list or tuple containing the query parameter values" ) - if self.connection._use_legacy_prepared_statements(): + if self.connection._use_legacy_prepared_statements(): # type: ignore[attr-defined] statement_name = self._generate_unique_statement_name() self._prepare_statement(operation, statement_name) @@ -83,7 +84,7 @@ def execute( # Type alias for cursor types returned by query engines -Cursor: TypeAlias = CustomTrinoCursor | tdclient.cursor.Cursor +Cursor: TypeAlias = CustomTrinoCursor | TDClientCursor class QueryEngine(metaclass=abc.ABCMeta): @@ -205,7 +206,7 @@ def create_header( if isinstance(extra_lines, str): header += f"-- {extra_lines}\n" - elif isinstance(extra_lines, list | tuple): + elif extra_lines: # list or tuple header += "".join([f"-- {line}\n" for line in extra_lines]) return header @@ -223,8 +224,8 @@ def _connect(self) -> Any: pass def _get_tdclient_cursor( - self, con: tdclient.api.API, **kwargs: Any - ) -> tdclient.cursor.Cursor: + self, con: TDClientConnection, **kwargs: Any + ) -> TDClientCursor: """Get DB-API cursor from tdclient Connection instance. ``kwargs`` are for setting specific parameters to Treasure Data REST @@ -290,7 +291,7 @@ def _get_tdclient_cursor( ) # update a clone of the original params - cursor_kwargs = con._cursor_kwargs.copy() + cursor_kwargs = con._cursor_kwargs.copy() # type: ignore[attr-defined] for k, v in kwargs.items(): if k not in api_param_names: raise RuntimeError( @@ -300,17 +301,17 @@ def _get_tdclient_cursor( cursor_kwargs[k] = v # keep the original `_cursor_kwargs` - original_cursor_kwargs = con._cursor_kwargs.copy() + original_cursor_kwargs = con._cursor_kwargs.copy() # type: ignore[attr-defined] # overwrite the original params - con._cursor_kwargs = cursor_kwargs + con._cursor_kwargs = cursor_kwargs # type: ignore[attr-defined] # `Connection#cursor` internally refers the customized # ``_cursor_kwargs`` cursor = con.cursor() # write the original params back to `_cursor_kwargs` - con._cursor_kwargs = original_cursor_kwargs + con._cursor_kwargs = original_cursor_kwargs # type: ignore[attr-defined] logger.warning( "returning `tdclient.cursor.Cursor`. This cursor, `Cursor#fetchone` " @@ -402,7 +403,9 @@ def cursor(self, force_tdclient: bool = False, **kwargs: Any) -> Cursor: # In production, return our custom cursor with the same request object return CustomTrinoCursor( - self.trino_connection, regular_cursor._request, self.user_agent + self.trino_connection, + regular_cursor._request, # type: ignore[attr-defined] + self.user_agent, # type: ignore[attr-defined] ) return self._get_tdclient_cursor(self.tdclient_connection, **kwargs) @@ -414,7 +417,7 @@ def close(self) -> None: def _connect( self, - ) -> tuple[trino.dbapi.Connection, tdclient.api.API]: + ) -> tuple[trino.dbapi.Connection, TDClientConnection]: # Create trino connection trino_connection = trino.dbapi.connect( host=self.presto_api_host, @@ -466,9 +469,7 @@ def user_agent(self) -> str: """User agent passed to a Hive connection.""" return f"pytd/{__version__} (tdclient/{tdclient.__version__})" - def cursor( - self, force_tdclient: bool = True, **kwargs: Any - ) -> tdclient.cursor.Cursor: + def cursor(self, force_tdclient: bool = True, **kwargs: Any) -> TDClientCursor: """Get cursor defined by DB-API. Parameters @@ -511,7 +512,7 @@ def close(self) -> None: """Close a connection to Hive.""" self.engine.close() - def _connect(self) -> tdclient.api.API: + def _connect(self) -> TDClientConnection: return tdclient.connect( apikey=self.apikey, endpoint=self.endpoint, diff --git a/pytd/writer.py b/pytd/writer.py index 948dd94..d216abd 100644 --- a/pytd/writer.py +++ b/pytd/writer.py @@ -105,7 +105,7 @@ def _cast_dtypes( """ df = dataframe if inplace else dataframe.copy() - for column, kind in dataframe.dtypes.apply(lambda dtype: dtype.kind).items(): + for column, kind in dataframe.dtypes.apply(lambda dtype: dtype.kind).items(): # type: ignore[misc] t = str if kind in ("i", "u"): t = "Int64" if df[column].isnull().any() else "int64" @@ -175,7 +175,11 @@ def _get_schema(dataframe: pd.DataFrame) -> tuple[list[str], list[str]]: class Writer(metaclass=abc.ABCMeta): def __init__(self) -> None: - self.closed: bool = False + self._closed: bool = False + + @property + def closed(self) -> bool: + return self._closed @abc.abstractmethod def write_dataframe( @@ -187,7 +191,7 @@ def write_dataframe( pass def close(self) -> None: - self.closed = True + self._closed = True @staticmethod def from_string( From b04df70184a018d0a5cbf947edccc019770b4ea0 Mon Sep 17 00:00:00 2001 From: Aki Ariga Date: Tue, 4 Nov 2025 00:51:31 -0800 Subject: [PATCH 3/3] Comment out codeql.yml --- .github/workflows/codeql-analysis.yml | 100 +++++++++++++------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 36419b6..cc1afe1 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,3 +1,5 @@ +# Comment out due to conflict with the default CodeQL workflow +# # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # @@ -9,64 +11,64 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL" +# name: "CodeQL" -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '38 8 * * 3' +# on: +# push: +# branches: [ master ] +# pull_request: +# # The branches below must be a subset of the branches above +# branches: [ master ] +# schedule: +# - cron: '38 8 * * 3' -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write +# jobs: +# analyze: +# name: Analyze +# runs-on: ubuntu-latest +# permissions: +# actions: read +# contents: read +# security-events: write - strategy: - fail-fast: false - matrix: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support +# strategy: +# fail-fast: false +# matrix: +# language: [ 'python' ] +# # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] +# # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - steps: - - name: Checkout repository - uses: actions/checkout@v5 +# steps: +# - name: Checkout repository +# uses: actions/checkout@v5 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. +# # Initializes the CodeQL tools for scanning. +# - name: Initialize CodeQL +# uses: github/codeql-action/init@v2 +# with: +# languages: ${{ matrix.language }} +# # If you wish to specify custom queries, you can do so here or in a config file. +# # By default, queries listed here will override any specified in a config file. +# # Prefix the list here with "+" to use these queries and those in the config file. - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality +# # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs +# # queries: security-extended,security-and-quality - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 +# # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). +# # If this step fails, then you should remove it and run the build manually (see below) +# - name: Autobuild +# uses: github/codeql-action/autobuild@v2 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun +# # ℹ️ Command-line programs to run using the OS shell. +# # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. +# # If the Autobuild fails above, remove it and uncomment the following three lines. +# # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh +# # - run: | +# # echo "Run, Build Application using script" +# # ./location_of_script_within_repo/buildscript.sh - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 +# - name: Perform CodeQL Analysis +# uses: github/codeql-action/analyze@v2