Skip to content

Commit b3a0bf8

Browse files
authored
Added lint local code command (#1710)
1 parent 9988acd commit b3a0bf8

File tree

18 files changed

+509
-83
lines changed

18 files changed

+509
-83
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ See [contributing instructions](CONTRIBUTING.md) to help improve this project.
6868
* [`move` command](#move-command)
6969
* [`alias` command](#alias-command)
7070
* [Code migration commands](#code-migration-commands)
71+
* [`lint-local-code` command](#lint-local-code-command)
7172
* [`migrate-local-code` command](#migrate-local-code-command)
7273
* [`migrate-dbsql-dashboards` command](#migrate-dbsql-dashboards-command)
7374
* [`revert-dbsql-dashboards` command](#revert-dbsql-dashboards-command)
@@ -973,6 +974,25 @@ clusters to be UC compatible.
973974

974975
[[back to top](#databricks-labs-ucx)]
975976

977+
## `lint-local-code` command
978+
979+
```text
980+
databricks labs ucx lint-local-code
981+
```
982+
983+
At any time, you can run this command to assess all migrations required in a local directory or a file. It only takes seconds to run and it
984+
gives you an initial overview of what needs to be migrated without actually performing any migration. A great way to start a migration!
985+
986+
This command detects all dependencies, and analyzes them. It is still experimental and at the moment only supports Python and SQL files.
987+
We expect this command to run within a minute on code bases up to 50.000 lines of code.
988+
Future versions of `ucx` will add support for more source types, and more migration details.
989+
990+
When run from an IDE terminal, this command generates output as follows:
991+
![img.png](docs/lint-local-code-output.png)
992+
With modern IDEs, clicking on the file link opens the file at the problematic line
993+
994+
[[back to top](#databricks-labs-ucx)]
995+
976996
## `migrate-local-code` command
977997

978998
```text

docs/lint-local-code-output.png

69.7 KB
Loading

labs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ commands:
192192
- name: migrate-local-code
193193
description: (Experimental) Migrate files in the current directory to be more compatible with Unity Catalog.
194194

195+
- name: lint-local-code
196+
description: (Experimental) Lint files in the current directory to highlight incompatibilities with Unity Catalog.
197+
195198
- name: show-all-metastores
196199
is_account_level: true
197200
description: Show all metastores available in the same region as the specified workspace

src/databricks/labs/ucx/cli.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from databricks.labs.ucx.config import WorkspaceConfig
1313
from databricks.labs.ucx.contexts.account_cli import AccountContext
14-
from databricks.labs.ucx.contexts.workspace_cli import WorkspaceContext
14+
from databricks.labs.ucx.contexts.workspace_cli import WorkspaceContext, LocalCheckoutContext
1515
from databricks.labs.ucx.hive_metastore.tables import What
1616

1717
ucx = App(__file__)
@@ -398,7 +398,7 @@ def revert_cluster_remap(w: WorkspaceClient, prompts: Prompts):
398398
@ucx.command
399399
def migrate_local_code(w: WorkspaceClient, prompts: Prompts):
400400
"""Fix the code files based on their language."""
401-
ctx = WorkspaceContext(w)
401+
ctx = LocalCheckoutContext(w)
402402
working_directory = Path.cwd()
403403
if not prompts.confirm("Do you want to apply UC migration to all files in the current directory?"):
404404
return
@@ -471,5 +471,15 @@ def revert_dbsql_dashboards(w: WorkspaceClient, dashboard_id: str | None = None)
471471
ctx.redash.revert_dashboards(dashboard_id)
472472

473473

474+
@ucx.command
475+
def lint_local_code(
476+
w: WorkspaceClient, prompts: Prompts, path: str | None = None, ctx: LocalCheckoutContext | None = None
477+
):
478+
"""Lint local code files looking for problems."""
479+
if ctx is None:
480+
ctx = LocalCheckoutContext(w)
481+
ctx.local_code_linter.lint(prompts, None if path is None else Path(path))
482+
483+
474484
if __name__ == "__main__":
475485
ucx()

src/databricks/labs/ucx/contexts/application.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from databricks.labs.ucx.recon.schema_comparator import StandardSchemaComparator
1818
from databricks.labs.ucx.source_code.python_libraries import PipResolver
1919
from databricks.sdk import AccountClient, WorkspaceClient, core
20+
from databricks.sdk.errors import ResourceDoesNotExist
2021
from databricks.sdk.service import sql
2122

2223
from databricks.labs.ucx.account.workspaces import WorkspaceInfo
@@ -46,7 +47,7 @@
4647
NotebookResolver,
4748
NotebookLoader,
4849
)
49-
from databricks.labs.ucx.source_code.files import FileLoader, ImportFileResolver
50+
from databricks.labs.ucx.source_code.files import FileLoader, FolderLoader, ImportFileResolver
5051
from databricks.labs.ucx.source_code.path_lookup import PathLookup
5152
from databricks.labs.ucx.source_code.graph import DependencyResolver
5253
from databricks.labs.ucx.source_code.whitelist import Whitelist
@@ -286,12 +287,15 @@ def aws_acl(self):
286287
@cached_property
287288
def principal_locations(self):
288289
eligible_locations = {}
289-
if self.is_azure:
290-
eligible_locations = self.azure_acl.get_eligible_locations_principals()
291-
if self.is_aws:
292-
eligible_locations = self.aws_acl.get_eligible_locations_principals()
293-
if self.is_gcp:
294-
raise NotImplementedError("Not implemented for GCP.")
290+
try:
291+
if self.is_azure:
292+
eligible_locations = self.azure_acl.get_eligible_locations_principals()
293+
if self.is_aws:
294+
eligible_locations = self.aws_acl.get_eligible_locations_principals()
295+
if self.is_gcp:
296+
raise NotImplementedError("Not implemented for GCP.")
297+
except ResourceDoesNotExist:
298+
pass
295299
return eligible_locations
296300

297301
@cached_property
@@ -382,6 +386,10 @@ def path_lookup(self):
382386
def file_loader(self):
383387
return FileLoader()
384388

389+
@cached_property
390+
def folder_loader(self):
391+
return FolderLoader(self.file_loader)
392+
385393
@cached_property
386394
def whitelist(self):
387395
# TODO: fill in the whitelist

src/databricks/labs/ucx/contexts/workspace_cli.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
from databricks.labs.ucx.assessment.aws import run_command, AWSResources
99
from databricks.labs.ucx.aws.access import AWSResourcePermissions
1010
from databricks.labs.ucx.aws.credentials import IamRoleMigration, IamRoleCreation
11+
from databricks.labs.ucx.aws.locations import AWSExternalLocationsMigration
1112
from databricks.labs.ucx.azure.access import AzureResourcePermissions
1213
from databricks.labs.ucx.azure.credentials import StorageCredentialManager, ServicePrincipalMigration
1314
from databricks.labs.ucx.azure.locations import ExternalLocationsMigration
14-
from databricks.labs.ucx.aws.locations import AWSExternalLocationsMigration
1515
from databricks.labs.ucx.azure.resources import AzureAPIClient, AzureResources
1616
from databricks.labs.ucx.contexts.application import CliContext
17-
from databricks.labs.ucx.source_code.files import LocalFileMigrator
17+
from databricks.labs.ucx.source_code.files import LocalFileMigrator, LocalCodeLinter
18+
from databricks.labs.ucx.source_code.notebooks.loaders import NotebookLoader
1819
from databricks.labs.ucx.workspace_access.clusters import ClusterAccess
1920

2021

@@ -31,10 +32,6 @@ def workspace_client(self) -> WorkspaceClient:
3132
def sql_backend(self) -> SqlBackend:
3233
return StatementExecutionBackend(self.workspace_client, self.config.warehouse_id)
3334

34-
@cached_property
35-
def local_file_migrator(self):
36-
return LocalFileMigrator(self.languages)
37-
3835
@cached_property
3936
def cluster_access(self):
4037
return ClusterAccess(self.installation, self.workspace_client, self.prompts)
@@ -169,3 +166,22 @@ def iam_role_creation(self):
169166
self.workspace_client,
170167
self.aws_resource_permissions,
171168
)
169+
170+
@cached_property
171+
def notebook_loader(self) -> NotebookLoader:
172+
return NotebookLoader()
173+
174+
175+
class LocalCheckoutContext(WorkspaceContext):
176+
"""Local context extends Workspace context to provide extra properties
177+
for running local operations."""
178+
179+
@cached_property
180+
def local_file_migrator(self):
181+
return LocalFileMigrator(self.languages)
182+
183+
@cached_property
184+
def local_code_linter(self):
185+
return LocalCodeLinter(
186+
self.file_loader, self.folder_loader, self.path_lookup, self.dependency_resolver, lambda: self.languages
187+
)

src/databricks/labs/ucx/source_code/base.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from abc import abstractmethod
44
from collections.abc import Iterable
55
from dataclasses import dataclass
6+
from pathlib import Path
7+
68

79
# Code mapping between LSP, PyLint, and our own diagnostics:
810
# | LSP | PyLint | Our |
@@ -54,6 +56,15 @@ def as_deprecation(self) -> 'Deprecation':
5456
def as_convention(self) -> 'Convention':
5557
return Convention(**self.__dict__)
5658

59+
def for_path(self, path: Path) -> LocatedAdvice:
60+
return LocatedAdvice(self, path)
61+
62+
63+
@dataclass
64+
class LocatedAdvice:
65+
advice: Advice
66+
path: Path
67+
5768

5869
class Advisory(Advice):
5970
"""A warning that does not prevent the code from running."""

0 commit comments

Comments
 (0)