Skip to content

Commit

Permalink
Move ConstructionBase dependency to an extension on 1.9+
Browse files Browse the repository at this point in the history
The dependency on ConstructionBase serves to provide a few convenience
methods for users of packages like Accessors; it doesn't provide any
functionality used directly by Legolas. This makes it a perfect
candidate for a package extension.

Package extensions are new in Julia 1.9, and Legolas currently supports
Julia 1.6. That means that we can't fully remove ConstructionBase as a
direct dependency yet, but we can restructure things to make that as
easy as possible once we drop support for earlier Julia versions.
  • Loading branch information
ararslan committed Mar 26, 2024
1 parent 8d0bd60 commit afb2f60
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 23 deletions.
6 changes: 6 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[weakdeps]
ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"

[extensions]
LegolasConstructionBaseExt = "ConstructionBase"

[compat]
Accessors = "0.1"
Aqua = "0.6"
Expand Down
32 changes: 32 additions & 0 deletions ext/LegolasConstructionBaseExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module LegolasConstructionBaseExt

using ConstructionBase
using Legolas

using Legolas: AbstractRecord

# We need a bit of extra work to integrate with ConstructionBase for pre-1.7 due
# to the overload of `propertynames(::Tables.AbstractRow)`. We could overload
# `check_properties_are_fields` but that's not part of the public API, so this
# is safer.
if VERSION < v"1.7"
ConstructionBase.getproperties(r::AbstractRecord) = NamedTuple(r)

# This is largely copy-paste from `ConstructionBase.setproperties_object`:
# https://github.com/JuliaObjects/ConstructionBase.jl/blob/cd24e541fd90ab54d2ee12ddd6ccd229be9a5f1e/src/ConstructionBase.jl#L211-L218
function ConstructionBase.setproperties(r::R, patch::NamedTuple) where {R<:AbstractRecord}
nt = getproperties(r)
nt_new = merge(nt, patch)
check_patch_properties_exist(nt_new, nt, r, patch)
args = Tuple(nt_new) # old Julia inference prefers if we wrap in `Tuple`
return constructorof(R)(args...)
end
end

function ConstructionBase.constructorof(::Type{<:R}) where {R<:AbstractRecord}
nt = NamedTuple{fieldnames(R)}
T = Base.typename(R).wrapper
return (args...) -> T(nt(args))
end

end # module
8 changes: 7 additions & 1 deletion src/Legolas.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module Legolas

using Tables, Arrow, UUIDs
using ConstructionBase: ConstructionBase

const LEGOLAS_SCHEMA_QUALIFIED_METADATA_KEY = "legolas_schema_qualified"

Expand All @@ -10,4 +9,11 @@ include("schemas.jl")
include("tables.jl")
include("record_merge.jl")

# TODO: Once we require Julia 1.9 or later at a minimum, we can remove this as well as
# all entries in the Project.toml `[deps]` section that are also listed in `[weakdeps]`.
if !isdefined(Base, :get_extension)
include(joinpath(dirname(@__DIR__), "ext", "LegolasConstructionBaseExt.jl"))
using .LegolasConstructionBaseExt
end

end # module
22 changes: 0 additions & 22 deletions src/schemas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -345,24 +345,6 @@ abstract type AbstractRecord <: Tables.AbstractRow end
@inline Tables.columnnames(r::AbstractRecord) = fieldnames(typeof(r))
@inline Tables.schema(::AbstractVector{R}) where {R<:AbstractRecord} = Tables.Schema(fieldnames(R), fieldtypes(R))

# we need a bit of extra work to integrate with ConstructionBase for pre-1.7 due
# to the overload of `propertynames(::Tables.AbstractRow)`
#
# we _could_ overload `check_properties_are_fields` but that's no part of the
# public API so this is safer
@static if VERSION < v"1.7"
ConstructionBase.getproperties(r::AbstractRecord) = NamedTuple(r)
# largely copy-paste from ConstructionBase.setproperties_object:
# https://github.com/JuliaObjects/ConstructionBase.jl/blob/cd24e541fd90ab54d2ee12ddd6ccd229be9a5f1e/src/ConstructionBase.jl#L211-L218
function ConstructionBase.setproperties(r::R, patch::NamedTuple) where {R <: AbstractRecord}
nt = ConstructionBase.getproperties(r)
nt_new = merge(nt, patch)
ConstructionBase.check_patch_properties_exist(nt_new, nt, r, patch)
args = Tuple(nt_new) # old julia inference prefers if we wrap in Tuple
return ConstructionBase.constructorof(R)(args...)
end
end

"""
Legolas.schema_version_from_record(record::Legolas.AbstractRecord)
Expand Down Expand Up @@ -628,10 +610,6 @@ function _generate_record_type_definitions(schema_version::SchemaVersion, record
kwargs_from_row = [Expr(:kw, n, :(get(row, $(Base.Meta.quot(n)), missing))) for n in keys(record_fields)]
outer_constructor_definitions = quote
$R(row) = $R(; $(kwargs_from_row...))
function $ConstructionBase.constructorof(::Type{<:$R})
nt = NamedTuple{$((:($k) for k in keys(record_fields))..., )}
(args...) -> $R(nt(args))
end
end
if isempty(type_param_defs)
inner_constructor_definitions = quote
Expand Down

0 comments on commit afb2f60

Please sign in to comment.