Ecto Changeset add functionality for warnings - functional-programming

I created a fork of ecto repository to extend Ecto.Changeset module with the ability to add warnings to the changeset. I wanted to have an add_warnings/4 function which adds a warning to the changeset as a simple list of warnings with this structure warnings: [{atom, {String.t, Keyword.t}}], similar to errors. The difference between the behavior of warnings and errors is that when an error occurs the data are not persisted, but when a warning occurs the data are persisted.
Ecto.Changeset struct extended with keys warnings and warningless?:
defstruct valid?: false, warningless?: false, data: nil, params: nil, changes: %{}, repo: nil,
errors: [], warnings: [], validations: [], required: [], prepare: [],
constraints: [], filters: %{}, action: nil, types: nil,
empty_values: #empty_values
Ecto functions for casting, changing, processing params, etc. adjusted. Function add_warnings/4 added:
#spec add_warning(t, atom, String.t, Keyword.t) :: t
def add_warning(%{warnings: warnings} = changeset, key, message, keys \\ []) when is_binary(message) do
%{changeset | warnings: [{key, {message, keys}}|warnings], warningless?: false}
end
The result is that I receive changeset with expected keys:
#Ecto.Changeset<action: nil, changes: %{}, data: #Company.Booking<>, errors: [],
valid?: true, warnings: [], warningless?: true>
When I make a change with error and warning I receive:
#Ecto.Changeset<action: nil,
changes: %{pickup_address: #Ecto.Changeset<action: :update,
changes: %{street_name: nil}, data: #Company.Address<>,
errors: [street_name: {"can't be blank", [validation: :required]}],
valid?: false,
warnings: [phone_number: {"This phone number is not common in Netherlands",
[]}], warningless?: false>}, data: #Company.Booking<>, errors: [],
valid?: false, warnings: [], warningless?: true>
So, everything is as expected, as far as warnings are concerned. Then, when I make a change with a warning but without an error, I receive:
#Ecto.Changeset<action: nil,
changes: %{pickup_address: #Ecto.Changeset<action: :update,
changes: %{street_name: "sss"}, data: #Company.Address<>, errors: [],
valid?: true,
warnings: [phone_number: {"This phone number is not common in Netherlands",
[]}], warningless?: false>}, data: #Company.Booking<>, errors: [],
valid?: true, warnings: [], warningless?: true>
Everything is as expected. When I don't make any changes to the form I still should receive a warning for phone number, but I receive:
#Ecto.Changeset<action: nil, changes: %{}, data: #Company.Booking<>, errors: [],
valid?: true, warnings: [], warningless?: true>
I got a changeset without any warnings as there is no changes key in changeset because the data didn't change.
The question is as follows, how to implement warnings functionality to always have warnings in the changeset, even if no change was made?

You should consider to prefill the warnings at the very beginning of the each changeset function you would create - since you can't use plug there you can come up to write a macro that will handle this logic for you, __using__ is advised, so it would be quite easy to distinguish your logic from Ecto's default logic.
Your validation shouldn't add warnings to warnings list, but you have to implement it another way around - if the field is fine, you would remove already existing warnings from this list. That way you would be sure that your changeset is fine when it's warningless, because it removed all the warnings from this list and it would works perfectly for empty changes in changeset.

Related

airflow db init error in WSL2 Ubuntu: "Additional properties are not allowed ('logo' was unexpected)"

