unique indentifiers of new activities in Brightway - brightway

I want to create a simple activity to add to my ecoinvent database on Brightway2. How can I create a unique identifier to act as the "code" field?
The only way I know to create an activity from scratch is:
bw.Database('database_name').new_activity('code')
but I need to specify a code, and I would rather have it automatically generated (as when we do a copy of an existing activity). Is there a way to do it?

In the docs, one can read:
Brightway2 identifies an activity or flow with the MD5 hash of a few attributes: For ecoinvent 2, the name, location, unit, and categories. For ecoinvent 3, the activity and reference product names.
When diving in the bw2io code though (specifically the utils), we see this is not actually exact: Brightway generates a unique code as the MD5 hash of the ecoinvent UUIDs for the activity and the reference flow:
In [1] import brightway2 as bw
import hashlib
act = bw.Database('ecoinvent 3.3 cutoff').random()
act['code']
Out[1] '965e4a277c353bd2ed8250b49c0e24ef'
In [2] act['activity'], act['flow']
Out[2] ('ff086b85-84bf-4e44-b60e-194c0ac7f7cf',
'45fbbc41-7ae9-46cc-bb31-abfa11e69de0')
In [3] string = u"".join((act['activity'].lower(), act['flow'].lower()))
string
Out[3] 'ff086b85-84bf-4e44-b60e-194c0ac7f7cf45fbbc41-7ae9-46cc-bb31-abfa11e69de0'
In [4] str(hashlib.md5(string.encode('utf-8')).hexdigest())
Out[4] '965e4a277c353bd2ed8250b49c0e24ef'
In [5] act['code'] == str(hashlib.md5(string.encode('utf-8')).hexdigest())
Out[5] True
Note that this implies you have informed the activity and flow fields of your activity dataset. You can generate these using the uuid library. You could also decide to use other fields in your MD5 hash (e.g. the name of the activity and of the reference flow, as the docs imply).

Related

django remove m2m instance when there are no more relations

In case we had the model:
class Publication(models.Model):
title = models.CharField(max_length=30)
class Article(models.Model):
publications = models.ManyToManyField(Publication)
According to: https://docs.djangoproject.com/en/4.0/topics/db/examples/many_to_many/, to create an object we must have both objects saved before we can create the relation:
p1 = Publication(title='The Python Journal')
p1.save()
a1 = Article(headline='Django lets you build web apps easily')
a1.save()
a1.publications.add(p1)
Now, if we called delete in either of those objects the object would be removed from the DB along with the relation between both objects. Up until this point I understand.
But is there any way of doing that, if an Article is removed, then, all the Publications that are not related to any Article will be deleted from the DB too? Or the only way to achieve that is to query first all the Articles and then iterate through them like:
to_delete = []
qset = a1.publications.all()
for publication in qset:
if publication.article_set.count() == 1:
to_delete(publication.id)
a1.delete()
Publications.filter(id__in=to_delete).delete()
But this has lots of problems, specially a concurrency one, since it might be that a publication gets used by another article between the call to .count() and publication.delete().
Is there any way of doing this automatically, like doing a "conditional" on_delete=models.CASCADE when creating the model or something?
Thanks!
I tried with #Ersain answer:
a1.publications.annotate(article_count=Count('article_set')).filter(article_count=1).delete()
Couldn't make it work. First of all, I couldn't find the article_set variable in the relationship.
django.core.exceptions.FieldError: Cannot resolve keyword 'article_set' into field. Choices are: article, id, title
And then, running the count filter on the QuerySet after filtering by article returned ALL the tags from the article, instead of just the ones with article_count=1. So finally this is the code that I managed to make it work with:
Publication.objects.annotate(article_count=Count('article')).filter(article_count=1).filter(article=a1).delete()
Definetly I'm not an expert, not sure if this is the best approach nor if it is really time expensive, so I'm open to suggestions. But as of now it's the only solution I found to perform this operation atomically.
You can remove the related objects using this query:
a1.publications.annotate(article_count=Count('article_set')).filter(article_count=1).delete()
annotate creates a temporary field for the queryset (alias field) which aggregates a number of related Article objects for each instance in the queryset of Publication objects, using Count function. Count is a built-in aggregation function in any SQL, which returns the number of rows from a query (a number of related instances in this case). Then, we filter out those results where article_count equals 1 and remove them.

Fetching Data from Database using Strings not IDs

Whenever we save data to the database, there is always a corresponding ID which we use to fetch the data from that specific column.
sql_con.execute("SELECT FROM DBNAME WHERE ID = ?", id)
The above code only allows us to fetch data based from the ID. The problem is that the above code only accepts 1 supplied binding. In my database, I used sets of strings as the ID for each column, which means that the binding of my IDs are more than 1. And, those sets of strings have different bindings (or character count).
How do I modify the code in above, so I could input strings as my ID, preventing it from receiving the specific error:
sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 1, and there are 8 supplied.
Thank you in advance. I use Python 3.xx and in-built module sqlite3. Database is in .db file format and is a disk-based database.
I found the answer for my own question, by asking someone else.
For you to resolve this problem with the bindings of the input, just simply convert the parameter into a tuple.
OLD CODE:
sql_con.execute("SELECT FROM DBNAME WHERE ID = ?", id)
INTO THIS...
NEW CODE:
sql_con.execute("SELECT * FROM DBNAME WHERE ID = ?", (id,))
Hope it helps.

Brightway2: Modifying/deleting exchanges from activity without using activity as dict

