Skip to content

Commit

Permalink
Add patch! methods for multiple records (#10)
Browse files Browse the repository at this point in the history
* fix multi-record post! to only do 10 records at a time

* ignore .envrc

* add tests for multi-patch

* bump minor version

* try to fix failures from other CI runs deleting records

* remove test manifest

* generate json to include ci string

Co-authored-by: Kevin Bonham <[email protected]>
  • Loading branch information
kescobo and Kevin Bonham authored May 6, 2022
1 parent d713462 commit 9d1a571
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 90 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
Manifest.toml
/docs/build/
.vscode
.envrc
test/add_records.json
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Airtable"
uuid = "96f7d883-6668-4fbe-bb01-b60427b16035"
authors = ["Kevin Bonham PhD <[email protected]>", "contributors"]
version = "0.2.2"
version = "0.2.3"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand Down
10 changes: 6 additions & 4 deletions docs/src/interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ AirRecord("recYvPIayZx1okJ41", AirTable("Table 1"), (Name = "Some Record", Notes

Notice that the return value is an [`AirRecord`](@ref).
It can be useful to hold onto this, since it contains the unique identifier.
You can also pass a vector of `NamedTuple`s to create multiple records.
You can also pass a vector of `NamedTuple`s to create multiple records
(`post!` will automatically limit 10 records at a time, in compliance with the Airtable API).

See the [note about rate limits](@ref ratelimit).

Expand All @@ -128,6 +129,10 @@ julia> Airtable.patch!(new_rec, (; Status="Done", Notes=missing))
AirRecord("recYvPIayZx1okJ41", AirTable("Table 1"), (Name = "Some Record", Status = "Done"))
```

You can also pass a vector of `AirRecords`
along with an equal-length vector of `NamedTuple`s to patch multiple records at once
(`patch!` will automatically limit to 10 records at a time, in compliance with the Airtable API).

### Using `delete!`

To remove a record, simply pass an `AirRecord` with the same `id` to [`delete!`](@ref).
Expand All @@ -142,9 +147,6 @@ JSON3.Object{Base.CodeUnits{UInt8, String}, Vector{UInt64}} with 2 entries:
[^1]: This is the default, you can change this with the `pageSize` parameter,
but 100 is the maximum.

### Add/update records


## [A note on rate limits](@id ratelimit)

Airtable.com only allows 5 requests / sec.
Expand Down
34 changes: 29 additions & 5 deletions src/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,13 @@ end


function post!(cred::Credential, tab::AirTable, recs::Vector{<:NamedTuple})
recs = (;records = [(; fields = nt) for nt in recs])
resp = post!(cred, path(tab), ["Content-Type" => "application/json"], JSON3.write(recs))

return _extract_records(tab, resp)
resps = AirRecord[]
for recpart in Iterators.partition(recs, 10)
topost = (; records = [(; fields = nt) for nt in recpart])
resp = post!(cred, path(tab), ["Content-Type" => "application/json"], JSON3.write(topost))
append!(resps, _extract_records(tab, resp))
end
return resps
end

post!(tab::AirTable, rec::AirRecord) = post!(Credential(), tab, rec)
Expand All @@ -223,9 +226,30 @@ delete!(cred::Credential, rec::AirRecord) = delete!(cred, path(rec))
delete!(rec::AirRecord) = delete!(Credential(), rec)

patch!(cred::Credential, rec::AirRecord) = _extract_record(table(rec), patch!(cred, path(rec), ["Content-Type" => "application/json"], JSON3.write(rec)))
patch!(cred::Credential, rec::AirRecord, fields::NamedTuple) = _extract_record(table(rec), patch!(cred, path(rec), ["Content-Type" => "application/json"], JSON3.write((; fields))))

function patch!(cred::Credential, tab::AirTable, recs::Vector{<:AirRecord})
resps = AirRecord[]
for recpart in Iterators.partition(recs, 10)
resp = patch!(cred, path(tab), ["Content-Type" => "application/json"], JSON3.write((; records = [
(; id=id(rec), fields=fields(rec)) for rec in recpart
])))
append!(resps, _extract_records(tab, resp))
end
return resps
end

function patch!(cred::Credential, tab::AirTable, recs::Vector{<:AirRecord}, fields::Vector{<:NamedTuple})
length(recs) == length(fields) || throw(ArgumentError("Lengths of records vector and fields vector must be the same"))
patch!(cred, tab, [AirRecord(Airtable.id(rec), tab, fs) for (rec, fs) in zip(recs, fields)])
end


patch!(rec::AirRecord) = patch!(Credential(), rec)
patch!(cred::Credential, rec::AirRecord, fields::NamedTuple) = _extract_record(table(rec), patch!(cred, path(rec), ["Content-Type" => "application/json"], string("""{ "fields": """, JSON3.write(fields), " }")))
patch!(rec::AirRecord, fields::NamedTuple) = patch!(Credential(), rec, fields)
patch!(tab::AirTable, recs::Vector{<:AirRecord}) = patch!(Credential(), tab, recs)
patch!(tab::AirTable, recs::Vector{<:AirRecord}, fields::Vector{<:NamedTuple}) = patch!(Credential(), tab, recs, fields)


Base.getindex(rec::AirRecord, k::Symbol) = fields(rec)[k]
Base.keys(rec::AirRecord) = keys(fields(rec))
Expand Down
32 changes: 27 additions & 5 deletions test/AirtableTests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ using Airtable
using Airtable.HTTP
using Airtable.JSON3
using ReTest
using Random

const CI_STRING = randstring()


@testset "Airtable.jl" begin
@testset "Constructors" begin
Expand All @@ -26,27 +30,45 @@ using ReTest

@test length(Airtable.query(tab; filterByFormula="{Keep}")) == 3

resp = Airtable.post!(tab, open(joinpath(@__DIR__, "add_records.json")))

toadd = [
(; Name = "TEST1",
Notes = "Some note",
Status = "Todo",
CI = CI_STRING),
(; Name = "TEST2",
Notes = "Other note",
Status = "Done",
CI = CI_STRING)
]

resp = Airtable.post!(tab, toadd)
@test resp isa Vector{AirRecord}
@test length(resp) == 2

for rec in resp
sleep(1) # avoid over-taxing api

@test rec[:Status] != "In progress"
@test Airtable.patch!(rec, (; Status="In progress")).id == Airtable.id(rec)
@test Airtable.patch!(rec, (; Status="In progress", CI=CI_STRING)).id == Airtable.id(rec)
@test Airtable.get(rec)[:Status] == "In progress"

@test_throws HTTP.ExceptionRequest.StatusError Airtable.patch!(rec, (; Status="Not valid"))
Airtable.patch!(rec, (; Status = rec[:Status]))

@test keys(rec) == (:Name, :Notes, :Status)
@test keys(rec) == (:Name, :Notes, :Status, :CI)
@test Airtable.delete!(rec).deleted
@test_throws HTTP.ExceptionRequest.StatusError Airtable.get(rec)
end

JSON3.write("add_records.json", (; records = [(; fields = rec) for rec in toadd]))
resp = Airtable.post!(tab, open("add_records.json"))

Airtable.patch!(tab, resp, [(; Status="In progress", CI=CI_STRING) for _ in 1:length(resp)])
@test all([Airtable.get(rec)[:Status] == "In progress" for rec in resp])

end
# Cleanup
dontkeep = Airtable.query(AirTable("Table 1", AirBase("appphImnhJO8AXmmo")); filterByFormula="NOT({Keep})")
dontkeep = Airtable.query(AirTable("Table 1", AirBase("appphImnhJO8AXmmo")); filterByFormula="AND({CI}='$CI_STRING', NOT({Keep}))")
if !isempty(dontkeep)
sleep(1)
for rec in dontkeep
Expand Down
57 changes: 0 additions & 57 deletions test/Manifest.toml

This file was deleted.

1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[deps]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ReTest = "e0db7c4e-2690-44b9-bad6-7687da720f89"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
18 changes: 0 additions & 18 deletions test/add_records.json

This file was deleted.

2 comments on commit 9d1a571

@kescobo
Copy link
Owner Author

@kescobo kescobo commented on 9d1a571 May 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/59830

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.2.3 -m "<description of version>" 9d1a571bc6ae7885a15a4ac2ff7831f45dd0208d
git push origin v0.2.3

Please sign in to comment.