OxygenFramework / Oxygen.jl

💨 A breath of fresh air for programming web apps in Julia
https://oxygenframework.github.io/Oxygen.jl/
MIT License
383 stars 25 forks source link

Does `@oxidise` mess with anything route related? My HTMX calls are no longer functional #164

Closed Dale-Black closed 4 months ago

Dale-Black commented 4 months ago
          No there isn't. I just changed that. Does `@oxidise` mess with anything route related? My HTMX calls are no longer functional
Info: 2024-02-15T07:25:00-0800 - 127.0.0.1:62474 - "GET /todo HTTP/1.1" 200
┌ Error: ERROR: 
│   exception =
│    MethodError: no method matching get(::Dict{String, String}, ::String, ::String)
│    You may have intended to import Base.get
│    
│    Closest candidates are:
│      get(::Function, ::String)
│       @ TodoApp ~/.julia/packages/Oxygen/Zd8hC/src/methods.jl:207
│    
│    Stacktrace:
│      [1] (::TodoApp.var"#9#10")(req::Request)
│        @ TodoApp ~/Library/CloudStorage/GoogleDrive-dalejamesblack@gmail.com/My Drive/dev/julia/HTMLStrings/examples/TodoApp/src/todo.jl:12
│      [2] (::Oxygen.Core.var"#47#52"{TodoApp.var"#9#10"})(req::Request)
│        @ Oxygen.Core ~/.julia/packages/Oxygen/Zd8hC/src/core.jl:610
│      [3] (::HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing})(req::Request)
│        @ HTTP.Handlers ~/.julia/packages/HTTP/bDoga/src/Handlers.jl:439
│      [4] (::Oxygen.Core.var"#21#23"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, String})(req::Request)
│        @ Oxygen.Core ~/.julia/packages/Oxygen/Zd8hC/src/core.jl:359
│      [5] (::Oxygen.Core.var"#27#30"{Request, Oxygen.Core.var"#21#23"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, String}})()
│        @ Oxygen.Core ~/.julia/packages/Oxygen/Zd8hC/src/core.jl:373
│      [6] handlerequest(getresponse::Oxygen.Core.var"#27#30"{Request, Oxygen.Core.var"#21#23"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, String}}, catch_errors::Bool; show_errors::Bool)
│        @ Oxygen.Core.Util ~/.julia/packages/Oxygen/Zd8hC/src/utilities/misc.jl:114
│      [7] handlerequest
│        @ Oxygen.Core.Util ~/.julia/packages/Oxygen/Zd8hC/src/utilities/misc.jl:109 [inlined]
│      [8] (::Oxygen.Core.var"#26#29"{Oxygen.Core.var"#21#23"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, String}, Bool, Bool})(req::Request)
│        @ Oxygen.Core ~/.julia/packages/Oxygen/Zd8hC/src/core.jl:372
│      [9] #invokelatest#2
│        @ ./essentials.jl:887 [inlined]
│     [10] invokelatest
│        @ ./essentials.jl:884 [inlined]
│     [11] #1
│        @ ~/Library/CloudStorage/GoogleDrive-dalejamesblack@gmail.com/My Drive/dev/julia/HTMLStrings/examples/TodoApp/server.jl:9 [inlined]
│     [12] (::Oxygen.Core.var"#33#36"{Request, var"#1#2"{Oxygen.Core.var"#26#29"{Oxygen.Core.var"#21#23"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, String}, Bool, Bool}}, DataStructures.CircularDeque{Oxygen.Core.Types.HTTPTransaction}, String})()
│        @ Oxygen.Core ~/.julia/packages/Oxygen/Zd8hC/src/core.jl:395
│     [13] handlerequest(getresponse::Oxygen.Core.var"#33#36"{Request, var"#1#2"{Oxygen.Core.var"#26#29"{Oxygen.Core.var"#21#23"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, String}, Bool, Bool}}, DataStructures.CircularDeque{Oxygen.Core.Types.HTTPTransaction}, String}, catch_errors::Bool; show_errors::Bool)
│        @ Oxygen.Core.Util ~/.julia/packages/Oxygen/Zd8hC/src/utilities/misc.jl:114
│     [14] handlerequest
│        @ ~/.julia/packages/Oxygen/Zd8hC/src/utilities/misc.jl:109 [inlined]
│     [15] #32
│        @ ~/.julia/packages/Oxygen/Zd8hC/src/core.jl:384 [inlined]
│     [16] #4
│        @ ~/.julia/packages/Oxygen/Zd8hC/src/core.jl:168 [inlined]
│     [17] (::HTTP.Handlers.var"#1#2"{Oxygen.Core.var"#4#6"{Oxygen.Core.var"#32#35"{var"#1#2"{Oxygen.Core.var"#26#29"{Oxygen.Core.var"#21#23"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, String}, Bool, Bool}}, DataStructures.CircularDeque{Oxygen.Core.Types.HTTPTransaction}, Bool, String}, Sockets.IPv4}})(stream::HTTP.Streams.Stream{Request, HTTP.Connections.Connection{Sockets.TCPSocket}})
│        @ HTTP.Handlers ~/.julia/packages/HTTP/bDoga/src/Handlers.jl:58
│     [18] (::Oxygen.Core.var"#7#8"{Oxygen.Core.var"#32#35"{var"#1#2"{Oxygen.Core.var"#26#29"{Oxygen.Core.var"#21#23"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, String}, Bool, Bool}}, DataStructures.CircularDeque{Oxygen.Core.Types.HTTPTransaction}, Bool, String}})(stream::HTTP.Streams.Stream{Request, HTTP.Connections.Connection{Sockets.TCPSocket}})
│        @ Oxygen.Core ~/.julia/packages/Oxygen/Zd8hC/src/core.jl:183
│     [19] #invokelatest#2
│        @ ./essentials.jl:887 [inlined]
│     [20] invokelatest
│        @ ./essentials.jl:884 [inlined]
│     [21] handle_connection(f::Function, c::HTTP.Connections.Connection{Sockets.TCPSocket}, listener::HTTP.Servers.Listener{Nothing, Sockets.TCPServer}, readtimeout::Int64, access_log::Function)
│        @ HTTP.Servers ~/.julia/packages/HTTP/bDoga/src/Servers.jl:450
│     [22] (::HTTP.Servers.var"#16#17"{Oxygen.Core.var"#7#8"{Oxygen.Core.var"#32#35"{var"#1#2"{Oxygen.Core.var"#26#29"{Oxygen.Core.var"#21#23"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}, String}, Bool, Bool}}, DataStructures.CircularDeque{Oxygen.Core.Types.HTTPTransaction}, Bool, String}}, HTTP.Servers.Listener{Nothing, Sockets.TCPServer}, Set{HTTP.Connections.Connection}, Int64, Oxygen.Core.var"#17#18", Base.Semaphore, HTTP.Connections.Connection{Sockets.TCPSocket}})()
│        @ HTTP.Servers ~/.julia/packages/HTTP/bDoga/src/Servers.jl:386
└ @ Oxygen.Core.Util ~/.julia/packages/Oxygen/Zd8hC/src/utilities/misc.jl:117