I would like to modify an activity's exchanges and save the activity back to the database.
It is possible to change other aspects of the activity, like its name:
some_act['name'] = "some new name"
and then save the activity with:
some_act.save()
It is also possible to modify exchanges the same way:
some_exc['scale"] = 0.5
and then save the exchange with:
some_exc.save()
However, the only way I have found to add/delete exchanges from a specific activity is to go through the dictionary version of the activity:
some_act_dataset = some_act._data
some_act_dataset['exchanges'] = [{exchange1}, {exchange2}] # exc must be valid exchange dict
The problem is that I don't know how to save the new activity (as dict) back to the database.
some_act_dataset.save() doesn't work, since dictionaries don't have a save method.
Database("my_database").write(some_act_dataset)overwrites all the other data in the database.
I could work in the loaded database:
loaded_db = Database("my_database").load()
and make the changes I need in the resulting dictionary, and then write the whole database, but when the databases are big, this seems like a costly operation.
So, the question is: is there a way to modify an activity's exchanges and save the activity back to the database without needing to overwrite the entire database?
Actiities and exchanges are stored in separate tables in the SQLite database, and they each have their own object. In the journey to and from the database, several translation layers are used:
However, we almost always work with Activity or Exchange objects. The key point here is that because activities and exchanges are two separate tables, they have to be treated separately.
To create a new exchange, use Activity.new_exchange():
In [1] from brightway2 import *
In [2]: act = Database("something").random()
In [3]: exc = act.new_exchange()
In [4]: type(exc)
Out[4]: bw2data.backends.peewee.proxies.Exchange
You can also specify data attributes in the new_exchange method call:
In [5]: exc = act.new_exchange(amount=1)
In [6]: exc['amount']
Out[6]: 1
To delete an Exchange, call Exchange.delete(). If you are doing a lot of data manipulation, you can either execute SQL directly against the database, or write peewee queries with ActivityDataset or ExchangeDataset (see e.g. the queries built in the construction of an Exchanges object).

Create a Couchbase Document without Specifying an ID

Is it possible to insert a new document into a Couchbase bucket without specifying the document's ID? I would like use Couchbase's Java SDK create a document and have Couchbase determine the document's UUID with Groovy code similar to the following:
import com.couchbase.client.java.CouchbaseCluster
import com.couchbase.client.java.Cluster
import com.couchbase.client.java.Bucket
import com.couchbase.client.java.document.JsonDocument
// Connect to localhost
CouchbaseCluster myCluster = CouchbaseCluster.create()
// Connect to a specific bucket
Bucket myBucket = myCluster.openBucket("default")
// Build the document
JsonObject person = JsonObject.empty()
.put("firstname", "Stephen")
.put("lastname", "Curry")
.put("twitterHandle", "#StephenCurry30")
.put("title", "First Unanimous NBA MVP)
// Create the document
JsonDocument stored = myBucket.upsert(JsonDocument.create(person));
No, Couchbase documents have to have a key, that's the whole point of a key-value store, after all. However, if you don't care what the key is, for example, because you retrieve documents through queries rather than by key, you can just use a uuid or any other unique value when creating the document.
It seems there is no way to have Couchbase generate the document IDs for me. At the suggestion of another developer, I am using UUID.randomUUID() to generate the document IDs in my application. The approach is working well for me so far.
Reference: https://forums.couchbase.com/t/create-a-couchbase-document-without-specifying-an-id/8243/4
As you already found out, generating a UUID is one approach.
If you want to generate a more meaningful ID, for instance a "foo" prefix followed by a sequence number, you can make use of atomic counters in Couchbase.
The atomic counter is a document that contains a long, on which the SDK relies to guarantee a unique, incremented value each time you call bucket.counter("counterKey", 1, 2). This code would take the value of the counter document "counterKey", increment it by 1 atomically and return the incremented value. If the counter doesn't exist, it is created with the initial value 2, which is the value returned.
This is not automatic, but a Couchbase way of creating sequences / IDs.

Restkit: when a foreign key is set to null the relationship in Core Data is not reset

Basically when a foreign key becomes null (after it was set to a value) the relationship in core data is not reset.
Take as an example the following one-to-many relationship:
contact <<---> company (contact has one company, company has many contacts)
Which is mapped in both directions with the following methods from Restkit:
RKRelationshipMapping *contactCustomerRelationshipMapping = [RKRelationshipMapping relationshipMappingFromKeyPath:#"contacts" toKeyPath:#"hasContacts" withMapping:contactResponseMapping];
[customerResponseMapping addPropertyMapping:contactCustomerRelationshipMapping];
[contactResponseMapping addConnectionForRelationship:#"forCustomer" connectedBy:#{#"companyID" : #"identifier"}];
Then, assume that a contact is linked to a company both in core data and in the remote server, so the JSON returns:
company_id = 123
which is correctly mapped to the relationship in Core Data.
Although when the relationship is null-ed out the returning JSON in response of a GET contact returns:
'contact': {
....
address = "20 Wordworth Ave";
city = "<null>";
"company_id" = "<null>";
...
}
The company_id is then set correctly in the core data entity but the relationship connection mapper then does not delete the reference to the company with id 123 via the relationship. So it seems like Restkit is not applying the null value of the foreign key to the relationship in Core Data.
I have verified that this happens only when company_id is reset to null and not when the value is changed to another company_id.
Let me know if you have any suggestion on how to solve the issue.
(Right now I am thinking to implement the setter for company_id and manually reset the relationship when it's null)
Thanks a lot!
I am using the latest Restkit development branch (which is tagged as 0.21.0 - currently the lastest release is 0.20.3 but blake watters told me that the development branch has already been tagged but he did not have the time to prepare docs)
I am actually using cocoapods and included the latest dev release with the line:
pod 'RestKit', :head
Your workaround should be doable.
This could be classed as a bug in RestKit. As such you'd be better off raising it as an issue. You can also looking at adding it as a feature.
It's possible that you could use fetchRequestBlocks in order to provide RestKit with the information required to handle this, but this would result in the object being deleted which may not be what you want.

Resources