Skip to content

Commit a8ea185

Browse files
committed
APP-6839: Migrate to uv
1 parent 2e433e1 commit a8ea185

File tree

13 files changed

+3066
-97
lines changed

13 files changed

+3066
-97
lines changed

.pre-commit-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ repos:
77
- id: trailing-whitespace
88
- id: debug-statements
99

10+
# Use uv to run formatting and QA tools
1011
- repo: https://github.com/astral-sh/ruff-pre-commit
1112
rev: v0.9.7
1213
hooks:
@@ -15,3 +16,12 @@ repos:
1516
name: ruff-check-autofix
1617
args: ["check", "--select", "I", "--fix", "--silent"]
1718
- id: ruff-format
19+
- repo: local
20+
hooks:
21+
- id: qa-checks
22+
name: qa-checks
23+
entry: uv run ./qa-checks
24+
language: system
25+
types: [python]
26+
pass_filenames: false
27+
stages: [manual]

README.md

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ To get started developing the SDK:
4949
Before committing code, ensure it adheres to the repository's formatting guidelines. You can apply the required formatting using the below command:
5050

5151
```bash
52-
./pyatlan-formatter
52+
uv run ./formatter
5353
```
5454

5555
### Environment Setup
@@ -115,23 +115,22 @@ If you've pushed new typedefs to Atlan and want to generate SDK asset models to
115115
> [!NOTE]
116116
> Before running any generator scripts, make sure you have [configured your environment variables](https://developer.atlan.com/sdks/python/#configure-the-sdk) specifically `ATLAN_BASE_URL` and `ATLAN_API_KEY`.
117117
118-
1. Retrieve the typedefs from your Atlan instance and save them to a JSON file by running:
118+
1. Run the combined generator script that handles all steps automatically:
119119
120120
```shell
121-
python3 pyatlan/generator/create_typedefs_file.py
122-
```
123-
124-
2. Generate the asset `models`, `enums`, and `struct` modules in the SDK based on the typedefs by running:
121+
# Use default location (/tmp/typedefs.json)
122+
uv run ./generator
125123
126-
```shell
127-
python3 pyatlan/generator/class_generator.py
124+
# Or specify a custom typedefs file location
125+
uv run ./generator ./my-typedefs.json
128126
```
129127
130-
3. The generated files will be unformatted. To format them properly, run the formatter:
131-
132-
```shell
133-
./pyatlan-formatter
134-
```
128+
This script will:
129+
- Check if typedefs file exists and is current (skip if already created today)
130+
- Retrieve typedefs from your Atlan instance if needed
131+
- Generate the asset `models`, `enums`, and `struct` modules
132+
- Format the generated code automatically
133+
- Support custom typedefs file paths for flexibility
135134
136135
## Attribution
137136

pyatlan-formatter renamed to formatter

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@ untracked_files=$(git ls-files --others --exclude-standard)
88
all_files="${changed_files}
99
${untracked_files}"
1010

11-
# Pass the filenames to pre-commit run --files
12-
echo "$all_files" | tr '\n' '\0' | xargs -0 pre-commit run --files
11+
# Pass the filenames to pre-commit run --files (qa-checks excluded via manual stage)
12+
if [ -n "$all_files" ]; then
13+
file_args=$(echo "$all_files" | tr '\n' ' ')
14+
uv run pre-commit run --files $file_args
15+
else
16+
echo "No files to format."
17+
fi

generator

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/bin/bash
2+
3+
# Combined generator script for Atlan Python SDK
4+
# This script runs both create_typedefs_file.py and class_generator.py
5+
# It intelligently skips creating typedefs if they already exist and are current
6+
7+
# Usage: ./generator [typedefs_file_path]
8+
# If typedefs_file_path is not provided, defaults to /tmp/typedefs.json
9+
10+
echo "🚀 Starting Atlan Python SDK code generation..."
11+
12+
# Check if ATLAN_BASE_URL and ATLAN_API_KEY are set
13+
if [ -z "$ATLAN_BASE_URL" ] || [ -z "$ATLAN_API_KEY" ]; then
14+
echo "❌ Error: ATLAN_BASE_URL and ATLAN_API_KEY environment variables must be set."
15+
echo "Please set these variables before running the generator:"
16+
echo " export ATLAN_BASE_URL='https://your-atlan-instance.com'"
17+
echo " export ATLAN_API_KEY='your-api-key'"
18+
exit 1
19+
fi
20+
21+
# Get the typedefs file path from command line argument or use default
22+
if [ -n "$1" ]; then
23+
TYPE_DEF_FILE="$1"
24+
echo "📁 Using custom typedefs file: $TYPE_DEF_FILE"
25+
else
26+
TMPDIR=${TMPDIR:-/tmp}
27+
TYPE_DEF_FILE="$TMPDIR/typedefs.json"
28+
echo "📁 Using default typedefs file: $TYPE_DEF_FILE"
29+
fi
30+
31+
# Check if typedefs file exists and is current (created today)
32+
SHOULD_CREATE_TYPEDEFS=true
33+
if [ -f "$TYPE_DEF_FILE" ]; then
34+
# Check if file was created today
35+
if [ "$(date -r "$TYPE_DEF_FILE" +%Y-%m-%d)" = "$(date +%Y-%m-%d)" ]; then
36+
echo "✅ Typedefs file already exists and is current: $TYPE_DEF_FILE"
37+
SHOULD_CREATE_TYPEDEFS=false
38+
else
39+
echo "⏰ Typedefs file exists but is not current, will recreate it"
40+
fi
41+
else
42+
echo "📄 Typedefs file does not exist, will create it"
43+
fi
44+
45+
# Step 1: Create typedefs file if needed
46+
if [ "$SHOULD_CREATE_TYPEDEFS" = true ]; then
47+
echo "🔄 Running create_typedefs_file.py..."
48+
if uv run python pyatlan/generator/create_typedefs_file.py --typedefs-file "$TYPE_DEF_FILE"; then
49+
echo "✅ Typedefs file created successfully"
50+
else
51+
echo "❌ Failed to create typedefs file"
52+
exit 1
53+
fi
54+
else
55+
echo "⏭️ Skipping typedefs creation (file is current)"
56+
fi
57+
58+
# Step 2: Run class generator
59+
echo "🔄 Running class_generator.py..."
60+
if uv run python pyatlan/generator/class_generator.py --typedefs-file "$TYPE_DEF_FILE"; then
61+
echo "✅ Class generation completed successfully"
62+
else
63+
echo "❌ Class generation failed"
64+
exit 1
65+
fi
66+
67+
# Step 3: Format the generated code
68+
echo "🎨 Formatting generated code..."
69+
if uv run ./formatter; then
70+
echo "✅ Code formatting completed"
71+
else
72+
echo "⚠️ Code formatting had issues, but generation completed"
73+
fi
74+
75+
echo "🎉 SDK code generation completed successfully!"
76+
echo "📁 Generated files are in: pyatlan/model/assets/"

mypy.ini

Lines changed: 0 additions & 2 deletions
This file was deleted.

pyatlan/generator/class_generator.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,23 @@ def get_type(type_: str):
9595
return ret_value
9696

9797

98-
def get_type_defs() -> TypeDefResponse:
98+
def get_type_defs(typedefs_file_path=None) -> TypeDefResponse:
99+
# Use provided path or default to tmp directory
100+
if typedefs_file_path:
101+
typedef_file = Path(typedefs_file_path)
102+
else:
103+
typedef_file = TYPE_DEF_FILE
104+
99105
if (
100-
not TYPE_DEF_FILE.exists()
101-
or datetime.date.fromtimestamp(os.path.getmtime(TYPE_DEF_FILE))
106+
not typedef_file.exists()
107+
or datetime.date.fromtimestamp(os.path.getmtime(typedef_file))
102108
< datetime.date.today()
103109
):
104110
raise ClassGenerationError(
105111
"File containing typedefs does not exist or is not current."
106-
f" Please run create_typedefs_file to create {TYPE_DEF_FILE}."
112+
f" Please run create_typedefs_file to create {typedef_file}."
107113
)
108-
with TYPE_DEF_FILE.open() as input_file:
114+
with typedef_file.open() as input_file:
109115
return TypeDefResponse(**json.load(input_file))
110116

111117

@@ -982,7 +988,19 @@ def filter_attributes_of_custom_entity_type():
982988

983989

984990
if __name__ == "__main__":
985-
type_defs = get_type_defs()
991+
import argparse
992+
993+
parser = argparse.ArgumentParser(
994+
description="Generate Atlan SDK classes from typedefs"
995+
)
996+
parser.add_argument(
997+
"--typedefs-file",
998+
type=str,
999+
help="Path to the typedefs file (default: /tmp/typedefs.json or $TMPDIR/typedefs.json)",
1000+
)
1001+
args = parser.parse_args()
1002+
1003+
type_defs = get_type_defs(args.typedefs_file)
9861004
filter_attributes_of_custom_entity_type()
9871005
AssetInfo.sub_type_names_to_ignore = type_defs.custom_entity_def_names
9881006
AssetInfo.set_entity_defs(type_defs.reserved_entity_defs)

pyatlan/generator/create_typedefs_file.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,46 @@
66
environment variables should be set before running this script.
77
"""
88

9+
import argparse
10+
import os
11+
from pathlib import Path
12+
913
from pyatlan.client.atlan import AtlanClient
10-
from pyatlan.generator.class_generator import TYPE_DEF_FILE
1114

1215

1316
class ServerError(Exception):
1417
pass
1518

1619

17-
def create_typedef_file():
20+
def create_typedef_file(typedefs_file_path=None):
21+
# Use provided path or default to tmp directory
22+
if typedefs_file_path:
23+
typedef_file = Path(typedefs_file_path)
24+
else:
25+
typedef_file = Path(os.getenv("TMPDIR", "/tmp")) / "typedefs.json"
26+
1827
client = AtlanClient()
1928
type_defs = client.typedef.get_all()
2029
if len(type_defs.entity_defs) == 0:
2130
raise ServerError("No entity definitions were returned from the server.")
22-
with TYPE_DEF_FILE.open("w") as output_file:
31+
32+
# Create directory if it doesn't exist
33+
typedef_file.parent.mkdir(parents=True, exist_ok=True)
34+
35+
with typedef_file.open("w") as output_file:
2336
output_file.write(type_defs.json())
24-
print(f"{TYPE_DEF_FILE} has been created.")
37+
print(f"{typedef_file} has been created.")
2538

2639

2740
if __name__ == "__main__":
28-
create_typedef_file()
41+
parser = argparse.ArgumentParser(
42+
description="Create typedefs file from Atlan instance"
43+
)
44+
parser.add_argument(
45+
"--typedefs-file",
46+
type=str,
47+
help="Path to the typedefs file (default: /tmp/typedefs.json or $TMPDIR/typedefs.json)",
48+
)
49+
args = parser.parse_args()
50+
51+
create_typedef_file(args.typedefs_file)

pyatlan/test_utils/base_vcr.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
# SPDX-License-Identifier: Apache-2.0
22
# Copyright 2025 Atlan Pte. Ltd.
33

4-
import pkg_resources # type: ignore[import-untyped]
4+
import importlib.metadata
55

66
from pyatlan.errors import DependencyNotFoundError
77

88
# Check if pytest-vcr plugin is installed
99
try:
10-
pkg_resources.get_distribution("pytest-vcr")
11-
except pkg_resources.DistributionNotFound:
10+
importlib.metadata.distribution("pytest-vcr")
11+
except importlib.metadata.PackageNotFoundError:
1212
raise DependencyNotFoundError(
1313
"pytest-vcr plugin is not installed. Please install pytest-vcr."
1414
)
1515

1616
# Check if vcrpy is installed and ensure the version is 6.0.x
1717
try:
18-
vcr_version = pkg_resources.get_distribution("vcrpy").version
18+
vcr_version = importlib.metadata.distribution("vcrpy").version
1919
if not vcr_version.startswith("6.0"):
2020
raise DependencyNotFoundError(
2121
f"vcrpy version 6.0.x is required, but found {vcr_version}. Please install the correct version."
2222
)
23-
except pkg_resources.DistributionNotFound:
23+
except importlib.metadata.PackageNotFoundError:
2424
raise DependencyNotFoundError(
2525
"vcrpy version 6.0.x is not installed. Please install vcrpy version 6.0.x."
2626
)

pyproject.toml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
[build-system]
2+
requires = ["setuptools>=61.0", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "pyatlan"
7+
dynamic = ["version"]
8+
description = "Atlan Python Client"
9+
readme = "README.md"
10+
license = {text = "Apache LICENSE 2.0"}
11+
authors = [
12+
{name = "Atlan Technologies Pvt Ltd", email = "[email protected]"}
13+
]
14+
maintainers = [
15+
{name = "Atlan Technologies Pvt Ltd", email = "[email protected]"}
16+
]
17+
keywords = ["atlan", "client"]
18+
classifiers = [
19+
"Programming Language :: Python :: 3.8",
20+
"Programming Language :: Python :: 3.9",
21+
"Programming Language :: Python :: 3.10",
22+
"Programming Language :: Python :: 3.11",
23+
"Programming Language :: Python :: 3.12",
24+
"Programming Language :: Python :: 3.13",
25+
"License :: OSI Approved :: Apache Software License",
26+
"Operating System :: OS Independent",
27+
"Development Status :: 5 - Production/Stable",
28+
]
29+
requires-python = ">=3.8"
30+
dependencies = [
31+
"requests~=2.32.3",
32+
"pydantic~=2.10.6",
33+
"jinja2~=3.1.6",
34+
"tenacity~=9.0.0",
35+
"urllib3>=1.26.0,<3",
36+
"lazy_loader~=0.4",
37+
"nanoid~=2.0.0",
38+
"pytz~=2025.1",
39+
"python-dateutil~=2.9.0.post0",
40+
"PyYAML~=6.0.2",
41+
]
42+
43+
[project.optional-dependencies]
44+
dev = [
45+
"mypy~=1.9.0",
46+
"ruff~=0.9.9",
47+
"types-requests~=2.31.0.6",
48+
"types-setuptools~=75.8.0.20250110",
49+
"pytest~=8.3.4",
50+
"pytest-vcr~=1.0.2",
51+
"vcrpy~=6.0.2",
52+
"pytest-order~=1.3.0",
53+
"pytest-timer[termcolor]~=1.0.0",
54+
"pytest-sugar~=1.0.0",
55+
"retry~=0.9.2",
56+
"pre-commit~=3.5.0",
57+
"deepdiff~=7.0.1",
58+
"pytest-cov~=5.0.0",
59+
"twine~=6.1.0",
60+
"types-retry~=0.9.9.20241221",
61+
"networkx~=3.1.0",
62+
"networkx-stubs~=0.0.1",
63+
]
64+
65+
[project.urls]
66+
Homepage = "https://github.com/atlanhq/atlan-python"
67+
Repository = "https://github.com/atlanhq/atlan-python"
68+
Documentation = "https://github.com/atlanhq/atlan-python"
69+
Issues = "https://github.com/atlanhq/atlan-python/issues"
70+
71+
[tool.setuptools.packages.find]
72+
where = ["."]
73+
include = ["pyatlan*"]
74+
75+
[tool.setuptools.package-data]
76+
pyatlan = ["py.typed", "logging.conf"]
77+
"*" = ["*.jinja2"]
78+
79+
[tool.setuptools.dynamic]
80+
version = {file = "pyatlan/version.txt"}
81+
82+
[tool.mypy]
83+
plugins = ["pydantic.mypy"]
84+
85+
[tool.ruff]
86+
fix = true
87+
line-length = 88
88+
exclude = ["env", "venv", "__pycache__"]
89+
90+
[tool.ruff.lint.isort]
91+
split-on-trailing-comma = false
92+
93+
[tool.ruff.lint.per-file-ignores]
94+
"tests/*" = ["S101"]
95+
"pyatlan/model/assets.py" = ["S307"]
96+
"pyatlan/model/assets/**.py" = ["E402", "F811"]
97+
"pyatlan/model/assets/core/**.py" = ["E402", "F811"]
98+
99+
[tool.pytest.ini_options]
100+
addopts = "-p no:name_of_plugin"
101+
filterwarnings = [
102+
"ignore::DeprecationWarning",
103+
"ignore:urllib3 v2 only supports OpenSSL 1.1.1+",
104+
]

0 commit comments

Comments
 (0)