Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions sdk/ai/azure-ai-projects/PYTHON_GAPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Java vs Python SDK — Custom Code Comparison

## Datasets

| Feature | Python | Java | Status |
|---|---|---|---|
| `upload_file` / `createDatasetWithFile` | ✅ Takes `connection_name` param | ✅ Takes `connectionName` param | ✅ Same |
| `upload_folder` / `createDatasetWithFolder` | ✅ Takes `connection_name` param, `file_pattern` regex filter | ✅ Takes `connectionName` param, no `file_pattern` | ⚠️ **Gap** — Java is missing `file_pattern`. Python lets you filter which files to upload via regex. |
| `list` / `listLatestVersion` ||| ✅ Same |
| `list_versions` / `listVersions` ||| ✅ Same |
| `get` / `getDatasetVersion` ||| ✅ Same |
| `delete` / `deleteVersion` ||| ✅ Same |
| `create_or_update` / `createOrUpdateVersion` ||| ✅ Same |
| `pending_upload` / `pendingUpload` ||| ✅ Same |
| `get_credentials` / `getCredentials` ||| ✅ Same |

## Connections

| Feature | Python | Java | Status |
|---|---|---|---|
| `list` / `listConnections` ||| ✅ Same |
| `get(name, include_credentials=)` | ✅ Single method with flag |`getConnection(name, includeCredentials)` | ✅ Same |
| `get_default(connection_type, include_credentials=)` ||`getDefaultConnection(connectionType, includeCredentials)` | ✅ Same |

## Deployments

| Feature | Python | Java | Status |
|---|---|---|---|
| `list` / `listDeployments` ||| ✅ Same |
| `get` / `getDeployment` ||| ✅ Same |

## Indexes

| Feature | Python | Java | Status |
|---|---|---|---|
| `list` / `listLatest` ||| ✅ Same |
| `list_versions` / `listVersions` ||| ✅ Same |
| `get` / `getVersion` ||| ✅ Same |
| `delete` / `deleteVersion` ||| ✅ Same |
| `create_or_update` / `createOrUpdateVersion` ||| ✅ Same |

## Telemetry

| Feature | Python | Java | Status |
|---|---|---|---|
| `telemetry.get_application_insights_connection_string()` | ✅ Custom sub-client |**Missing entirely** | 🔴 **Gap** — Python has a `TelemetryOperations` sub-client. Java has nothing. |

## Memory Stores (Beta)

| Feature | Python | Java | Status |
|---|---|---|---|
| `beta.memory_stores.search_memories()` | ✅ Custom patch with OpenAI `ResponseInputParam` support | ✅ Surfaced in `azure-ai-agents` SDK | ✅ Different SDK, not a gap |
| `beta.memory_stores.begin_update_memories()` | ✅ Custom LRO poller | ✅ Surfaced in `azure-ai-agents` SDK | ✅ Different SDK, not a gap |

## Top-level Client

| Feature | Python | Java | Status |
|---|---|---|---|
| `get_openai_client()` | ✅ Returns `openai.OpenAI` with auth wired up |`buildOpenAIClient()` on the builder | ✅ Different pattern but equivalent |
| Console logging via `AZURE_AI_PROJECTS_CONSOLE_LOGGING` ||| Minor — nice-to-have |
2 changes: 1 addition & 1 deletion sdk/ai/azure-ai-projects/assets.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"AssetsRepo":"Azure/azure-sdk-assets","AssetsRepoPrefixPath":"java","TagPrefix":"java/ai/azure-ai-projects","Tag": "java/ai/azure-ai-projects_de0a038f6e"}
{"AssetsRepo":"Azure/azure-sdk-assets","AssetsRepoPrefixPath":"java","TagPrefix":"java/ai/azure-ai-projects","Tag": "java/ai/azure-ai-projects_cd9e4bffa9"}
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,29 @@ public Mono<Connection> getConnection(String name, boolean includeCredentials) {
return getConnection(name);
}
}