Originally posted by @Dale-Black in https://github.com/OxygenFramework/Oxygen.jl/issues/122#issuecomment-1946322439

Dale-Black commented 4 months ago

Didn't want to derail #122 so posted here. Were there changes to @get calls? My original code is now longer working after updating to master

JanisErdmanis commented 4 months ago

Unfortunately, it was not possible to extend the Base.get with the @oxidise macro as that broke precompilation. There are three solutions which you can do as the user of the new @oxidise macro:

There does not look much to do without a breaking change. Perhaps we could export the functional syntax get, post, put only when @oxidise macro is used and thus import Base: get could be included.

Dale-Black commented 4 months ago

Ahh yes I like the second option, that works great. This is pretty exciting! It's still a little janky doing frontend but "full stack" now feels doable! Great, work on the HMR stuff, thank you.

2024-02-15 12 49 16

JanisErdmanis commented 4 months ago

Very cool animation. Perhaps @ndortega can add it to the readme!

Dale-Black commented 4 months ago

Thanks! I just added an updated gif to my HTMLStrings.jl readme here (https://github.com/Dale-Black/HTMLStrings.jl/blob/main/images/todo_app.gif) if it's of use.

If there is any interest, I would be happy to talk more about integrating HTMLStrings with Oxygen.jl even tighter. It's a super simple templating DSL. I know I would love to have Oxygen.jl and HTMLStrings.jl work together to build static HTML pages

ndortega commented 4 months ago

Hi @Dale-Black,

Do you mind checking out and testing this branch: feature/oxidise-context-refactor I'm planning on merging it soon and wanted to see if this impacted your workflow. In my testing, this branch no longer requires you to do the following:

import Base: get
import Oxygen; Oxygen.@oxidise

You should be able to import Oxygen like you did before and call the new macro like below

using Oxygen; @oxidise

You can try it out by running the following

pkg> add https://github.com/OxygenFramework/Oxygen.jl#feature/oxidise-context-refactor

Let me know what you think. Fair warning, I'm planning on merging this branch sooner rather than later to get ready for a release sometime next week.

JanisErdmanis commented 4 months ago

@ndortega the warning appears when @oxidise macro is used from within the module during precompilation:

        Info Given ModuleA was explicitly requested, output will be shown live 
WARNING: Method definition get(Function, String) in module Oxygen at /Users/jerdmanis/BtSync/Projects/Oxygen.jl/src/methods.jl:207 overwritten in module ModuleA on the same line (check for duplicate calls to `include`).
ERROR: Method overwriting is not permitted during Module precompilation. Use `__precompile__(false)` to opt-out of precompilation.
  ? ModuleA
[ Info: Precompiling ModuleA [d71a4861-d403-46ba-8c26-d84b084f954f]
WARNING: Method definition get(Function, String) in module Oxygen at /Users/jerdmanis/BtSync/Projects/Oxygen.jl/src/methods.jl:207 overwritten in module ModuleA on the same line (check for duplicate calls to `include`).
ERROR: Method overwriting is not permitted during Module precompilation. Use `__precompile__(false)` to opt-out of precompilation.

The change you made with import Base: get in oxidise macro is equivalent to putting that statement in methods.jl. This was the reason why it was put at the top of the scope, keeping the get to work with using Oxygen as previously and thus only if one uses @oxidise macro behaviour is different.

ndortega commented 4 months ago

Hi @JanisErdmanis,

Thanks for bringing that up, can you show me the steps to reproduce that precompilation warning?

Don't worry I also so your comments on the pr, rest assured I have good reasons for making those changes, but I won't be able to respond till this afternoon.

JanisErdmanis commented 4 months ago

Create a package OxygenTest with the following contents:

module OxygenInstance

using Oxygen
import Base: get # This simulates the inclusion of this statement in `@oxidise` macro
@oxidise

@get "/inside" function(req::Request)
    return "Hello from Inside now 232332"
end

@get "/test" function(req::Request)
     return "Tes"
end

end 

Use it as a module:

using OxygenInstance

function ReviseHandler(handle)
    req -> begin
        Revise.revise()
        invokelatest(handle, req)
    end
end

server = OxygenInstance.serve(port=2345; middleware=[ReviseHandler])
Dale-Black commented 4 months ago

Hi @Dale-Black,

Do you mind checking out and testing this branch: feature/oxidise-context-refactor I'm planning on merging it soon and wanted to see if this impacted your workflow. In my testing, this branch no longer requires you to do the following:

import Base: get
import Oxygen; Oxygen.@oxidise

You should be able to import Oxygen like you did before and call the new macro like below

using Oxygen; @oxidise

You can try it out by running the following

pkg> add https://github.com/OxygenFramework/Oxygen.jl#feature/oxidise-context-refactor

Let me know what you think. Fair warning, I'm planning on merging this branch sooner rather than later to get ready for a release sometime next week.

Hi, sorry for not replying last week. Is this still open? If so, I can give it a quick test

ndortega commented 4 months ago

Hi @Dale-Black,

I've reverted the breaking things I introduced, but did push some modifications. These changes went live in v1.5.0 let me know if you face any issues in this latest version.

Dale-Black commented 4 months ago

Amazing, this just works now

module TodoApp

import Oxygen: @get, @post, @delete
Oxygen.@oxidise
import HTTP
using HTMLStrings

include("routes/index.jl")
include("routes/todo.jl")

end # module

Fantastic work and great library

JanisErdmanis commented 2 months ago

I just had an unexpected epiphany. The experience with respect to the get method shadowing can be made better. To avoid the issues where shadowed get method overtakes Base.get method we could do the following:

get(collection, key) = Base.get(collection, key)

get(func::Function, path::String) = route([GET], path, func)
get(func::Function, path::Function) = route([GET], path, func)

@ndortega can we add the additional method for get?

ndortega commented 2 months ago

Great Idea! I'll create some tests with both get() functions to make sure the shadowing works as expected

JanisErdmanis commented 2 months ago

Note that you may need to add get(collection, key) = Base.get(collection, key) within the @oxidise macro so that it would not conflict with import Base: get which would be overwritten otherwise. Or alternativelly remove import Base: get which shall also work as long as user does not reference get method before Oxygen is imported.