[issue-997] - More pylint fixes (#2107)

* Update naming and disable false positives

* Rename for naming convention

* Fix linting issues

* Moved pylint to pylintrc

* Moved pylint to pylintrc

* Rename CI

* Check sonar properties

* Fix pull_request_target checkout
This commit is contained in:
Pere Miquel Brull 2022-01-07 19:50:53 +01:00 committed by GitHub
parent dbcc5e9dd2
commit 74e9038b92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 103 additions and 49 deletions

View File

@ -43,6 +43,8 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up JDK 11 - name: Set up JDK 11
uses: actions/setup-java@v1 uses: actions/setup-java@v1

View File

@ -10,7 +10,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
name: py-integration-tests name: py-tests
on: on:
push: push:
branches: [main] branches: [main]
@ -24,7 +24,11 @@ jobs:
run_tests: run_tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - name: Checkout
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up JDK 11 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
@ -68,6 +72,9 @@ jobs:
echo "sonar.pullrequest.base=origin/main" >> ingestion/sonar-project.properties echo "sonar.pullrequest.base=origin/main" >> ingestion/sonar-project.properties
echo "sonar.pullrequest.provider=github" >> ingestion/sonar-project.properties echo "sonar.pullrequest.provider=github" >> ingestion/sonar-project.properties
# Validate new properties
cat ingestion/sonar-project.properties
make token=${{ secrets.INGESTION_SONAR_SECRET }} sonar_ingestion make token=${{ secrets.INGESTION_SONAR_SECRET }} sonar_ingestion
- name: Run Sonar - name: Run Sonar

View File

@ -1,11 +1,17 @@
[BASIC] [BASIC]
# W1203: logging-fstring-interpolation - f-string brings better readability and unifies style # W1203: logging-fstring-interpolation - f-string brings better readability and unifies style
# W1202: logging-format-interpolation - lazy formatting in logging functions # W1202: logging-format-interpolation - lazy formatting in logging functions
disable=W1203,W1202 # R0903: too-few-public-methods - False negatives in pydantic classes
# W0707: raise-missing-from - Tends to be a false positive as exception are closely encapsulated
disable=W1203,W1202,R0903,W0707
docstring-min-length=20 docstring-min-length=20
max-args=7 max-args=7
max-attributes=12 max-attributes=12
# usual typevar naming
good-names=T,C
[MASTER] [MASTER]
fail-under=6.0 fail-under=6.0
init-hook='from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.dirname(find_pylintrc()))' init-hook='from pylint.config import find_pylintrc; import os, sys; sys.path.append(os.path.dirname(find_pylintrc()))'

View File

@ -8,6 +8,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""
Interface definition for an Auth provider
"""
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from dataclasses import dataclass from dataclasses import dataclass

View File

@ -8,10 +8,11 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""
Python API REST wrapper and helpers
"""
import logging import logging
import time import time
from enum import Enum
from typing import List, Optional from typing import List, Optional
import requests import requests
@ -48,22 +49,21 @@ class APIError(Exception):
if http_error is not None and hasattr(http_error, "response"): if http_error is not None and hasattr(http_error, "response"):
return http_error.response.status_code return http_error.response.status_code
return None
@property @property
def request(self): def request(self):
if self._http_error is not None: if self._http_error is not None:
return self._http_error.request return self._http_error.request
return None
@property @property
def response(self): def response(self):
if self._http_error is not None: if self._http_error is not None:
return self._http_error.response return self._http_error.response
return None
class TimeFrame(Enum):
Day = "1Day"
Hour = "1Hour"
Minute = "1Min"
Sec = "1Sec"
class ClientConfig(ConfigModel): class ClientConfig(ConfigModel):
@ -83,7 +83,12 @@ class ClientConfig(ConfigModel):
allow_redirects: Optional[bool] = False allow_redirects: Optional[bool] = False
class REST(object): class REST:
"""
REST client wrapper to manage requests with
retries, auth and error handling.
"""
def __init__(self, config: ClientConfig): def __init__(self, config: ClientConfig):
self.config = config self.config = config
self._base_url: URL = URL(self.config.base_url) self._base_url: URL = URL(self.config.base_url)

View File

@ -8,6 +8,10 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""
Helper methods to handle creds retrieval
for the OpenMetadata Python API
"""
import os import os
from typing import Tuple from typing import Tuple
@ -23,17 +27,17 @@ class URL(str):
note: we use *value and v0 to allow an empty URL string note: we use *value and v0 to allow an empty URL string
""" """
if value: if value:
v0 = value[0] url = value[0]
if not (isinstance(v0, str) or isinstance(v0, URL)): if not isinstance(url, (URL, str)):
raise TypeError(f'Unexpected type for URL: "{type(v0)}"') raise TypeError(f'Unexpected type for URL: "{type(url)}"')
if not ( if not (
v0.startswith("http://") url.startswith("http://")
or v0.startswith("https://") or url.startswith("https://")
or v0.startswith("ws://") or url.startswith("ws://")
or v0.startswith("wss://") or url.startswith("wss://")
): ):
raise ValueError( raise ValueError(
f'Passed string value "{v0}" is not an' f'Passed string value "{url}" is not an'
f' "http*://" or "ws*://" URL' f' "http*://" or "ws*://" URL'
) )
return str.__new__(cls, *value) return str.__new__(cls, *value)
@ -55,9 +59,10 @@ class DATE(str):
) )
try: try:
dateutil.parser.parse(value) dateutil.parser.parse(value)
except Exception as e: except Exception as exc:
msg = f"{value} is not a valid date string: {e}" msg = f"{value} is not a valid date string: {exc}"
raise Exception(msg) raise Exception(msg)
return str.__new__(cls, value) return str.__new__(cls, value)
@ -69,10 +74,11 @@ class FLOAT(str):
""" """
def __new__(cls, value): def __new__(cls, value):
if isinstance(value, float) or isinstance(value, int): if isinstance(value, (float, int)):
return value return value
if isinstance(value, str): if isinstance(value, str):
return float(value.strip()) return float(value.strip())
raise ValueError(f'Unexpected float format "{value}"') raise ValueError(f'Unexpected float format "{value}"')

View File

@ -109,6 +109,7 @@ class OMetaLineageMixin(Generic[T]):
return res return res
except APIError as err: except APIError as err:
logger.error( logger.error(
f"Error {err.status_code} trying to GET linage for {entity.__class__.__name__} and {path}" f"Error {err.status_code} trying to GET linage for"
+ f"{entity.__class__.__name__} and {path}"
) )
return None return None

View File

@ -11,7 +11,7 @@ from metadata.generated.schema.entity.data.mlmodel import MlModel
from metadata.generated.schema.type.entityLineage import EntitiesEdge from metadata.generated.schema.type.entityLineage import EntitiesEdge
from metadata.generated.schema.type.entityReference import EntityReference from metadata.generated.schema.type.entityReference import EntityReference
from metadata.ingestion.ometa.client import REST from metadata.ingestion.ometa.client import REST
from metadata.ingestion.ometa.mixins.lineageMixin import OMetaLineageMixin from metadata.ingestion.ometa.mixins.lineage_mixin import OMetaLineageMixin
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -36,7 +36,7 @@ class OMetaTableMixin:
:param table: Table Entity to update :param table: Table Entity to update
:param location: Location Entity to add :param location: Location Entity to add
""" """
resp = self.client.put( self.client.put(
f"{self.get_suffix(Table)}/{table.id.__root__}/location", f"{self.get_suffix(Table)}/{table.id.__root__}/location",
data=str(location.id.__root__), data=str(location.id.__root__),
) )

View File

@ -1,4 +1,3 @@
# pylint: disable=invalid-name
""" """
Mixin class containing entity versioning specific methods Mixin class containing entity versioning specific methods
@ -15,7 +14,7 @@ from metadata.generated.schema.type import basic
from metadata.generated.schema.type.entityHistory import EntityVersionHistory from metadata.generated.schema.type.entityHistory import EntityVersionHistory
from metadata.ingestion.ometa.client import REST from metadata.ingestion.ometa.client import REST
T = TypeVar("T", bound=BaseModel) # pylint: disable=invalid-name T = TypeVar("T", bound=BaseModel)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -8,6 +8,12 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""
OpenMetadata is the high level Python API that serves as a wrapper
for the metadata-server API. It is based on the generated pydantic
models from the JSON schemas and provides a typed approach to
working with OpenMetadata entities.
"""
import logging import logging
from typing import Generic, List, Optional, Type, TypeVar, Union, get_args from typing import Generic, List, Optional, Type, TypeVar, Union, get_args
@ -38,10 +44,9 @@ from metadata.generated.schema.type import basic
from metadata.generated.schema.type.entityHistory import EntityVersionHistory from metadata.generated.schema.type.entityHistory import EntityVersionHistory
from metadata.ingestion.ometa.auth_provider import AuthenticationProvider from metadata.ingestion.ometa.auth_provider import AuthenticationProvider
from metadata.ingestion.ometa.client import REST, APIError, ClientConfig from metadata.ingestion.ometa.client import REST, APIError, ClientConfig
from metadata.ingestion.ometa.mixins.lineageMixin import OMetaLineageMixin from metadata.ingestion.ometa.mixins.mlmodel_mixin import OMetaMlModelMixin
from metadata.ingestion.ometa.mixins.mlModelMixin import OMetaMlModelMixin from metadata.ingestion.ometa.mixins.table_mixin import OMetaTableMixin
from metadata.ingestion.ometa.mixins.tableMixin import OMetaTableMixin from metadata.ingestion.ometa.mixins.version_mixin import OMetaVersionMixin
from metadata.ingestion.ometa.mixins.versionMixin import OMetaVersionMixin
from metadata.ingestion.ometa.openmetadata_rest import ( from metadata.ingestion.ometa.openmetadata_rest import (
Auth0AuthenticationProvider, Auth0AuthenticationProvider,
GoogleAuthenticationProvider, GoogleAuthenticationProvider,
@ -126,13 +131,16 @@ class OpenMetadata(
self.client = REST(client_config) self.client = REST(client_config)
self._use_raw_data = raw_data self._use_raw_data = raw_data
def get_suffix(self, entity: Type[T]) -> str: def get_suffix(self, entity: Type[T]) -> str: # pylint: disable=R0911,R0912
""" """
Given an entity Type from the generated sources, Given an entity Type from the generated sources,
return the endpoint to run requests. return the endpoint to run requests.
Might be interesting to follow a more strict Might be interesting to follow a more strict
and type-checked approach and type-checked approach
Disabled pylint R0911: too-many-return-statements
Disabled pylint R0912: too-many-branches
""" """
# Entity Schemas # Entity Schemas
@ -411,7 +419,7 @@ class OpenMetadata(
if self._use_raw_data: if self._use_raw_data:
return resp return resp
else:
entities = [entity(**t) for t in resp["data"]] entities = [entity(**t) for t in resp["data"]]
total = resp["paging"]["total"] total = resp["paging"]["total"]
after = resp["paging"]["after"] if "after" in resp["paging"] else None after = resp["paging"]["after"] if "after" in resp["paging"] else None
@ -430,7 +438,7 @@ class OpenMetadata(
if self._use_raw_data: if self._use_raw_data:
return resp return resp
else:
return EntityVersionHistory(**resp) return EntityVersionHistory(**resp)
def list_services(self, entity: Type[T]) -> List[EntityList[T]]: def list_services(self, entity: Type[T]) -> List[EntityList[T]]:
@ -441,7 +449,7 @@ class OpenMetadata(
resp = self.client.get(self.get_suffix(entity)) resp = self.client.get(self.get_suffix(entity))
if self._use_raw_data: if self._use_raw_data:
return resp return resp
else:
return [entity(**p) for p in resp["data"]] return [entity(**p) for p in resp["data"]]
def delete(self, entity: Type[T], entity_id: Union[str, basic.Uuid]) -> None: def delete(self, entity: Type[T], entity_id: Union[str, basic.Uuid]) -> None:

View File

@ -8,7 +8,10 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""
Helper classes to model OpenMetadata Entities,
server configuration and auth.
"""
import http.client import http.client
import json import json
import logging import logging
@ -110,6 +113,10 @@ class GoogleAuthenticationProvider(AuthenticationProvider):
class OktaAuthenticationProvider(AuthenticationProvider): class OktaAuthenticationProvider(AuthenticationProvider):
"""
Prepare the Json Web Token for Okta auth
"""
def __init__(self, config: MetadataServerConfig): def __init__(self, config: MetadataServerConfig):
self.config = config self.config = config
@ -118,9 +125,9 @@ class OktaAuthenticationProvider(AuthenticationProvider):
return cls(config) return cls(config)
def auth_token(self) -> str: def auth_token(self) -> str:
from okta.jwt import JWT from okta.jwt import JWT # pylint: disable=import-outside-toplevel
my_pem, my_jwk = JWT.get_PEM_JWK(self.config.private_key) _, my_jwk = JWT.get_PEM_JWK(self.config.private_key)
claims = { claims = {
"sub": self.config.client_id, "sub": self.config.client_id,
"iat": time.time(), "iat": time.time(),

View File

@ -8,7 +8,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""
REST Auth & Client for Apache Superset
"""
import json import json
import logging import logging
from typing import Optional from typing import Optional
@ -33,6 +35,10 @@ class SupersetConfig(ConfigModel):
class SupersetAuthenticationProvider(AuthenticationProvider): class SupersetAuthenticationProvider(AuthenticationProvider):
"""
Handle SuperSet Auth
"""
def __init__(self, config: SupersetConfig): def __init__(self, config: SupersetConfig):
self.config = config self.config = config
client_config = ClientConfig(base_url=config.url, api_version="api/v1") client_config = ClientConfig(base_url=config.url, api_version="api/v1")
@ -57,7 +63,11 @@ class SupersetAuthenticationProvider(AuthenticationProvider):
return json.dumps(auth_request) return json.dumps(auth_request)
class SupersetAPIClient(object): class SupersetAPIClient:
"""
Superset client wrapper using the REST helper class
"""
client: REST client: REST
_auth_provider: AuthenticationProvider _auth_provider: AuthenticationProvider
@ -74,7 +84,7 @@ class SupersetAPIClient(object):
self.client = REST(client_config) self.client = REST(client_config)
def fetch_total_dashboards(self) -> int: def fetch_total_dashboards(self) -> int:
response = self.client.get(f"/dashboard?q=(page:0,page_size:1)") response = self.client.get("/dashboard?q=(page:0,page_size:1)")
return response.get("count") or 0 return response.get("count") or 0
def fetch_dashboards(self, current_page: int, page_size: int): def fetch_dashboards(self, current_page: int, page_size: int):
@ -84,7 +94,7 @@ class SupersetAPIClient(object):
return response return response
def fetch_total_charts(self) -> int: def fetch_total_charts(self) -> int:
response = self.client.get(f"/chart?q=(page:0,page_size:1)") response = self.client.get("/chart?q=(page:0,page_size:1)")
return response.get("count") or 0 return response.get("count") or 0
def fetch_charts(self, current_page: int, page_size: int): def fetch_charts(self, current_page: int, page_size: int):