/**
* Get the default connection for a given connection type.
*
* @param connectionType The type of the connection. Required.
* @param includeCredentials Whether to include credentials in the response.
* @throws IllegalArgumentException thrown if parameters fail the validation.
* @throws HttpResponseException thrown if the request is rejected by server.
* @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401.
* @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404.
* @throws ResourceModifiedException thrown if the request is rejected by server on status code 409.
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent.
* @return a Mono that completes with the default connection for the given type.
* @throws IllegalStateException if no default connection is found for the given type.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Connection> getDefaultConnection(ConnectionType connectionType, boolean includeCredentials) {
if (connectionType == null) {
return Mono.error(new IllegalArgumentException("connectionType cannot be null."));
}
return listConnections(connectionType, true).next()
.switchIfEmpty(
Mono.error(new IllegalStateException("No default connection found for type: " + connectionType)))
.flatMap(connection -> getConnection(connection.getName(), includeCredentials));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@
import com.azure.core.http.rest.RequestOptions;
import com.azure.core.http.rest.Response;
import com.azure.core.util.BinaryData;
import com.azure.core.util.logging.ClientLogger;

/**
* Initializes a new instance of the synchronous AIProjectClient type.
*/
@ServiceClient(builder = AIProjectClientBuilder.class)
public final class ConnectionsClient {

private static final ClientLogger LOGGER = new ClientLogger(ConnectionsClient.class);

@Generated
private final ConnectionsImpl serviceClient;

Expand Down Expand Up @@ -264,4 +267,30 @@ public Connection getConnection(String name, boolean includeCredentials) {
return getConnection(name);
}
}

