Skip to content

Commit

Permalink
Some minor improvements suggested from Ned's use (#9249)
Browse files Browse the repository at this point in the history
- Adjusted `AWS_Credential` to have a `Default` and removed support for `Nothing` from functions.
- Renamed `Response_Body.to_file` to `Response_Body.write`.
- Add `write` to `Response`.
- Add `Table.get_value` and `DB_Table.get_value` allowing getting a single value from a table.
- Added `Data.download` allowing downloading from a URL to a file.
- Added text widget as input widget for `Data` methods.

![image](https://github.com/enso-org/enso/assets/4699705/fcfc8b1e-1197-4106-b8a7-43b1435327c0)
  • Loading branch information
jdunkerley authored Mar 4, 2024
1 parent 9ee4348 commit 7f6d10a
Show file tree
Hide file tree
Showing 16 changed files with 132 additions and 46 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@
- [Allow reading Data Links configured locally or in the Cloud.][9215]
- [Allow using `enso://` paths in `Data.read` and other places.][9225]
- [Update the XML methods and add more capabilities to document.][9233]
- [Added `Data.download` and a few other changes.][9249]

[debug-shortcuts]:
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
Expand Down Expand Up @@ -900,6 +901,7 @@
[9215]: https://github.com/enso-org/enso/pull/9215
[9225]: https://github.com/enso-org/enso/pull/9225
[9233]: https://github.com/enso-org/enso/pull/9233
[9249]: https://github.com/enso-org/enso/pull/9249

#### Enso Compiler

Expand Down
10 changes: 4 additions & 6 deletions distribution/lib/Standard/AWS/0.0.0-dev/src/AWS_Credential.enso
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ polyglot java import org.enso.aws.AwsCredential
polyglot java import org.enso.aws.ProfileReader

type AWS_Credential
## Access AWS using the default credential method.
Default

## Access using IAM via an AWS profile.

Arguments:
Expand All @@ -25,15 +28,10 @@ type AWS_Credential
profile_names = Vector.from_polyglot_array <|
ProfileReader.INSTANCE.getProfiles

## PRIVATE
default_widget : Widget
default_widget =
fqn = Meta.get_qualified_type_name AWS_Credential
make_single_choice [["default", "Nothing"], ["by profile", fqn + ".Profile"], ["by key", fqn + ".Key"]]

## PRIVATE
as_java : AWS_Credential | Nothing -> AwsCredential
as_java (credential : AWS_Credential | Nothing) = case credential of
AWS_Credential.Default -> AwsCredential.Default.new
AWS_Credential.Profile profile -> AwsCredential.Profile.new profile
AWS_Credential.Key access_key_id secret_access_key ->
AwsCredential.Key.new (as_hideable_value access_key_id) (as_hideable_value secret_access_key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type Redshift_Details
jdbc_properties self =
credentials = case self.credentials of
Nothing -> Pgpass.read self.host self.port self.schema
AWS_Credential.Default -> Pgpass.read self.host self.port self.schema
AWS_Credential.Profile profile ->
[Pair.new 'user' self.db_user] + (if profile == '' then [] else [Pair.new 'profile' profile])
AWS_Credential.Key access_key_id secret_access_key ->
Expand Down
2 changes: 0 additions & 2 deletions distribution/lib/Standard/AWS/0.0.0-dev/src/Errors.enso
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from Standard.Base import all

import project.AWS_Credential.AWS_Credential

polyglot java import software.amazon.awssdk.core.exception.SdkClientException

## An error in the core AWS SDK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import project.AWS_Credential.AWS_Credential

## PRIVATE
Decodes the JSON representation of `AWS_Credential` as defined in `dataLinkSchema.json#/$defs/AwsAuth`.
decode_aws_credential json -> AWS_Credential | Nothing =
decode_aws_credential json -> AWS_Credential =
case get_required_field "type" json of
"aws_auth" -> case get_required_field "subType" json of
"default" -> Nothing
"default" -> AWS_Credential.Default
"profile" ->
profile = get_required_field "profile" json
AWS_Credential.Profile profile
Expand Down
37 changes: 17 additions & 20 deletions distribution/lib/Standard/AWS/0.0.0-dev/src/S3/S3.enso
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ polyglot java import software.amazon.awssdk.services.s3.S3Client
Arguments:
- credentials: AWS credentials. If not provided, the default credentials will
be used.
@credentials AWS_Credential.default_widget
list_buckets : AWS_Credential | Nothing -> Vector Text ! S3_Error
list_buckets credentials:(AWS_Credential | Nothing)=Nothing = handle_s3_errors <|
list_buckets : AWS_Credential -> Vector Text ! S3_Error
list_buckets credentials:AWS_Credential=AWS_Credential.Default = handle_s3_errors <|
client = make_client credentials
buckets = client.listBuckets.buckets
buckets.map b->b.name
Expand All @@ -48,9 +47,8 @@ list_buckets credentials:(AWS_Credential | Nothing)=Nothing = handle_s3_errors <
- max_count: the maximum number of items to return. The default is 1000.
- credentials: AWS credentials. If not provided, the default credentials will
be used.
@credentials AWS_Credential.default_widget
list_objects : Text -> Text -> AWS_Credential | Nothing -> Integer -> Vector Text ! S3_Error
list_objects bucket prefix="" credentials:(AWS_Credential | Nothing)=Nothing max_count=1000 =
list_objects : Text -> Text -> AWS_Credential -> Integer -> Vector Text ! S3_Error
list_objects bucket prefix="" credentials:AWS_Credential=AWS_Credential.Default max_count=1000 =
read_bucket bucket prefix credentials delimiter="" max_count=max_count . second

## PRIVATE
Expand All @@ -62,9 +60,8 @@ list_objects bucket prefix="" credentials:(AWS_Credential | Nothing)=Nothing max
- prefix: The prefix to use when searching for keys to return.
- credentials: The credentials for the AWS resource.
- delimiter: The delimiter used to deduce common prefixes.
@credentials AWS_Credential.default_widget
read_bucket : Text -> Text -> AWS_Credential | Nothing -> Integer -> Text -> Pair Vector Vector ! S3_Error
read_bucket bucket prefix="" credentials:(AWS_Credential | Nothing)=Nothing delimiter="/" max_count=1000 = handle_s3_errors bucket=bucket <|
read_bucket : Text -> Text -> AWS_Credential -> Integer -> Text -> Pair Vector Vector ! S3_Error
read_bucket bucket prefix="" credentials:AWS_Credential=AWS_Credential.Default delimiter="/" max_count=1000 = handle_s3_errors bucket=bucket <|
client = make_client credentials

per_request = Math.min max_count 1000
Expand Down Expand Up @@ -95,8 +92,8 @@ read_bucket bucket prefix="" credentials:(AWS_Credential | Nothing)=Nothing deli
- key: the key of the object.
- credentials: AWS credentials. If not provided, the default credentials will
be used.
head : Text -> Text -> AWS_Credential | Nothing -> Map Text Any ! S3_Error
head bucket key="" credentials:(AWS_Credential | Nothing)=Nothing =
head : Text -> Text -> AWS_Credential -> Map Text Any ! S3_Error
head bucket key="" credentials:AWS_Credential=AWS_Credential.Default =
response = raw_head bucket key credentials
pairs = response.sdkFields.map f-> [f.memberName, f.getValueOrDefault response]
Map.from_vector pairs
Expand All @@ -108,7 +105,7 @@ head bucket key="" credentials:(AWS_Credential | Nothing)=Nothing =
- bucket: the name of the bucket.
- key: the key of the object.
- credentials: AWS credentials.
raw_head : Text -> Text -> AWS_Credential | Nothing -> Map Text Any ! S3_Error
raw_head : Text -> Text -> AWS_Credential -> Map Text Any ! S3_Error
raw_head bucket key credentials =
client = make_client credentials
case key == "" of
Expand All @@ -129,8 +126,8 @@ raw_head bucket key credentials =
- credentials: AWS credentials. If not provided, the default credentials will
be used.
- delimiter: The delimiter to use for deducing the filename from the path.
get_object : Text -> Text -> AWS_Credential | Nothing -> Response_Body ! S3_Error
get_object bucket key credentials:(AWS_Credential | Nothing)=Nothing delimiter="/" = handle_s3_errors bucket=bucket key=key <|
get_object : Text -> Text -> AWS_Credential -> Response_Body ! S3_Error
get_object bucket key credentials:AWS_Credential=AWS_Credential.Default delimiter="/" = handle_s3_errors bucket=bucket key=key <|
request = GetObjectRequest.builder.bucket bucket . key key . build

client = make_client credentials
Expand All @@ -147,26 +144,26 @@ get_object bucket key credentials:(AWS_Credential | Nothing)=Nothing delimiter="
Response_Body.Raw_Stream input_stream metadata s3_uri

## PRIVATE
put_object (bucket : Text) (key : Text) credentials:(AWS_Credential | Nothing)=Nothing request_body = handle_s3_errors bucket=bucket key=key <|
put_object (bucket : Text) (key : Text) credentials:AWS_Credential=AWS_Credential.Default request_body = handle_s3_errors bucket=bucket key=key <|
client = make_client credentials
request = PutObjectRequest.builder.bucket bucket . key key . build
client.putObject request request_body . if_not_error Nothing

## PRIVATE
upload_file (local_file : File) (bucket : Text) (key : Text) credentials:(AWS_Credential | Nothing)=Nothing = handle_s3_errors bucket=bucket key=key <|
upload_file (local_file : File) (bucket : Text) (key : Text) credentials:AWS_Credential=AWS_Credential.Default = handle_s3_errors bucket=bucket key=key <|
request_body = Request_Body.from_local_file local_file
put_object bucket key credentials request_body

## PRIVATE
Deletes the object.
It will not raise any errors if the object does not exist.
delete_object (bucket : Text) (key : Text) credentials:(AWS_Credential | Nothing)=Nothing = handle_s3_errors bucket=bucket key=key <|
delete_object (bucket : Text) (key : Text) credentials:AWS_Credential=AWS_Credential.Default = handle_s3_errors bucket=bucket key=key <|
client = make_client credentials
request = DeleteObjectRequest.builder . bucket bucket . key key . build
client.deleteObject request . if_not_error Nothing

## PRIVATE
copy_object (source_bucket : Text) (source_key : Text) (target_bucket : Text) (target_key : Text) credentials:(AWS_Credential | Nothing)=Nothing = handle_s3_errors bucket=source_bucket key=source_key <|
copy_object (source_bucket : Text) (source_key : Text) (target_bucket : Text) (target_key : Text) credentials:AWS_Credential=AWS_Credential.Default = handle_s3_errors bucket=source_bucket key=source_key <|
client = make_client credentials
request = CopyObjectRequest.builder
. destinationBucket target_bucket
Expand All @@ -189,8 +186,8 @@ handle_s3_errors ~action bucket="" key="" =
AWS_SDK_Error.handle_java_errors <| s3_handler <| action

## PRIVATE
make_client : (AWS_Credential | Nothing) -> S3Client
make_client credentials:(AWS_Credential | Nothing) =
make_client : AWS_Credential -> S3Client
make_client credentials:AWS_Credential =
builder = ClientBuilder.new (AWS_Credential.as_java credentials)
builder.buildS3Client

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ from project.Internal.Data_Link_Helpers import decode_aws_credential
## PRIVATE
type S3_Data_Link
## PRIVATE
Value (uri : Text) format (credentials : AWS_Credential | Nothing)
Value (uri : Text) format (credentials : AWS_Credential)

## PRIVATE
parse json -> S3_Data_Link =
Expand Down
6 changes: 3 additions & 3 deletions distribution/lib/Standard/AWS/0.0.0-dev/src/S3/S3_File.enso
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ type S3_File
If the path contains `.` or `..` segments, they will be normalized.
- credentials: The credentials to use when accessing the file.
If not specified, the default credentials are used.
new : Text -> AWS_Credential | Nothing -> S3_File ! Illegal_Argument
new (uri : Text = S3.uri_prefix) (credentials : AWS_Credential | Nothing = Nothing) =
new : Text -> AWS_Credential -> S3_File ! Illegal_Argument
new (uri : Text = S3.uri_prefix) credentials:AWS_Credential=AWS_Credential.Default =
S3_File.Value (S3_Path.parse uri) credentials

## PRIVATE
Value (s3_path : S3_Path) (credentials : AWS_Credential | Nothing)
Value (s3_path : S3_Path) credentials:AWS_Credential

## GROUP Standard.Base.Metadata
Gets the URI of this file
Expand Down
30 changes: 29 additions & 1 deletion distribution/lib/Standard/Base/0.0.0-dev/src/Data.enso
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import project.Network.HTTP.Request_Error
import project.Network.URI.URI
import project.Nothing.Nothing
import project.System.File.File
import project.System.File.Generic.Writable_File.Writable_File
from project.Data.Boolean import Boolean, False, True
from project.Metadata.Widget import Text_Input
from project.System.File_Format import Auto_Detect, File_Format

## ALIAS load, open
Expand Down Expand Up @@ -59,6 +61,7 @@ from project.System.File_Format import Auto_Detect, File_Format
import Standard.Examples

example_xls_to_table = Data.read Examples.xls (Excel (Worksheet 'Dates'))
@path Text_Input
@format File_Format.default_widget
read : Text | File -> File_Format -> Problem_Behavior -> Any ! File_Error
read path format=Auto_Detect (on_problems=Problem_Behavior.Report_Warning) = case path of
Expand Down Expand Up @@ -89,12 +92,14 @@ read path format=Auto_Detect (on_problems=Problem_Behavior.Report_Warning) = cas
import Standard.Examples

example_read = Data.read_text Examples.csv_path
@path Text_Input
@encoding Encoding.default_widget
read_text : (Text | File) -> Encoding -> Problem_Behavior -> Text
read_text path (encoding=Encoding.utf_8) (on_problems=Problem_Behavior.Report_Warning) =
File.new path . read_text encoding on_problems

## GROUP Input
ICON data_input
Lists files contained in the provided directory.

Arguments:
Expand Down Expand Up @@ -145,12 +150,14 @@ read_text path (encoding=Encoding.utf_8) (on_problems=Problem_Behavior.Report_Wa

example_list_files =
Data.list_directory Examples.data_dir name_filter="**.md" recursive=True
@directory Text_Input
list_directory : File -> Text | Nothing -> Boolean -> Vector File
list_directory directory:File (name_filter:(Text | Nothing)=Nothing) recursive:Boolean=False =
directory . list name_filter=name_filter recursive=recursive

## ALIAS download, http get
GROUP Input
ICON data_input
Fetches from the provided URI and returns the response, parsing the body if
the content-type is recognised. Returns an error if the status code does not
represent a successful response.
Expand All @@ -175,7 +182,8 @@ list_directory directory:File (name_filter:(Text | Nothing)=Nothing) recursive:B

import Standard.Base.Data
file = enso_project.data / "spreadsheet.xls"
Data.fetch URL . body . to_file file
Data.fetch URL . body . write file
@uri Text_Input
fetch : (URI | Text) -> HTTP_Method -> Vector (Header | Pair Text Text) -> Boolean -> Any ! Request_Error | HTTP_Error
fetch (uri:(URI | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (Header | Pair Text Text))=[]) (try_auto_parse_response:Boolean=True) =
response = HTTP.fetch uri method headers
Expand All @@ -188,6 +196,7 @@ fetch (uri:(URI | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (

## ALIAS http post, upload
GROUP Input
ICON data_output
Writes the provided data to the provided URI. Returns the response,
parsing the body if the content-type is recognised. Returns an error if the
status code does not represent a successful response.
Expand Down Expand Up @@ -304,8 +313,27 @@ fetch (uri:(URI | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (
test_file = enso_project.data / "sample.txt"
form_data = Map.from_vector [["key", "val"], ["a_file", test_file]]
response = Data.post url_post (Request_Body.Form_Data form_data url_encoded=True)
@uri Text_Input
post : (URI | Text) -> Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> Boolean -> Any ! Request_Error | HTTP_Error
post (uri:(URI | Text)) (body:Request_Body=Request_Body.Empty) (method:HTTP_Method=HTTP_Method.Post) (headers:(Vector (Header | Pair Text Text))=[]) (try_auto_parse_response:Boolean=True) =
response = HTTP.post uri body method headers
if try_auto_parse_response.not then response.with_materialized_body else
response.decode if_unsupported=response.with_materialized_body

## GROUP Input
ICON data_download
Fetches from the provided URI and if successful writes to the file. Returns
an error if the status code does not represent a successful response.

Arguments:
- uri: The URI to fetch.
- file: The file to write the response to.
- method: The HTTP method to use. Must be one of `HTTP_Method.Get`,
`HTTP_Method.Head`, `HTTP_Method.Delete`, `HTTP_Method.Options`.
Defaults to `HTTP_Method.Get`.
- headers: The headers to send with the request. Defaults to an empty vector.
@uri Text_Input
download : (URI | Text) -> Writable_File -> HTTP_Method -> Vector (Header | Pair Text Text) -> Boolean -> File ! Request_Error | HTTP_Error
download (uri:(URI | Text)) file:Writable_File (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (Header | Pair Text Text))=[]) =
response = HTTP.fetch uri method headers
response.write file
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import project.Network.HTTP.Response_Body.Response_Body
import project.Network.HTTP.Response_Body.Throw_Unsupported_Error
import project.Network.URI.URI
import project.Nothing.Nothing
import project.System.File.Existing_File_Behavior.Existing_File_Behavior
import project.System.File.File
import project.System.File.Generic.Writable_File.Writable_File
import project.System.File_Format.Auto_Detect
import project.System.File_Format.File_Format
import project.System.File_Format.Infer
Expand Down Expand Up @@ -156,12 +159,33 @@ type Response

import Standard.Examples

example_to_text = Examples.get_geo_data.decode_as_json
example_to_text = Data.fetch Examples.geo_data_url . decode_as_json
@encoding Encoding.default_widget
decode_as_json : Encoding | Infer -> JS_Object | Boolean | Number | Nothing | Text | Vector
decode_as_json self (encoding : Encoding | Infer = Infer) =
self.body.decode_as_json encoding

## GROUP Output
ALIAS to_file
Write response body to a File.

Arguments:
- file: The file to write the body to.
- on_existing_file: Specifies how to proceed if the file already exists.

> Examples
Write the contents of the request body to a scratch file on disk. The
file will be created if it does not exist, and will be overwritten if
it does.

import Standard.Examples

example_write =
Data.fetch Examples.geo_data_url . write Examples.scratch_file
write : Writable_File -> Existing_File_Behavior -> File
write self file:Writable_File on_existing_file=Existing_File_Behavior.Backup =
self.body.write file on_existing_file

## PRIVATE
Convert to a JavaScript Object representing this Response.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ type Response_Body
to_text self = "Response_Body"

## GROUP Output
ALIAS to_file
Write response body to a File.

Arguments:
Expand All @@ -194,10 +195,10 @@ type Response_Body

import Standard.Examples

example_to_file =
Examples.get_geo_data.to_file Examples.scratch_file
to_file : Writable_File -> Existing_File_Behavior -> File
to_file self file:Writable_File on_existing_file=Existing_File_Behavior.Backup =
example_write =
Examples.get_geo_data.write Examples.scratch_file
write : Writable_File -> Existing_File_Behavior -> File
write self file:Writable_File on_existing_file=Existing_File_Behavior.Backup =
self.with_stream body_stream->
file.write on_existing_file output_stream->
r = output_stream.write_stream body_stream
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
import Standard.Base.Errors.Illegal_State.Illegal_State
import Standard.Base.Errors.Unimplemented.Unimplemented
import Standard.Base.System.File.Generic.Writable_File.Writable_File
from Standard.Base.Metadata import make_single_choice
from Standard.Base.Metadata import make_single_choice, Widget
from Standard.Base.Runtime import assert
from Standard.Base.Widget_Helpers import make_delimiter_selector, make_format_chooser

Expand Down Expand Up @@ -146,6 +146,21 @@ type DB_Table
_ -> Error.throw (Illegal_Argument.Error "expected 'selector' to be either a Text or an Integer, but got "+(Meta.get_simple_type_name selector)+".")
if internal_column.is_nothing then if_missing else self.make_column internal_column

## ALIAS get cell, cell value
ICON select_column
Gets a value from the table.

Arguments:
- selector: The name or index of the column.
- index: The index of the value to get within the column.
- if_missing: The value to use if the selector isn't present.
@selector Widget_Helpers.make_column_name_selector
@index (t-> Widget.Numeric_Input minimum=0 maximum=t.row_count-1)
get_value : Text | Integer -> Integer -> Any -> Any
get_value self selector=0 index=0 ~if_missing=Nothing =
col = self.get selector if_missing=Nothing
if Nothing == col then if_missing else col.get index if_missing

## GROUP Standard.Base.Selections
ICON select_column
Gets the first column.
Expand Down
Loading

0 comments on commit 7f6d10a

Please sign in to comment.