Skip to content

Commit

Permalink
Widget support for Data.fetch and Data.post. (#11199)
Browse files Browse the repository at this point in the history
- Add `pretty` for `Date` and `Time`.
- Add constructors for `JS_Object` and `Dictionary` to the component browser.
- Add widgets to `Dictionary` methods.
![image](https://github.com/user-attachments/assets/4f6c58d5-9eb5-40e5-96c1-2e06e23051d0)
- Add conversion from Vector to Dictionary.
- Add `pair` method shorthand for `Pair.Value`.
- Created widget for `Header`.
- Added widgets for `Data.fetch` and `Data.post`.
- Added widgets for `Request_Body` constructors.
- Update the Forbidden Operation message to be friendlier.
![image](https://github.com/user-attachments/assets/eaac5def-a91f-450f-b814-d776311962e3)

Video before fixing Forbidden Message:

https://github.com/user-attachments/assets/f9c4bde4-3f0a-49f1-a3ca-a0aaa3219286

(cherry picked from commit 28bbc34)
  • Loading branch information
jdunkerley committed Oct 7, 2024
1 parent 58e6e55 commit efcaebd
Show file tree
Hide file tree
Showing 27 changed files with 191 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ instance =
## PRIVATE
create_dry_run_file file copy_original =
_ = [file, copy_original]
Error.throw (Forbidden_Operation.Error "Currently dry-run is not supported for S3_File, so writing to an S3_File is forbidden if the Output context is disabled.")
Error.throw (Forbidden_Operation.Error "As writing is disabled, no action has been performed. Press the Write button ▶ to write the file.")

## PRIVATE
remote_write_with_local_file file existing_file_behavior action =
Expand Down
8 changes: 4 additions & 4 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 @@ -67,7 +67,7 @@ type S3_File
value. The value is returned from this method.
with_output_stream : Vector File_Access -> (Output_Stream -> Any ! File_Error) -> Any ! File_Error
with_output_stream self (open_options : Vector) action = if self.is_directory then Error.throw (S3_Error.Error "S3 directory cannot be opened as a stream." self.uri) else
Context.Output.if_enabled disabled_message="Writing to an S3_File is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot write to S3. Press the Write button ▶ to perform the operation." panic=False <|
open_as_data_link = (open_options.contains Data_Link_Access.No_Follow . not) && (Data_Link.is_data_link self)
if open_as_data_link then Data_Link_Helpers.write_data_link_as_stream self open_options action else
if open_options.contains File_Access.Append then Error.throw (S3_Error.Error "S3 does not support appending to a file. Instead you may read it, modify and then write the new contents." self.uri) else
Expand Down Expand Up @@ -251,7 +251,7 @@ type S3_File
copy_to : File_Like -> Boolean -> Any ! S3_Error | File_Error
copy_to self (destination : File_Like) (replace_existing : Boolean = False) = Data_Link_Helpers.disallow_links_in_copy self destination <|
if self.is_directory then Error.throw (S3_Error.Error "Copying S3 folders is currently not implemented." self.uri) else
Context.Output.if_enabled disabled_message="Copying an S3_File is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot copy to a file. Press the Write button ▶ to perform the operation." panic=False <|
destination_writable = Writable_File.from destination
case destination_writable.file of
# Special shortcut for more efficient handling of S3 file copying (no need to move the data to our machine)
Expand Down Expand Up @@ -292,7 +292,7 @@ type S3_File
move_to self (destination : File_Like) (replace_existing : Boolean = False) =
if self.is_directory then Error.throw (S3_Error.Error "Moving S3 folders is currently not implemented." self.uri) else
Data_Link_Helpers.disallow_links_in_move self destination <|
Context.Output.if_enabled disabled_message="File moving is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot move the file. Press the Write button ▶ to perform the operation." panic=False <|
r = self.copy_to destination replace_existing=replace_existing
r.if_not_error <|
# If source and destination are the same, we do not want to delete the file
Expand Down Expand Up @@ -341,7 +341,7 @@ type S3_File
will occur.
delete_if_exists : Boolean -> Nothing
delete_if_exists self (recursive : Boolean = False) =
Context.Output.if_enabled disabled_message="Deleting an S3_File is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot delete the file. Press the Write button ▶ to perform the operation." panic=False <|
case self.is_directory of
True ->
# This is a temporary simplified implementation to ensure cleaning up after tests
Expand Down
11 changes: 7 additions & 4 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data.enso
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,9 @@ list (directory:(Text | File)=enso_project.root) (name_filter:Text="") recursive
Data.fetch URL . body . write file
@uri Text_Input
@format Data_Read_Helpers.format_widget_with_raw_response
@headers Header.default_widget
fetch : (URI | Text) -> HTTP_Method -> Vector (Header | Pair Text Text) -> File_Format -> Any ! Request_Error | HTTP_Error
fetch (uri:(URI | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (Header | Pair Text Text))=[]) (format = Auto_Detect) =
fetch (uri:(URI | Text)) (method:HTTP_Method=..Get) (headers:(Vector (Header | Pair Text Text))=[]) (format = Auto_Detect) =
Data_Read_Helpers.fetch_following_data_links uri method headers (Data_Read_Helpers.handle_legacy_format "fetch" "format" format)

## ALIAS http post, upload
Expand Down Expand Up @@ -321,9 +322,10 @@ fetch (uri:(URI | Text)) (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (
form_data = Dictionary.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
@headers Header.default_widget
@response_format Data_Read_Helpers.format_widget_with_raw_response
post : (URI | Text) -> Request_Body -> HTTP_Method -> Vector (Header | Pair Text Text) -> File_Format -> 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))=[]) (response_format = Auto_Detect) =
post (uri:(URI | Text)) (body:Request_Body=..Empty) (method:HTTP_Method=..Post) (headers:(Vector (Header | Pair Text Text))=[]) (response_format = Auto_Detect) =
response = HTTP.post uri body method headers
Data_Read_Helpers.decode_http_response_following_data_links response (Data_Read_Helpers.handle_legacy_format "post" "response_format" response_format)

Expand All @@ -340,9 +342,10 @@ post (uri:(URI | Text)) (body:Request_Body=Request_Body.Empty) (method:HTTP_Meth
Defaults to `HTTP_Method.Get`.
- headers: The headers to send with the request. Defaults to an empty vector.
@uri Text_Input
@headers Header.default_widget
download : (URI | Text) -> Writable_File -> HTTP_Method -> Vector (Header | Pair Text Text) -> File ! Request_Error | HTTP_Error
download (uri:(URI | Text)) file:Writable_File (method:HTTP_Method=HTTP_Method.Get) (headers:(Vector (Header | Pair Text Text))=[]) =
Context.Output.if_enabled disabled_message="Downloading to a file is forbidden as the Output context is disabled." panic=False <|
download (uri:(URI | Text)) file:Writable_File (method:HTTP_Method=..Get) (headers:(Vector (Header | Pair Text Text))=[]) =
Context.Output.if_enabled disabled_message="As writing is disabled, cannot download to a file. Press the Write button ▶ to perform the operation." panic=False <|
response = HTTP.fetch uri method headers
case Data_Link.is_data_link response.body.metadata of
True ->
Expand Down
50 changes: 41 additions & 9 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data/Dictionary.enso
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import project.Data.Pair.Pair
import project.Data.Text.Text
import project.Data.Vector.Vector
import project.Error.Error
import project.Errors.Common.Missing_Argument
import project.Errors.Illegal_Argument.Illegal_Argument
import project.Errors.No_Such_Key.No_Such_Key
import project.Meta
import project.Metadata.Widget
import project.Nothing.Nothing
import project.Panic.Panic
from project.Data.Boolean import Boolean, False, True
from project.Data.Text.Extensions import all
from project.Metadata.Choice import Option
from project.Metadata.Widget import Single_Choice, Vector_Editor
from project.Widget_Helpers import make_all_selector

## A key-value store. It is possible to use any type as keys and values and mix
them in one Dictionary. Keys are checked for equality based on their hash
Expand All @@ -32,14 +38,12 @@ from project.Data.Text.Extensions import all
to pass a foreign map into Enso, where it will be treated as a Dictionary.
@Builtin_Type
type Dictionary key value
## PRIVATE
ADVANCED
## ICON array_new2
Returns an empty dictionary.
empty : Dictionary
empty = @Builtin_Method "Dictionary.empty"

## PRIVATE
ADVANCED
## ICON array_new2
Returns a single-element dictionary with the given key and value.
A Call to `Dictionary.singleton key value` is the same as a call to
`Dictionary.empty.insert key value`.
Expand All @@ -53,12 +57,14 @@ type Dictionary key value
value 2.

example_singleton = Dictionary.singleton "my_key" 2
@key (make_all_selector ..Always)
@value (make_all_selector ..Always)
singleton : Any -> Any -> Dictionary
singleton key value = Dictionary.empty.insert key value

## ALIAS dictionary, lookup table
GROUP Constants
ICON convert
ICON array_new2
Builds a dictionary from two Vectors. The first vector contains the keys,
and the second vector contains the values. The two vectors must be of the
same length.
Expand All @@ -71,6 +77,8 @@ type Dictionary key value
meaning that if two entries in the vector share the same key, an
`Illegal_Argument` error is raised. If set to `False`, the last entry
with a given key will be kept.
@keys (Vector_Editor item_editor=make_all_selector display=..Always item_default="Nothing")
@values (Vector_Editor item_editor=make_all_selector display=..Always item_default="Nothing")
from_keys_and_values : Vector Any -> Vector Any -> Boolean -> Dictionary ! Illegal_Argument
from_keys_and_values keys:Vector values:Vector error_on_duplicates:Boolean=True =
if keys.length != values.length then Error.throw (Illegal_Argument.Error "`Dictionary.from_keys_and_values` encountered two vectors of different lengths.") else
Expand All @@ -80,7 +88,7 @@ type Dictionary key value

## ALIAS dictionary, lookup table
GROUP Constants
ICON convert
ICON array_new2
Builds a dictionary from a vector of key-value pairs, with each key-value
pair represented as a 2 element vector.

Expand All @@ -96,8 +104,9 @@ type Dictionary key value
Building a dictionary containing two key-value pairs.

example_from_vector = Dictionary.from_vector [["A", 1], ["B", 2]]
@vec key_value_widget
from_vector : Vector Any -> Boolean -> Dictionary ! Illegal_Argument
from_vector vec error_on_duplicates=True =
from_vector vec error_on_duplicates:Boolean=True =
vec.fold Dictionary.empty m-> el-> if el.length != 2 then Error.throw (Illegal_Argument.Error "`Dictionary.from_vector` encountered an invalid value. Each value in the vector has to be a key-value pair - it must have exactly 2 elements.") else
key = el.at 0
value = el.at 1
Expand Down Expand Up @@ -152,8 +161,11 @@ type Dictionary key value
import Standard.Examples

example_insert = Examples.dictionary.insert 7 "seven"
@key (make_all_selector ..Always)
@value (make_all_selector ..Always)
insert : Any -> Any -> Dictionary
insert self key value = @Builtin_Method "Dictionary.insert"
insert self key=(Missing_Argument.throw "key") value=(Missing_Argument.throw "value") =
self.insert_builtin key value

## GROUP Selections
ICON table_clean
Expand All @@ -170,8 +182,9 @@ type Dictionary key value
import Standard.Examples

Examples.dictionary.remove "A"
@key key_widget
remove : Any -> Dictionary ! No_Such_Key
remove self key =
remove self key=(Missing_Argument.throw "key") =
Panic.catch Any (self.remove_builtin key) _->
Error.throw (No_Such_Key.Error self key)

Expand All @@ -191,6 +204,7 @@ type Dictionary key value
import Standard.Examples

example_at = Examples.dictionary.at "A"
@key key_widget
at : Any -> Any ! No_Such_Key
at self key = self.get key (Error.throw (No_Such_Key.Error self key))

Expand All @@ -211,12 +225,14 @@ type Dictionary key value
import Standard.Examples

example_get = Examples.dictionary.get 2 "zero"
@key key_widget
get : Any -> Any -> Any
get self key ~if_missing=Nothing = self.get_builtin key if_missing

## GROUP Logical
ICON preparation
Returns True iff the Dictionary contains the given `key`.
@key (make_all_selector ..Always)
contains_key : Any -> Boolean
contains_key self key = @Builtin_Method "Dictionary.contains_key"

Expand Down Expand Up @@ -423,3 +439,19 @@ type Dictionary key value
## PRIVATE
get_builtin : Any -> Any -> Any
get_builtin self key ~if_missing = @Builtin_Method "Dictionary.get_builtin"

## PRIVATE
key_widget dict:Dictionary -> Widget =
values = dict.keys.map k-> Option k.to_text k.pretty
Single_Choice display=..Always values=values

## PRIVATE
key_value_widget -> Widget =
fqn = Meta.get_qualified_type_name Pair . drop (..Last 5)
default = 'pair "key" Nothing'
pair = Option "Pair" fqn+".pair" [["first", make_all_selector], ["second", make_all_selector]]
item_editor = Single_Choice display=..Always values=[pair]
Vector_Editor item_editor=item_editor display=..Always item_default=default

## PRIVATE
Dictionary.from (that:Vector) = Dictionary.from_vector that
19 changes: 15 additions & 4 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data/Json.enso
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ from project.Data.Ordering import all
from project.Data.Range.Extensions import all
from project.Data.Text.Extensions import all
from project.Metadata.Choice import Option
from project.Metadata.Widget import Single_Choice
from project.Metadata.Widget import Single_Choice, Text_Input, Vector_Editor
from project.Widget_Helpers import make_all_selector

polyglot java import com.fasterxml.jackson.core.JsonLocation
polyglot java import com.fasterxml.jackson.core.JsonProcessingException
Expand Down Expand Up @@ -161,10 +162,11 @@ type JS_Object
loop name_iterator builder
JS_Object.Value object_node (make_field_names object_node)

## PRIVATE
Creates a Jackson_Object from a list of key-value pairs.
## ICON braces
Creates a JS_Object from a list of key-value pairs.
Keys must be `Text` values.
Values will be recursively converted to JSON serializable as needed.
@pairs key_value_widget
from_pairs : Vector -> JS_Object
from_pairs pairs =
mapper = ObjectMapper.new
Expand Down Expand Up @@ -348,7 +350,7 @@ type JS_Object
## PRIVATE
Make a field name selector
make_field_name_selector : JS_Object -> Display -> Widget
make_field_name_selector js_object display=Display.Always =
make_field_name_selector js_object display:Display=..Always =
Single_Choice display=display values=(js_object.field_names.map n->(Option n n.pretty))

## PRIVATE
Expand Down Expand Up @@ -423,3 +425,12 @@ make_enso object =
_ -> js_object

if parsed.is_error then js_object else parsed

## PRIVATE
key_value_widget : Widget
key_value_widget =
fqn = Meta.get_qualified_type_name Pair . drop (..Last 5)
default = 'pair "key" Nothing'
pair = Option "Pair" fqn+".pair" [["first", Text_Input], ["second", make_all_selector]]
item_editor = Single_Choice display=..Always values=[pair]
Vector_Editor item_editor=item_editor display=..Always item_default=default
9 changes: 9 additions & 0 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data/Pair.enso
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,12 @@ check_start_valid start function max=3 =
used_start = if start < 0 then start + 2 else start
if used_start < 0 || used_start >= max then Error.throw (Index_Out_Of_Bounds.Error start max) else
function used_start

## ICON array_new
Create a new Pair from two elements.

Arguments:
- first: The first element.
- second: The second element.
pair : Any -> Any -> Pair
pair first second -> Pair = Pair.new first second
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,11 @@ type Date
format self format:Date_Time_Formatter=Date_Time_Formatter.iso_date =
format.format_date self

## PRIVATE
Convert to a Enso code representation of this Date.
pretty : Text
pretty self = "(Date.new " + self.year.to_text + " " + self.month.to_text + " " + self.day.to_text + ")"

## PRIVATE
week_days_between start end =
## We split the interval into 3 periods: the first week (containing the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,18 @@ type Time_Of_Day
format self format:Date_Time_Formatter =
format.format_time self

## PRIVATE
Convert to a Enso code representation of this Time_Of_Day.
pretty : Text
pretty self = "(Time_Of_Day.new "
+ (if self.hour == 0 then "" else " hour="+self.hour.to_text)
+ (if self.minute == 0 then "" else " minute="+self.minute.to_text)
+ (if self.second == 0 then "" else " second="+self.second.to_text)
+ (if self.millisecond == 0 then "" else " millisecond="+self.millisecond.to_text)
+ (if self.microsecond == 0 then "" else " microsecond="+self.microsecond.to_text)
+ (if self.nanosecond == 0 then "" else " nanosecond="+self.nanosecond.to_text)
+ ")"

## PRIVATE
Time_Of_Day.from (that:JS_Object) =
## Must have hour and minute but second and nanosecond are optional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ type Enso_File
value. The value is returned from this method.
with_output_stream : Vector File_Access -> (Output_Stream -> Any ! File_Error) -> Any ! File_Error
with_output_stream self (open_options : Vector) action =
Context.Output.if_enabled disabled_message="Writing to an Enso_File is forbidden as the Output context is disabled." panic=False <|
Context.Output.if_enabled disabled_message="As writing is disabled, cannot write to a file. Press the Write button ▶ to perform the operation." panic=False <|
is_data_link = Data_Link.is_data_link self
open_as_data_link = (open_options.contains Data_Link_Access.No_Follow . not) && is_data_link
if open_as_data_link then Data_Link_Helpers.write_data_link_as_stream self open_options action else
Expand Down
Loading

0 comments on commit efcaebd

Please sign in to comment.