Python: How do you use match_args and kw_args together in a dataclass? - python-3.10

Given the following example:
from dataclasses import dataclass
#dataclass
class Person:
name: str = ""
#dataclass(kw_only=True)
class AnotherPerson:
name: str = ""
print(Person.__match_args__)
print(AnotherPerson.__match_args__)
When running, you get the following:
('name',)
()
According to the documentation for the dataclass match_args argument (emphasis mine):
match_args: If true (the default is True), the __match_args__ tuple will be created from the list of parameters to the generated __init__() method (even if __init__() is not generated, see above). If false, or if __match_args__ is already defined in the class, then __match_args__ will not be generated.
Given that match_args defaults to true, I would think that the __match_args__ variable should be set to the values as they appear in the __init__ method, though this does not appear to be the case with keyword arguments. Is this just an undocumented restriction, or am I doing something wrong?
Regardless, how would I go about generating these __match_args__ tuples without explicitly writing them out?
Thanks

Related

How do I use a for loop to emit a list of objects in Terraform?

I have a variable defined as,
variable "ssh_permit" {
type = list(object({ name = string, ip = string }))
default = [
{
name = "alice"
ip = "1.1.1.1"
},
{
name = "bob"
ip = "2.2.2.2"
},
]
}
I'm attempting to use a for loop, to post-process this value into an object that can be assigned to the security_rule attribute on an Azure NSG.
The docs seem to indicate that this should be possible.
However, I get:
Error: Incorrect attribute value type
on test.tf line 81, in resource "azurerm_network_security_group" "foo_nsg":
81: security_rule = [for idx, rule in var.ssh_permit: {
82: name = "allow-${rule.name}"
83: priority = 100 + idx
84: direction = "Inbound"
85: access = "Allow"
86: protocol = "TCP"
87: source_address_prefix = rule.ip
88: source_port_range = "*"
89: destination_address_prefix = "*"
90: destination_port_range = "22"
91: } ]
|----------------
| var.ssh_permit is list of object with 2 elements
Inappropriate value for attribute "security_rule": element 0: attributes
"description", "destination_address_prefixes",
"destination_application_security_group_ids", "destination_port_ranges",
"source_address_prefixes", "source_application_security_group_ids", and
"source_port_ranges" are required.
It is like Terraform is ignoring the fact that there's a for loop there. Yes, var.ssh_permit is a list of object w/ 2 elements, but it gets transformed into the appropriate object for security_element.
(I've gotten this to work with the dynamic block syntax, but I'd like to ignore that for the purposes of the question, since I'm trying to also learn the TF syntax for for loops. It appears that it should work here, so I'd like to understand why not.)
This failure is reporting that the first element value you've provided for security_rule doesn't meet the type constraint for this argument.
Based on the error message, it seems like this argument requires a list of objects with various attributes, including all of the ones listed in the error message. One way to make this result conform to the type, then, would be to set all of those extra attributes to null so that the object has a compatible type but still leaves those attributes unpopulated:
security_rule = [
for idx, rule in var.ssh_permit: {
name = "allow-${rule.name}"
priority = 100 + idx
direction = "Inbound"
access = "Allow"
protocol = "TCP"
source_address_prefix = rule.ip
source_port_range = "*"
destination_address_prefix = "*"
destination_port_range = "22"
description = null
destination_address_prefixes = null
destination_application_security_group_ids = null
destination_port_ranges = null
source_address_prefixes = null
source_application_security_group_ids = null
source_port_ranges = null
}
]
The above expression should make the result have a suitable type, although the provider is free to impose additional validation constraints beyond type checking and so it may not consider null to be a suitable value for all of these attributes.
You mentioned dynamic blocks and so I expect you have an underlying question here about why you don't typically need to set arguments to null in blocks but yet you do here in this expression.
This is because a block in the Terraform language is a special language structure rather than a normal value, and so it has some extra capabilities that differentiate it from values, including the idea that particular arguments can be optional, and that a block can contain nested blocks. Nested blocks are a language construct somewhat unique to Terraform, due to its intent to use a declarative style that reads like a description of a desired result rather than like a typical computer program.
However, whenever you see the security_rule = syntax you are assigning a value to an argument. Values in Terraform are a lot more like the typical idea of values in general-purpose programming languages, and in particular each value has a type. By using the { ... } syntax here, you've constructed an object-typed value. Notice that "object" isn't actually a type itself, but rather it's a subcategory of types that have attributes, similar to how in some general-purpose programming languages "object" just means an instance of any class, and the class itself is the type.
A less-conventional characteristic of the Terraform language, though, is that it has a structural type system, which means that object types are defined entirely by their shape (what attributes they have) and not by a "class name", as you might see in other languages. Resource arguments have type constraints, which state which types of values are allowed to be assigned there.
The definition of this security_rule argument in the provider seems to declare it as if it had the following type constraint (although provider-defined type constraints are not visible directly in the language itself):
list(object({
name = string
priority = number
direction = string
access = string
protocol = string
source_address_prefix = string
source_port_range = string
destination_address_prefix = string
destination_port_range = string
description = string
destination_address_prefixes = list(string)
destination_application_security_group_ids = list(string)
destination_port_ranges = list(string)
source_address_prefixes = list(string)
source_application_security_group_ids = list(string)
source_port_ranges = list(string)
}))
(I might not have got this exactly right; I just guessed based on what we've discussed so far in this question, rather than referring to the documentation.)
Terraform decides if an object type meets an object type constraint by checking that it has at least the attributes defined in the type constraint. Type checking failed in your case because the object you provided was lacking some of the attributes from the type constraint.
I think there's an extra detail worth mentioning here for this security_rule argument in particular. Although the provider developers seem to have forgotten to document it as such, I think this argument is marked in the provider schema as using the legacy Attributes as Blocks mode, which is a special shim that Terraform supports to allow backward compatibility with some situations where provider designs were relying on configuration validation bugs in Terraform v0.11 that have since been fixed.
I won't include all of the special cases about Attributes as Blocks mode here because they only apply to some special old provider designs and they're already described in detail in the documentation page, but I wanted to mention it since I think this argument using that mode contributes to its behavior being a little confusing/inconsistent when compared to a typical resource type argument.
security_rule is a block, not an attribute. Thus you can't treat is as an attribute using
security_rule =
It must be defined through dynamic blocks, which as you noted, works.
Your second link to example = is for attribute, not block.

Limit Array to multiple specific data types

I am working on refactoring a tool to OOP in PS5.
I have a number of classes. Sets can contain other Sets as well as Packages. Packages can contain other Packages and Tasks. And Tasks can contain other Tasks. For example...
Set1
Package1.1
Task1.1
Set2
Package2.1
Task2.1
Set2A
Package2A
Task2A.1
Task2A.2
Package2.2
Task2.2
Set3
Package3.1
Task3.1
Task3.1A
I plan to have Set, Package and Task classes, but there are a number of different Tasks with some common features and some unique, so I will have a base Task class that is then extended by the various final task classes.
My question relates to the data structure to contain the nested objects. If each class could only contain the next deeper type everything would be easy; the variable to hold the Packages in a Set could be an array of Packages, i.e. [Package[]]$Contents.
I could make it super flexible and just do an array; [Array]$Contents, but that allows for invalid items like strings and such.
Alternatively I could have some sort of Root class, with Sets, Packages and Tasks all extended that, and final Tasks then extending Tasks, and use[Root[]]$Contents or some such. But that might not be possible and it would still allow for adding a Task to a Set, since a final Task class would ultimately be extending from Root.
So, the question becomes, can you define an array that accepts multiple possible types but is still limited, something like [Set/Package[]]$Contents? Or is there perhaps a totally different way to define a variable that limits the valid members? An Enum seems to have potential, but it seems like they are limited to strings as I tried
enum AllowedTypes {
[Array]
[Strings]
}
and that in no good.
Or am I best of just using an Array and validating what I am adding in the Add method of each Class? I can see a possible solution there where I have overloaded Add methods in the Set class, one that takes a Set, one that takes a Package, and one that takes a generic object and throws an error to log. Assuming that the more specific overload method takes priority rather than everything going to the generic method since it's technically valid. Or perhaps that generic method won't even work since the collection of overloaded Add methods technically can't collapse to one valid choice because a Set is both a [Set] and a [PSObject] I guess.
PetSerAl, as countless times before, has provided an excellent (uncommented) solution in a comment on the question, without coming back to post that solution as an answer.
Given the limits of code formatting in comments, it's worth presenting the solution in a more readable format; additionally, it has been streamlined, modularized, extended, and commented:
In short: a PowerShell custom class (PSv5+) is used to subclass standard type [System.Collections.ObjectModel.Collection[object]] in order to limit adding elements to a list of permitted types passed to the constructor.
class MyCollection : System.Collections.ObjectModel.Collection[object] {
# The types an instance of this collection
# is permitted to store instance of, initialized via the constructor.
[Type[]] $permittedTypes
# The only constructor, to which the permitted types must be passed.
MyCollection([Type[]] $permittedTypes) { $this.permittedTypes = $permittedTypes }
# Helper method to determine if a given object is of a permitted type.
[bool] IsOfPermittedType([object] $item) {
return $this.permittedTypes.Where({ $item -is $_ }, 'First')
}
# Hidden helper method for ensuring that an item about to be inserted / added
# / set is of a permissible type; throws an exception, if not.
hidden AssertIsOfPermittedType([object] $item) {
if (-not $this.IsOfPermittedType($item)) {
Throw "Type not permitted: $($item.GetType().FullName)"
}
}
# Override the base class' .InsertItem() method to add type checking.
# Since the original method is protected, we mark it as hidden.
# Note that the .Add() and .Insert() methods don't need overriding, because they
# are implemented via this method.
hidden InsertItem([int] $index, [object] $item) {
$this.AssertIsOfPermittedType($item)
([System.Collections.ObjectModel.Collection[object]] $this).InsertItem($index, $item)
}
# Override the base class' SetItem() method to add type checking.
# Since the original method is protected, we mark it as hidden.
# This method is implicitly called when indexing ([...]) is used.
hidden SetItem([int] $index, [object] $item) {
$this.AssertIsOfPermittedType($item)
([System.Collections.ObjectModel.Collection[object]] $this).SetItem($index, $item)
}
# Note: Since the *removal* methods (.Remove(), .RemoveAt())
# need to type checking, there is no need to override them.
}
With the above class defined, here's sample code that exercises it:
# Create an instance of the custom collection type, passing integers and strings
# as the only permitted types.
# Note the (...) around the type arguments, because they must be passed
# as a *single argument* that is an *array*.
# Without the inner (...) PowerShell would try to pass them as *individual arguments*.
$myColl = [MyCollection]::new(([int], [string]))
# OK, add an [int]
# .Add() implicitly calls the overridden .InsertItem() method.
$myColl.Add(1)
$myColl.Add('hi') # OK, add a [string]
# OK, override the 1st element with a different [int]
# (though a [string] would work too).
# This implicitly calls the overridden .SetItem() method.
$myColl[0] = 2
# OK - insert a [string] item at index 0
$myColl.Insert(0, 'first')
# $myColl now contains: 'first', 2, 'hi'
# Try to add an impermissible type:
$myColl.Add([long] 42)
# -> Statement-terminating error:
# 'Exception calling "Add" with "1" argument(s): "Type not permitted: System.Int64"'

I can't access the _groups property in angular/d3js

I have a problem accessing my "_groups" property with the following code:
function mouseDate(scale){
var g = d3.select("#group")._groups[0][0]
var x0 = scale.invert(d3.mouse(g)[0]);
console.log(x0);
}
Result of my console.log:
Selection {_groups: Array(1), _parents: Array(1)}
_groups: Array(1)
0: Array(1)
0: g#group
When I compile the code I have the following error :
D:/Documents/starter-propre-angular4/src/app/pages/graphe-page/graphe.page.ts (782,32): Property '_groups' does not exist on type 'Selection<BaseType, {}, HTMLElement, any>'.
So my question is: Is there a solution to get the information in "_groups" all year round knowing that I am evolving into TypeScript using d3js
The _groups property is a private member of a Selection object and should as such not be accessed directly. (Side note: it is common convention in JavaScript that any member starting with an underscore denotes a private member. See, e.g., "Underscore prefix for property and method names in JavaScript").
Since the property is considered private it is not part of the public interface and is therefore not included in the TypeScript type declaration for the d3-selection module. Hence, you get the compiler error you witnessed. Although this actually will work in pure JavaScript the TypeScript compiler did exactly what it is supposed to do—namely, prevent you from doing some unsafe stuff.
Looking at the code you posted, though, it becomes apparent that you are not interested in the _groups property itself but rather in _groups[0][0] which internally refers to the first node of the selection. Fortunately, the selection.node() method will return exactly that first element. You can do it like this:
function mouseDate(scale){
var g = d3.select("#group").node();
var x0 = scale.invert(d3.mouse(g)[0]);
console.log(x0);
}

On App Engine, how do you make an ndb Property immutable once set?

This seems like such a commonplace requirement, it'd be builtin, but anyway: If you have a model like the following one, how do you stop the eggs property being mutated after it's been set?
class Spam(ndb.Model):
eggs = ndb.StringProperty()
The goal is is to have the property not required, so it'd default to None, but once it's been mutated from None, to a string in the above case, it can never be changed again, but any insight into defining an immutable property would be appreciated.
I had considered using the validator argument of the ndb.Property to pass in a function; see the answers below for why that wouldn't work here. It's useful for understanding the objects and namespaces involved.
One approach which does not require you to use custom properties is to use hooks See docs https://developers.google.com/appengine/docs/python/ndb/modelclass#Model__post_get_hook
You would use a _post_get_hook(cls, key, future) and a _pre_put_hook(self)
In _post_get_hook you would store away the original value of the property
and in _pre_put_hook you would check that it is the same as the original value unless the original value is None.
ie
class Spam(ndb.Model):
eggs = ndb.StringProperty()
#classmethod
def _post_get_hook(cls,key,future):
obj = future.get_result()
obj._my_eggs_prop = obj.eggs
def _pre_put_hook(self):
if hasattr(self,'_my_eggs_prop'):
if self.eggs != self._my_eggs_prop:
if self._my_eggs_prop != None:
# do some logging.
raise ValueError
else:
setattr(self,'_my_eggs_prop',self.eggs)
# if the saved value doesn't exist, create it and store
# the value in case an update occurs after the initial put
# this also means the object was created and not get()
Here is an example of it working
s~lightning-catfish> import spam
s~lightning-catfish> x = spam.Spam(id='canned')
s~lightning-catfish> x.eggs = 'green'
s~lightning-catfish> x.put()
Key('Spam', 'canned')
s~lightning-catfish> y = x.key.get()
s~lightning-catfish> y._my_eggs_prop
u'green'
s~lightning-catfish> y.eggs = 'blue'
s~lightning-catfish> y.put()
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/timh/google_appengine/google/appengine/ext/ndb/model.py", line 3232, in _put
return self._put_async(**ctx_options).get_result()
File "/home/timh/google_appengine/google/appengine/ext/ndb/model.py", line 3247, in _put_async
self._pre_put_hook()
File "spam.py", line 18, in _pre_put_hook
raise ValueError
ValueError
The downside of this approach is you could change the property rely in it for some additional code and then only find out about it when you do the put. However that may not be so bad, as you should in theory not have any code that is modifying the property once changed. So you want to log, and track down how this could be occurring. Alternately you could reset the value to the original setting, but then you leave incorrect code in place.
A custom property takes a little more thought ;-)
Unfortunately you can't do what you want with a validator.
(prop,value) are the property instance and value to set. You don't have a handle in the validator for the instance of the class and therefore the pre-existing value. All the methods of the property you need to get the existing value need a model instance as an argument - like _has_value . The docs
Will be called with arguments (prop, value) and should either return
the (possibly coerced) value or raise an exception. Calling the
function again on a coerced value should not modify the value further.
(For example, returning value.strip() or value.lower() is fine, but
not value + '$'.) May also return None, which means "no change". See
also Writing Property Subclasses
`https://developers.google.com/appengine/docs/python/ndb/properties#options
You will need to write a custom property to manage the state of the value and prevent overwriting once set.
See the example below showing you that you haven't got access to the pre-existing value of the property and a validator isn't passed it or the instance of the model which would have the value.
s~lightning-catfish> from pdb import set_trace
s~lightning-catfish> def valid(prop,val):
... set_trace()
... return val
...
s~lightning-catfish> class X(ndb.Model):
... x = ndb.StringProperty(validator=valid)
...
s~lightning-catfish> y = X(x="abc")
> <console>(3)valid()
(Pdb) p prop
StringProperty('x', validator=<function valid at 0xaf2d02c>)
(Pdb) p prop._has_value
<bound method StringProperty._has_value of StringProperty('x', validator=<function valid at 0xaf2d02c>)>
(Pdb) p prop._has_value()
*** TypeError: TypeError('_has_value() takes at least 2 arguments (1 given)',)
(Pdb) c
s~lightning-catfish> y
X(x='abc')

Strict (2048): Declaration of CsvImportBehavior::setup() should be compatible with ModelBehavior

I am getting the following error:
Strict (2048): Declaration of CsvImportBehavior::setup() should be compatible
with ModelBehavior::setup(Model $model, $config = Array)
[APP\Plugin\Utils\Model\Behavior\CsvImportBehavior.php, line 20]
I followed the tutorial on this site: http://www.pronique.com/blog/enable-csv-import-all-controllers-models-cakephp-2
When I import my CSV file, it gives the following flash message:
Successfully imported 0 records from Book1.csv
I don't understand why its not importing, does it have something to do with the error/warning its giving?
I looked inside the behaviour (CsvImportBehaviour.php at line 20): class CsvImportBehavior extends ModelBehavior {
That does not make sense on line 20, that's just the class declaration, so I moved down on the code and saw the following: public function setup(Model &$Model, $settings = array()) {-- this does seem to me to be according to the standards.
To suppress the errors/warnings, try to:
remove the & before $Model (not required as Model is an object and therefore already passed byref)
Optionally (see comment by #mark):
rename $Model to $model (lowercase)
rename $settings to $config
I don't know the reason for not importing records from the CSV, that will require debugging on your side.
Alternatives
CakePHP also has a CSV dataSource as part of the datasources plug in.
Using this, you can create a Model that, in stead of using a database, uses a CSV file as its source. This allows you to, for example, do this;
$csvData = $this->MyCsvModel->find('all');
Which will return all rows from the CSV file. Importing this into your database will be easy to implement by saving $csvData to another model
Links:
https://github.com/cakephp/datasources/tree/2.0
https://github.com/cakephp/datasources/blob/2.0/Model/Datasource/CsvSource.php

Resources