If you have two modules, A and B, which should contain two functions, bar and baz, that depend each other's module, this can be implemented by first declaring the functions as empty, and adding methods afterwards:
module Wrapper
module A
const x = 1
function bar end
end # module A
module B
const x = 2
function baz end
end # module B
import .A: bar
import .B: baz
bar(expr) = quote
println("bar", $(B.x))
$expr
end
baz(expr) = quote
println("baz", $(A.x))
$expr
end
end # module Wrapper
However, I have a case where A and B contain macros that depend on bar and baz. Since (I think?) I cannot add methods to a macro from outside a module, they have to be declared inside. But then I cannot circumvent the cyclic imports anymore -- the following fails because WARNING: could not import Wrapper.B into A, leaving B undefined in A:
module Wrapper
module A
import ..Wrapper.B
const x = 1
macro foo(expr)
B.baz(expr)
end
function bar end
end # module A
module B
import ..Wrapper.A
const x = 2
macro foo(expr)
A.bar(expr)
end
function baz end
end # module B
import .A: bar
import .B: baz
bar(expr) = quote
println("bar", $(B.x))
$expr
end
baz(expr) = quote
println("baz", $(A.x))
$expr
end
end # module Wrapper
Is there any possibility how this pattern can be implemented? (Just renaming the foos and moving them into Wrapper is not really an option, since I want the names to be the same...).
I could solve the problem by declaring bar and baz outside of A and B, using and redefining them in the submodules, and finally implementing their methods afterwards:
module Wrapper
function bar end
function baz end
module A
import ..Wrapper
const x = 1
macro foo(expr)
Wrapper.baz(expr)
end
const bar = Wrapper.bar
end # module A
module B
import ..Wrapper
const x = 2
macro foo(expr)
Wrapper.bar(expr)
end
const baz = Wrapper.baz
end # module B
import .A
import .B
bar(expr) = quote
println("bar", $(B.x))
$expr
end
baz(expr) = quote
println("baz", $(A.x))
$expr
end
end # module Wrapper
Result:
julia> Wrapper.A.bar(1)
quote
#= /tmp/test.jl:119 =#
println("bar", 2)
#= /tmp/test.jl:120 =#
1
end
julia> Wrapper.B.baz(1)
quote
#= /tmp/test.jl:124 =#
println("baz", 1)
#= /tmp/test.jl:125 =#
1
end
julia> Wrapper.B.#foo 1
bar2
1
julia> Wrapper.A.#foo 1
baz1
1
Related
Sorbet's T.class_of can be used to match descendant classes, but not classes that include a module:
module X; end
class A; end
class B < A; include X; end
B.ancestors # => [B, X, A, Object, PP::ObjectMixin, Kernel, BasicObject]
sig { params(x: T.class_of(A)).void }
def is_a(x); end
is_a(A) # => ok
is_a(B) # => ok
sig { params(x: T.class_of(X)).void }
def is_b(x); end
is_b(X) # => ok
is_b(B) # => error
According to https://sorbet.org/docs/class-of, this is because B is not an
instance of X's singleton_class:
B.is_a?(A.singleton_class) # => true
B.is_a?(B.singleton_class) # => true
B.is_a?(X.singleton_class) # => false
But no alternative suggestion is provided. Is there a way to type check based on the contents of a class' ancestors? i.e. something like class_of that can be used to check for a class mixing in a module?
I'm using https://github.com/kenn/active_flag and https://github.com/chanzuckerberg/sorbet-rails
This is what its rbi looks like:
module ActiveFlag
extend ActiveSupport::Concern
end
class ActiveFlag::Definition
def column; end
def human(key, options = nil); end
def humans; end
def initialize(column, keys, klass); end
def keys; end
def maps; end
def pairs; end
def set_all!(key); end
def to_array(integer); end
def to_i(arg); end
def to_value(instance, integer); end
def unset_all!(key); end
end
class ActiveFlag::Railtie < Rails::Railtie
end
class ActiveFlag::Value < Set
def method_missing(symbol, *args, &block); end
def raw; end
def set!(key, options = nil); end
def set(key); end
def set?(key); end
def to_human; end
def to_s; end
def unset!(key, options = nil); end
def unset(key); end
def unset?(key); end
def with(instance, definition); end
end
module ActiveFlag::ClassMethods
def flag(column, keys); end
end
class ActiveRecord::Base
extend ActiveFlag::ClassMethods
end
The last bit (extending AR::Base) I added, the rest srb rbi gems generated automatically.
To actually use Active Flag, I then do this in my model:
flag :visible_to, [:employee, :manager, :admin]
visible_to is an integer column. Sorbet Rails has already typed it as such:
sig { returns(Integer) }
def visible_to; end
sig { params(value: Integer).void }
def visible_to=(value); end
sig { returns(T::Boolean) }
def visible_to?; end
I could change this definition, but it's an autogenerated file and I assume the changes will get lost. So the next thing I tried was adding a sig directly above where the method gets used:
sig { returns(ActiveFlag::Value) }
def visible_to; end
flag :visible_to, [:employee, :manager, :admin]
The problem here is that Sorbet fails because the method I've "defined" doesn't return anything. Which I know is fine, because it'll get overriden when flag is called (it uses define_method internally), but I don't know how to communicate this to Sorbet.
Returning value that does not conform to method result type https://srb.help/7005
54 | def visible_to; end
^^^^^^^^^^^^^^^^^^^
Expected ActiveFlag::Value
Method visible_to has return type ActiveFlag::Value
54 | def visible_to; end
^^^^^^^^^^^^^^
Got NilClass originating from:
54 | def visible_to; e
So my question is. What's the best way to tell Sorbet that this method will return an ActiveFlag::Value once it gets defined, ideally without making a manual change to an autogenerated file?
btw. I tried to see how types for enum in Rails are handled... it doesn't look like that's been done on sorbet-typed yet. Possibly for the same reason.
When you want to override a generated rbi file you can create a second rbi file for the same class in your workspace and move the methods in there. Sorbet will handle merging multiple definitions files for you. We keep these files in a separate sorbet/custom directory next to the generated files; others keep the rbi files next to the rb files in their application directory.
As to enums, Sorbet does not natively support enum literal types so that's likely why.
You could implement a custom plugin for sorbet-rails to generate methods added by active_flag and remove the sigs generated wrongly. Here is the documentation for it:
https://github.com/chanzuckerberg/sorbet-rails/blob/master/README.md#extending-model-generation-task-with-custom-plugins
Is it possible to have two classes
class SimulationDigitizer(HasTraits):
width = Int(1920)
height = Int(1080)
name = 'Simulation'
class FileDigitizer(HasTraits):
Filename = File
name = 'File'
and another class 'Digitizer' having an attribute (or trait) UserDigitizer whose edition dialog will propose a drop-down list with 'Simulation' and 'File' and, depending on the choice, the instance edition of either FileDigitizer or SimulationDigitizer and get the result in UserDigitizer ?
Thanks
Thanks Jonathan, you are right, the problem is to select the appropriate subcomponent among many possible.
I ended with the following code, I guess it can be improved in many ways. I am new both to Python and Traits.
# -*- coding: utf-8 -*-
from traits.api import HasTraits, Int, File, Enum, Instance
from traitsui.api import View, Item, Handler, HGroup, InstanceEditor
class UserComponent ( HasTraits ):
""" An empty class from which all user component classes are
derived.
"""
pass
class SimulationDigitizer(UserComponent):
width = Int(1920)
height = Int(1080)
nature = 'Simulation'
class FileDigitizer(UserComponent):
filename = File
nature = 'Reading a file'
UserDigitizers = [FileDigitizer, SimulationDigitizer]
class UserComponentHandler(Handler):
def __init__(self,_user_components_dict):
Handler.__init__(self)
self._user_components_dict = _user_components_dict
def object_user_component_nature_changed ( self, info ):
# Find new UserComponent class from string in user_component_nature
new_user_component = self._user_components_dict[info.object.user_component_nature]
# If different, change user_component value
if info.object.user_component is not new_user_component:
info.object.user_component = new_user_component
class Digitizer(HasTraits):
user_component_nature = Enum([x().nature for x in UserDigitizers])
_user_file_digitizer = FileDigitizer()
_user_simulation_digitizer = SimulationDigitizer()
# Dictionary with keys = nature and values = user digitizers
_user_digitizers_dict = {x.nature: x for x in [_user_file_digitizer,_user_simulation_digitizer]}
user_component = Enum(_user_file_digitizer,_user_simulation_digitizer)
view = View(HGroup(Item('user_component_nature',
label = 'Nature'),
Item('user_component',
show_label = False,
editor = InstanceEditor(label = 'Edit',
kind = 'modal'))),
handler = UserComponentHandler(_user_digitizers_dict))
d = Digitizer()
if __name__ == '__main__':
d.configure_traits()
I want to read an ascii file that contains end-of line comments into an Astropy table. Something like this:
a | b
1 | 2
3 | 4 # this is a comment
# another comment
5 | 6
As shown here, this doesn't do what I want:
Table.read(filename, format='ascii.csv', delimiter='|', comment='\s*#')
What options do I have to pass to Table.read to make this work?
There is no built-in way to do this, but it should take only a few lines of custom code. The basic idea is to make a subclass of the CSV reader where you override the data_class = CsvData with your own Data class which itself is a subclass of CsvData. In your Data subclass override the process_lines method to do your custom comment line handling. Look at the BaseData class in core.py to see the baseline process_lines behavior.
from astropy.io.ascii.basic import Csv, CsvData
class CsvCommentData(CsvData):
comment = '#' # regular expression not allowed for this reader
def _strip_trailing_comments(self, lines):
for line in lines:
index = line.rfind(self.comment)
if index >= 0:
line = line[:index]
yield line
def process_lines(self, lines):
return super(CsvCommentData, self).process_lines(self._strip_trailing_comments(lines))
class CsvComment(Csv):
_format_name = 'csv_comment'
_io_registry_can_write = True
_description = 'Comma-separated-values with trailing comments allowed'
data_class = CsvCommentData
Given two model that are namespaced as
SomeModule::V1::Api
SomeModule::V2::Api
I want to make a call in my controller like:
api = SomeModule::V1::Api
but have the "V1" portion be a variable, so that I can swap between versions.
Any ideas on how to make that happen?
v = 'V1'
"SomeModule::#{v}::Api".constantize
=> SomeModule::V1::Api
Example:
module SomeModule
module V1; end
module V2; end
end
class SomeModule::V1::Api
def self.foo; 'V1 foo'; end
end
class SomeModule::V2::Api
def self.foo; 'V2 foo'; end
end
v = 'V1'
puts "SomeModule::#{v}::Api".constantize.foo
=> V1 foo
v = 'V2'
puts "SomeModule::#{v}::Api".constantize.foo
=> V2 foo
If you don't want to use #constantize (which is a part of ActiveSupport), you can do it with Plain Old Ruby:
version = "V1"
SomeModule.const_get(version).const_get("Api")
# => SomeModule::V1::Api