I have the next visualization of the error:
File "/home/tinmar/.local/lib/python3.10/site-packages/jsonschema/validators.py", line 353, in validate
raise error
jsonschema.exceptions.ValidationError: Additional properties are not allowed ('**logo**' was unexpected)
Failed validating 'additionalProperties' in schema['properties']['integrations']['items']:
{'additionalProperties': False,
'properties': {'external-doc-url': {'description': 'URL to external '
'documentation for '
'the integration.',
'type': 'string'},
'how-to-guide': {'description': 'List of paths to '
'how-to-guide for the '
'integration. The path '
'must start with '
"'/docs/'",
'items': {'type': 'string'},
'type': 'array'},
'integration-name': {'description': 'Name of the '
'integration.',
'type': 'string'},
'tags': {'description': 'List of tags describing the '
"integration. While we're "
'using RST, only one tag is '
'supported per integration.',
'items': {'enum': ['apache',
'aws',
'azure',
'gcp',
'gmp',
'google',
'protocol',
'service',
'software',
'yandex'],
'type': 'string'},
'maxItems': 1,
'minItems': 1,
'type': 'array'}},
'required': ['integration-name', 'external-doc-url', 'tags'],
'type': 'object'}
On instance['integrations'][0]:
{'external-doc-url': 'https://www.sqlite.org/index.html',
'how-to-guide': ['/docs/apache-airflow-providers-sqlite/operators.rst'],
'integration-name': 'SQLite',
**'logo': '/integration-logos/sql**,
'tags': ['software']}
I really don't know what the hell is happen here, I tried almost every solution I can find in the internet but nothing was usefull...
By the way, before this error, I have other one:
'''
ModuleNotFoundError: No module named 'wtforms.compat'
'''
Error that was solved by this code
pip install wtforms==2.3.3
pip install marsmallow==3.0.0
I think that maybe the first error was the cause of all, but I'm not sure about it and I don't know how can be possible
This errors came when I try to uninstall airflow 2.5.0 in order to downgrade to airflow 2.0.0. I mean, this was the cause of the other two errors, I think so...
I also try to uninstall and reinstall WSL2, this threw me some errors but I solve it, I don't think this was the problem because before I trying this, the other errors already pop out.
I expect a local airflow env in Ubuntu with WSL2
I will appreciate if someone have any idea on how to solve it, thank you before it :)

Ansible showing task changed but the task has condition (creates: ) and does not actually execute

