Current section
Files
Jump to
Current section
Files
examples/file_upload_multipart.exs
defmodule Tinkex.Examples.FileUploadMultipart do
@moduledoc """
Demonstrates multipart/form-data encoding capability in Tinkex.
This example shows how the SDK can build multipart payloads with file uploads,
matching Python SDK parity. The multipart infrastructure supports:
- File paths (read and encode automatically)
- Raw binary content
- Tuples with metadata: {filename, content}, {filename, content, content_type}
- Nested form fields with bracket notation (foo[bar]=value)
NOTE: The Tinker API currently has no endpoints that accept file uploads.
This example demonstrates the SDK capability for when such endpoints are added,
or for use with custom endpoints that accept multipart/form-data.
"""
alias Tinkex.Files.Transform, as: FileTransform
alias Tinkex.Multipart.{Encoder, FormSerializer}
def run do
{:ok, _} = Application.ensure_all_started(:tinkex)
file_path = resolve_file_path()
note = System.get_env("TINKER_UPLOAD_NOTE", "Multipart demo from Tinkex")
IO.puts("=" |> String.duplicate(60))
IO.puts("Tinkex Multipart Encoding Demo")
IO.puts("=" |> String.duplicate(60))
# Step 1: Show the input file
file_size = File.stat!(file_path).size
IO.puts("\n[1] Input File:")
IO.puts(" Path: #{file_path}")
IO.puts(" Size: #{file_size} bytes")
# Step 2: Transform files (reads path into binary, extracts filename)
files = %{"file" => file_path}
{:ok, normalized_files} = FileTransform.transform_files(files)
IO.puts("\n[2] File Transformation:")
IO.puts(" Input: %{\"file\" => \"#{file_path}\"}")
case normalized_files do
%{"file" => {filename, content}} ->
IO.puts(" Output: %{\"file\" => {\"#{filename}\", <<#{byte_size(content)} bytes>>}}")
%{"file" => content} when is_binary(content) ->
IO.puts(" Output: %{\"file\" => <<#{byte_size(content)} bytes>>}")
other ->
IO.puts(" Output: #{inspect(other)}")
end
# Step 3: Serialize form fields (nested maps become bracket notation)
body = %{note: note, metadata: %{source: "tinkex", version: Tinkex.Version.current()}}
form_fields = FormSerializer.serialize_form_fields(body)
IO.puts("\n[3] Form Field Serialization:")
IO.puts(" Input: #{inspect(body)}")
IO.puts(" Output: #{inspect(form_fields)}")
# Step 4: Encode multipart body
{:ok, multipart_body, content_type} = Encoder.encode_multipart(form_fields, normalized_files)
IO.puts("\n[4] Multipart Encoding:")
IO.puts(" Content-Type: #{content_type}")
IO.puts(" Body size: #{byte_size(multipart_body)} bytes")
# Show multipart structure (first 500 chars, redacting binary)
IO.puts("\n[5] Multipart Body Preview:")
preview = multipart_body |> String.slice(0, 500) |> String.replace(~r/[^\x20-\x7E\r\n]/, ".")
IO.puts(" #{String.replace(preview, "\r\n", "\n ")}")
if byte_size(multipart_body) > 500 do
IO.puts(" ... (#{byte_size(multipart_body) - 500} more bytes)")
end
# Step 5: Demonstrate API integration (optional, requires API key)
IO.puts("\n[6] API Integration:")
case System.get_env("TINKER_API_KEY") do
nil ->
IO.puts(" Skipped - TINKER_API_KEY not set")
IO.puts(" (Set TINKER_API_KEY and TINKER_UPLOAD_ENDPOINT to test live upload)")
api_key ->
case System.get_env("TINKER_UPLOAD_ENDPOINT") do
nil ->
IO.puts(" API key present but TINKER_UPLOAD_ENDPOINT not set")
IO.puts(" Note: The Tinker API has no file upload endpoints currently.")
IO.puts(" Set TINKER_UPLOAD_ENDPOINT to test against a custom endpoint.")
endpoint ->
IO.puts(" Attempting POST to #{endpoint}...")
config =
Tinkex.Config.new(
api_key: api_key,
base_url:
System.get_env(
"TINKER_BASE_URL",
"https://tinker.thinkingmachines.dev/services/tinker-prod"
)
)
case Tinkex.API.post(endpoint, body, config: config, files: files) do
{:ok, response} ->
IO.puts(" Success!")
IO.inspect(response, label: " Response")
{:error, error} ->
IO.puts(" Failed: #{inspect(error)}")
end
end
end
IO.puts("\n" <> String.duplicate("=", 60))
IO.puts("Demo complete. Multipart encoding is working correctly.")
IO.puts(String.duplicate("=", 60))
end
defp resolve_file_path do
env_path = System.get_env("TINKER_UPLOAD_FILE")
default_path = Path.join(["examples", "uploads", "sample_upload.bin"])
cond do
env_path && env_path != "" ->
if File.exists?(env_path) do
env_path
else
raise "TINKER_UPLOAD_FILE set to #{env_path} but file does not exist"
end
File.exists?(default_path) ->
default_path
true ->
raise "Missing upload file; set TINKER_UPLOAD_FILE or ensure #{default_path} exists"
end
end
end
Tinkex.Examples.FileUploadMultipart.run()