-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make dependencies optional #18
base: master
Are you sure you want to change the base?
Conversation
Oh and here's a REPL session to prove it basically works: julia> import Pkg;
julia> Pkg.activate(;temp=true);
julia> Pkg.develop(;path=".");
julia> Pkg.add("JSON");
julia> import Serde, JSON;
julia> x = Serde.to_json(5)
"5"
julia> @assert 5 == Serde.deser_json(Number, x) |
Hello, @har7an! We are big fans of Rust here, too. Thank you for your contribution. I really like the idea of giving options for providing your own parser. Having JSON as a weak dependency is definitely a boon, too. We should include something like that in a future release. I have only one concern though. Wouldn't it mean that we would have to keep track of compatibility with every parser option we add to ext? Maybe an overloaded function for a parser package would be a better course of action in this case? |
Hi @dmitrii-doronin,
I'm not sure I understand what you're referring to. With my changes in place, the But I'm not a seasoned Julia developer, so I may have a wrong picture there. That's how I understand the situation.
How exactly would that change the "keeping track of parser compatibility" bit? |
Ah, and I forgot:
I'm not saying we let users provide their own parsers, that is not this PRs scope (Although I guess that technically it might be possible). I'm merely allowing users to pull in the dependencies they want, but only from the set we support. So if you want to use |
Ah, I think i get you now. So in the grand scheme of things you want to make CSV, YAML, etc. optional for a user to install? |
Exactly, IMO users should "pay" only for what they really want to have in their application. |
That's a really good idea. I'd have to look deeper into Julia extensions, since, to be fair, I have never used this feature before. But we definitely want to merge this at some point. @gryumov cc |
Hello @har7an! Thank you for the suggestion – I really like this idea. @dmitrii-doronin, let's assess the user-friendliness of this feature and aim to propose a solution by next week. |
Great, I'll look into it. @har7an does this sit well with you? |
@dmitrii-doronin I think that'll work, or else I'll let you know. I should have a solution by tuesday, but probably earlier. I'll leave the question of whether you like my "design decisions" for later then and make a usable "prototype" first. Changing how we integrate the extensions into the main thing is comparatively little effort. |
Okay I added some more changes now, mostly to restore the tests back to working order. All the tests from the "JSON" group seem to pass now, but I get failures for test case 30 from My knowledge about Julia macros and the codebase isn't really big enough to determine the cause yet. Pretty much the only change I can think of that may have caused this is in Next I'll work on making all the other "markup languages" extensions as well. |
Moved It just occured to me: I think it would be a good idea to define the prototypes for all the |
I've seen this issue, too. It's version dependent, unfortunately. Conversion to an output type of functions seems to throw ErrorException in 1.10 instead of MethodError. Check the tests with 1.9.4. |
I like the idea. We could move all the interface functions into a singular module and then have extensions expand on them. it has recently occurred to me that we're missing 'ignore_field' for queries. Extending a singular interface to modules should alleviate this.
The best course I think would be to go in small incremental steps. We can workout this PR and use it for some time to see, whether it's convenient or not. Does it mean that usage of JSON extension would force user to explicitly import JSON in every use-case? |
Btw, you can ping me in Julia official slack. |
Jup, that works, thanks. Should I investigate that as part of this PR, or do we
I'm not sure we mean the same thing (yet). You can have a look at
That is correct. Afaik that's also how e.g. For the tests, I "solve" this by just importing all the packages at the top of
Thanks for the offer, but I don't have Slack and don't feel like changing that. I have now moved all the bits that need dependencies to extensions (JSON, YAML, The package in its current state, when imported, pulls in a total of 4
Afaik all of these are part of the Stdlib, so they don't require external code |
Hey there! I was playing around with this package a bit and came here to see if there is already some work going into making the parsers optional. Glad to see this already tackled! From my (little) experience / what I read and heard about package extensions I would like to offer a bit of information about how they are supposed to work. I believe this could be helpful to remove the fairly complicated (and possibly hacky...) infrastructure about exposing the extension functions from the main package. package extensions are supposed to extend on already existing functions rather than exporting new symbols. The docs have a nice example for this: https://pkgdocs.julialang.org/v1/creating-packages/#Conditional-loading-of-code-in-packages-(Extensions) The general workflow for the extension should thus be that all functions you want to extend are defined in the main package, either as simple as module Serde
function to_json end
function from_json end
...
end or with a default method which throws an informative error: module Serde
to_json(args...; kwargs...) = error("please import the JSON module...")
from_json(args...; kwargs...) = error("please import the JSON module...")
...
end and then in the extension you add more specific methods to the respective functions: module SerdeJSONExt
using Serde # get access to barebones functions
using JSON
function Serde.to_json(...)
# do things
end
function Serde.from_json(...)
# do things
end When you want add methods to a function which is owned by another package, you either have to explicity import it as |
Hi @SuperGrobi, thanks for sharing your insights! I already noticed that the current approach doesn't really work, unfortunately. It fails for example when trying to override the serialization behavior for a custom type. So I'll try to rebase the code and then pull out more potentially interesting functions into the base package, so we only override existing things in the extension. I'll keep you posted here. |
87d52a8
to
123ee10
Compare
Ah. I think I see the problem now. The relevant functions are currently under I just had another idea, which might or might not work with the whole package architecture... in theory it should be possible to dispatch on the modules used for serialisation: ser_value(m::Module, ::Type{T}, ::Val{x}, v::V) where {T,x,V}) = ser_value(Val(m), T, x, v) and then have users extend for JSON ser_value(::Val{JSON}, ::Type{MyType}, Val{MyFieldName}, v::MyFieldType) = ... (just an idea. this breaks if there is no external dependecy for serialisation.) |
@SuperGrobi I've had the same thought already, but I'm indecisive which is better. My thought was that, since we have the "module" names included in all the public functions already (i.e. If I were to write the code from scratch I'd probably go for your solution, though. |
Fair. That would come pretty close to a full rewrite. |
JSON and YAML should be back in a working state now, the rest I'll take care of another day. I've moved the If you disagree with my decision please let me know, but then we'll have to introduce a different, opaque error mechanism. |
So I've just tried to integrate the current state of the package with an application of mine, but it doesn't work as I expect. I'm at a loss with the dispatch mechanism and how methods for functions are actually chosen. I think that with the current design, my approach doesn't work. :( I'll try a few more things with dispatching on modules to see whether what I had initially intended is possible at all. I'll keep you updated. |
Hi, @har7an and @SuperGrobi. You're both doing amazing work here. I am preoccupied with other tasks right now, so I can't try tackling this issue currently. However, I know this package pretty well. So, if you need any insight into the inner workings of the package, just give me a ping here and I'll do my best to help you out with it. |
@har7an @SuperGrobi I gave it a thought and the best course of action, IMO, is to have own parsers inside Serde. We a) are not breaking the current interface and b) allow for the extensions without major rewrites. What do you think? |
Hey @dmitrii-doronin, sorry for the delay, I got a little sidetracked the last weeks. I'm not sure about that. Doesn't that entail effectively rewriting everything that's already done in Today I did a little exercise with dispatching on If an extension isn't loaded (due to e.g. the dependency not being loaded), it will throw a Then we can decide what to do: We can either keep the current interface and redirect each of these calls. For example: I'll go ahead and implement this today to see whether it can actually work. My plan is to rewrite the I see that quite a bit of code has been added since I last rebased. I'll first try to make this version of the code work (somehow) and then think about incorporating all the new code, I think. This is gonna be fun... |
TOML and YAML are now reimplemented, the tests still work. I'll implement the rest later today and then give you a quick rundown of how it works and what it looks like. |
and define all functions to be extended in the "base" module, rather than defining new functions inside the extension and making them accessible from the base package.
and define all functions to be extended in the "base" module, rather than defining new functions inside the extension and making them accessible from the base package.
of all code in the body to ensure they're recognized as methods without concrete implementation.
that will allow us to dispatch on functions from package extensions.
for the new public API to allow better function dispatch.
for the new public API to allow better function dispatch.
for the new public API to allow better function dispatch.
for the new public API to allow better function dispatch.
for the new public API to allow better function dispatch.
for when a required module isn't present, to inform the user what they have to do now.
for all extension modules and throw a descriptive error message when the user attempts to call a function without having the module imported.
for the `to_string` method and friends by deriving a symbol across the modules `fullname` rather than the first name component. This (hopefully) makes symbols unambiguous and allows adding methods for arbitrary user-provided modules, too.
in function definition and don't derive it by hand any longer.
in the XML and YAML extensions, which would cause `from_string` to fail in unexpected ways.
for all types that are now implemented with the extension mechanism.
`to_string`, `from_string` and `parse`.
that houses the new public API using the `to_string` and `from_string` functions and friends. These aren't exported since their names are pretty nondescript by themselves, so we're reimporting the in the base module to make them accessible there instead.
and integrate Query into the new module structure. It is now also implemented using dispatch and the new API functions `to_string` and friends. The old-style API has been kept intact.
into a new subdirectory `Formats/` so they don't clutter up the project base.
and, since the containing folder is not empty, move it to the `src/` directory.
and move the file to the `src/` folder since the containing folder is now empty.
37f2dd7
to
2716445
Compare
I tried integrating this into some code of mine and I noticed that, since we're using extensions now to provide YAML/JSON/..., users can no longer override the I guess technically these functions have never been part of the public API (they aren't exported, nor documented, nor mentioned in the docs) so we actually achieved encapsulation in Julia. I could imagine that more users than only me sort of use this in their own code, though... I think in the long run it would be neat to have something like a Or do we already have such a thing and I'm unaware of it? |
Hello,
thanks for creating this amazing package! I'll use it in a new project of mine to work with JSON and, being a Rust programmer, I was missing a replacement for
serde
until I found this. Amazing work!I tend to be very picky about the dependencies I pull into a project and try to keep them minimal. Since I only need JSON personally, I was wondering if the rest could be left out of the package.
This is my first attempt at writing an extension in Julia. What I did is make
JSON
a weak dependency and create a new module in theext/
subfolder for this purpose. Thanks to how the package is written, I pulled out the JSON-specificSerJson.jl
,ParJson.jl
andDeJson.jl
into that new extension asSer.jl
,Par.jl
andDe.jl
respectively and had to change barely a thing in the process.The
Serde
package now has a new fileJSON.jl
that checks at runtime whether theJSON
extension is present and exposes the functions from there to the user. Users that don'timport
theJSON
package will not be able to serialize/deserialize objects, but neither will JSON be pulled in unconditionally bySerde
. The code isn't pretty yet, I was mostly trying to see if it works and wanted to discuss the idea with you before investing more time.If you're interested, I'd be happy to hear your feedback and convert the other "languages" into extensions as well!
Pull request checklist