My ansible-playbook is running some long running task with async tag and also utilizes "creates:" condition, so it is run only once on the server. When I was writing the playbook yesterday, I am pretty sure, the task was skipped when the log set in "creates:" tag existed.
It shows changed now though, everytime I run it.
I am confused as I do not think I did change anything and I'd like to set up my registered varaible correctly as unchanged, when the condition is true.
Output of ansible-play (debug section shows the task is changed: true):
TASK [singleserver : Install Assure1 SingleServer role] *********************************************************************************************************************************
changed: [crassure1]
TASK [singleserver : Debug] *************************************************************************************************************************************************************
ok: [crassure1] => {
"msg": {
"ansible_job_id": "637594935242.28556",
"changed": true,
"failed": false,
"finished": 0,
"results_file": "/root/.ansible_async/637594935242.28556",
"started": 1
}
}
But if I check the actual results file on the target maschine, it correctly resolved condition and did not actually execute the shell script, so the task should be unchanged (shows message the task is skipped as the log exists):
[root#crassure1 assure1]# cat "/root/.ansible_async/637594935242.28556"
{"invocation": {"module_args": {"warn": true, "executable": null, "_uses_shell": true, "strip_empty_ends": true, "_raw_params": "/opt/install/install_command.sh", "removes": null, "argv": null, "creates": "/opt/assure1/logs/SetupWizard.log", "chdir": null, "stdin_add_newline": true, "stdin": null}}, "cmd": "/opt/install/install_command.sh", "changed": false, "rc": 0, "stdout": "skipped, since /opt/assure1/logs/SetupWizard.log exists"}[root#crassure1 assure1]# Connection reset by 172.24.36.123 port 22
My playbook section looks like this:
- name: Install Assure1 SingleServer role
shell:
#cmd: "/opt/assure1/bin/SetupWizard -a --Depot /opt/install/:a1-local --First --WebFQDN crassure1.tspdata.local --Roles All"
cmd: "/opt/install/install_command.sh"
async: 7200
poll: 0
register: Assure1InstallWait
args:
creates: /opt/assure1/logs/SetupWizard.log
- name: Debug
debug:
msg: "{{ Assure1InstallWait }}"
- name: Check on Installation status every 15 minutes
async_status:
jid: "{{ Assure1InstallWait.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 30
delay: 900
when: Assure1InstallWait is changed
Is there something I am missing, or is that some kind of a bug?
I am limited by Ansible version available in configured trusted repo, so I am using ansible 2.9.25
Q: "The module shell shows changed every time I run it"
A: In async mode the task can't be skipped immediately. First, the module shell must find out whether the file /opt/assure1/logs/SetupWizard.log exists at the remote host or not. Then, if the file exists the module will decide to skip the execution of the command. But, you run the task asynchronously. In this case, Ansible starts the module and returns without waiting for the module to complete. That's what the registered variable Assure1InstallWait says. The task started but didn't finish yet.
"msg": {
"ansible_job_id": "637594935242.28556",
"changed": true,
"failed": false,
"finished": 0,
"results_file": "/root/.ansible_async/637594935242.28556",
"started": 1
}
The decision to set such a task changed is correct, I think because the execution on the remote host is going on.
Print the registered result of the module async. You'll see, that the command was skipped because the file exists (you've printed the async file at the remote instead). Here the attribute changed is set false because now we know the command didn't execute
job_result:
...
attempts: 1
changed: false
failed: false
finished: 1
msg: Did not run command since '/tmp/SetupWizard.log' exists
rc: 0
...

Idiomatic/Groovy way to add two maps, either of which may be null

I have the following map:
configs = [
common : [
foo : '123',
bar : '456'
],
dev : [
foo : '789',
bar : '012'
],
test : null
]
When I add dev to common, it works great - the values from common are overridden by the values from dev. Just what I want.
dev = configs['common'] + configs['dev']
println dev
// --> [foo:789, bar:012]
However, if I try the same with test, I get the following error:
groovy.lang.GroovyRuntimeException: Ambiguous method overloading for method java.util.LinkedHashMap#plus.
Cannot resolve which method to invoke for [null] due to overlapping prototypes between:
[interface java.util.Collection]
[interface java.util.Map]
I can make it work by doing something like the following:
test = [:]
test = configs['common']==null ? test : test + configs['common'] // First add common bits
test = configs['test']==null ? test : test + configs['test'] // Then override with environment specific bits
println test
// --> [foo:123, bar:456]
But this seems ugly and bloated.
Can someone with better Groovy-fu show me a nicer way? Thanks!
You can use Elvis operator to bring an empty map to the equation when config['test'] == null. Consider the following example:
def configs = [
common : [
foo : '123',
bar : '456'
],
dev : [
foo : '789',
bar : '012'
],
test : null
]
def dev = configs['common'] + (configs['dev'] ?: [:])
println dev
def test = configs['common'] + (configs['test'] ?: [:])
println test
Output:
[foo:789, bar:012]
[foo:123, bar:456]
You can use it whenever you expect that one value can be represented by null.

Cannot find function `jsx:is_json/1` although jsx is included and compiled

I am writing a webapp using Erlang toolchain (OTP, rebar3, cowboy, jsx...). The following code does not work because jsx:is_json/1 cannot be found during runtime.
handle_login(Req, State) ->
{ok, Data, _} = cowboy_req:body(Req),
case jsx:is_json(Data) of
false -> cowboy_req:reply(400,
[
{<<"content-type">>, <<"application/json">>}
],
<<"Invalid JSON">>,
Req);
Stacktrace:
{[{reason,undef},
{mfa,{erbid_api_handler,handle,2}},
{stacktrace,
[{jsx,is_json,[<<"{\"username\":\"tom\"}">>],[]},
{erbid_api_handler,handle_login,2,
[{file,
"/Users/khanhhua/dev/project-erbid/_build/default/lib/erbid/src/erbid_api_handler.erl"},
{line,45}]},
{erbid_api_handler,handle,2,
... truncated for brevity
Folder structure:
I need to know how to fix the issue. Thanks.
I have found the cause of this issue. I did not not include module jsx in my erbid.app.src's applications section.
{application, erbid, [
{description, "Realtime system"},
{vsn, "0.1.0"},
{registered, []},
{applications, [
kernel,
stdlib,
cowboy,
jsx
]},
{mod, {erbid, []}},
{env, []}
]}.
Totally due to my lack of Erlang experience.

How do I set up Quixote with Karma?

Quixote looks really cool. Am having a bit of trouble getting it working with Karma. I tried emulating Bjorn's example but am getting this error:
Error: Mismatched anonymous define() module: function () {var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
In my karma.conf.js, I have:
files: [
'www/latest/tests/lib/jasmine-beforeAll.js',
'www/latest/tests/lib/sinon-1.7.3.js',
'www/latest/tests/test-main.js',
{pattern: 'www/latest/vendor/**/*.js', included: false},
{pattern: 'www/latest/app/**/*.js', included: false},
{pattern: 'www/latest/tests/lib/*.js', included: false},
{pattern: 'www/latest/tests/specs/**/*.js', included: false},
'node_modules/quixote/dist/*.js'
],
Then in my test, I have
var quixote = require("../../../../node_modules/quixote/dist/quixote.js");
(this seems redundant as having Quixote in files hash would make it globally available but wanted to follow Bjorn's example to the letter).
Here's a plugin I made, please file tickets if it doesn't work for you. https://github.com/woldie/karma-quixote
npm install --save-dev karma-quixote

Resources