/**
* Get the default connection for a given connection type.
*
* @param connectionType The type of the connection. Required.
* @param includeCredentials Whether to include credentials in the response.
* @throws IllegalArgumentException thrown if parameters fail the validation.
* @throws HttpResponseException thrown if the request is rejected by server.
* @throws ClientAuthenticationException thrown if the request is rejected by server on status code 401.
* @throws ResourceNotFoundException thrown if the request is rejected by server on status code 404.
* @throws ResourceModifiedException thrown if the request is rejected by server on status code 409.
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent.
* @return the default connection for the given type.
* @throws IllegalStateException if no default connection is found for the given type.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Connection getDefaultConnection(ConnectionType connectionType, boolean includeCredentials) {
if (connectionType == null) {
throw LOGGER.logExceptionAsError(new IllegalArgumentException("connectionType cannot be null."));
}
for (Connection connection : listConnections(connectionType, true)) {
return getConnection(connection.getName(), includeCredentials);
}
throw LOGGER
.logExceptionAsError(new IllegalStateException("No default connection found for type: " + connectionType));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@
import com.azure.core.util.FluxUtil;
import com.azure.storage.blob.BlobAsyncClient;
import com.azure.storage.blob.BlobClientBuilder;
import com.azure.storage.blob.BlobContainerAsyncClient;
import com.azure.storage.blob.BlobContainerClientBuilder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand Down Expand Up @@ -192,14 +195,35 @@ public Mono<DatasetCredential> getCredentials(String name, String version) {
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<FileDatasetVersion> createDatasetWithFile(String name, String version, Path filePath) {
return createDatasetWithFile(name, version, filePath, null);
}

/**
* Creates a dataset from a single file. Uploads the file to blob storage and registers the dataset.
*
* @param name The name of the resource.
* @param version The specific version id of the DatasetVersion to create or replace.
* @param filePath The path to the file to upload.
* @param connectionName The name of an Azure Storage Account connection to use for uploading.
* If null, the default Azure Storage Account connection will be used.
* @return A Mono that completes with a FileDatasetVersion representing the created dataset.
* @throws IllegalArgumentException If the provided path is not a file
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<FileDatasetVersion> createDatasetWithFile(String name, String version, Path filePath,
String connectionName) {
if (!Files.isRegularFile(filePath)) {
return Mono.error(new IllegalArgumentException("The provided path is not a file: " + filePath));
}
PendingUploadRequest request = new PendingUploadRequest();
if (connectionName != null) {
request.setConnectionName(connectionName);
}
return this.pendingUpload(name, version, request).flatMap(pendingUploadResponse -> {
String blobUri = pendingUploadResponse.getBlobReference().getBlobUrl();
String sasUri = pendingUploadResponse.getBlobReference().getCredential().getSasUrl();
BlobAsyncClient blobClient = new BlobClientBuilder().endpoint(sasUri).blobName(name).buildAsyncClient();
BlobAsyncClient blobClient = new BlobClientBuilder().endpoint(sasUri)
.blobName(filePath.getFileName().toString())
.buildAsyncClient();
return blobClient.upload(BinaryData.fromFile(filePath), true).thenReturn(blobClient.getBlobUrl());
}).flatMap(blobUrl -> {
RequestOptions requestOptions = new RequestOptions();
Expand All @@ -222,33 +246,48 @@ public Mono<FileDatasetVersion> createDatasetWithFile(String name, String versio
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<FolderDatasetVersion> createDatasetWithFolder(String name, String version, Path folderPath) {
return createDatasetWithFolder(name, version, folderPath, null);
}

/**
* Creates a dataset from a folder. Uploads all files in the folder to blob storage and registers the dataset.
*
* @param name The name of the resource.
* @param version The specific version id of the DatasetVersion to create or replace.
* @param folderPath The path to the folder containing files to upload.
* @param connectionName The name of an Azure Storage Account connection to use for uploading.
* If null, the default Azure Storage Account connection will be used.
* @return A Mono that completes with a FolderDatasetVersion representing the created dataset.
* @throws IllegalArgumentException If the provided path is not a directory.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<FolderDatasetVersion> createDatasetWithFolder(String name, String version, Path folderPath,
String connectionName) {
if (!Files.isDirectory(folderPath)) {
return Mono.error(new IllegalArgumentException("The provided path is not a folder: " + folderPath));
}
// Request a pending upload for the folder
PendingUploadRequest request = new PendingUploadRequest();
if (connectionName != null) {
request.setConnectionName(connectionName);
}
return this.pendingUpload(name, version, request).flatMap(pendingUploadResponse -> {
String blobContainerUri = pendingUploadResponse.getBlobReference().getBlobUrl();
String containerUrl = pendingUploadResponse.getBlobReference().getBlobUrl();
String sasUri = pendingUploadResponse.getBlobReference().getCredential().getSasUrl();
String containerUrl = blobContainerUri.substring(0, blobContainerUri.lastIndexOf('/'));
// Find all files in the directory
BlobContainerAsyncClient containerClient
= new BlobContainerClientBuilder().endpoint(sasUri).buildAsyncClient();
try {
List<Path> files = Files.walk(folderPath).filter(Files::isRegularFile).collect(Collectors.toList());
// Upload each file in the directory one after another
List<Path> files;
try (Stream<Path> fileStream = Files.walk(folderPath)) {
files = fileStream.filter(Files::isRegularFile).collect(Collectors.toList());
}
return Flux.fromIterable(files).flatMap(filePath -> {
// Calculate relative path from the base folder
String relativePath = folderPath.relativize(filePath).toString().replace('\\', '/');
// Create blob client for each file
BlobAsyncClient blobClient
= new BlobClientBuilder().endpoint(sasUri).blobName(relativePath).buildAsyncClient();
// Upload the file
return blobClient.upload(BinaryData.fromFile(filePath), true);
return containerClient.getBlobAsyncClient(relativePath).upload(BinaryData.fromFile(filePath), true);
}).then(Mono.just(containerUrl));
} catch (Exception e) {
return Mono.error(new RuntimeException("Error walking through folder path", e));
}
}).flatMap(containerUrl -> {
// Create a FolderDatasetVersion with the container URL
RequestOptions requestOptions = new RequestOptions();
FolderDatasetVersion folderDataset = new FolderDatasetVersion().setDataUri(containerUrl);
return this
Expand Down
Loading