Skip to content

Conversation

@streino
Copy link
Contributor

@streino streino commented Jan 6, 2026

Prep work for opendatateam/udata-front-kit#961.

Tagging as "feat" since we have some misc changes on the main path (sorting universe additions and removals). I don't expect it'll matter, but you never know 🐛

I'll make a follow-up PR to refactor the datagouv API to return actual objects instead of dicts so we can have tighter typing (and prepare for datagouv-client, see below). Then we should be done with the prep work for opendatateam/udata-front-kit#961 and ecolabdata/ecospheres#802.

Once all this lands I'll look into replacing our homemade datagouv API wrapper with datagouv-client, and hopefully move the datagouv mocks over there. So if you have suggestions on improving the mocks, we can start the discussion here :)

@streino streino requested a review from abulte January 6, 2026 16:02
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the remaining TODO can wait a bit. I don't expect the upcoming planned features to change those code paths, so in the interest of time and shipping those feature, I think we can plan to deal with the TODO when moving to datagouv-client.

Copy link
Collaborator

@abulte abulte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like we're covered 🏰

Comment on lines +35 to +40
def as_dict(self) -> dict[str, Any]:
return {
"id": self.id,
"slug": self.slug,
"name": self.name,
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be done through __dict__?

Copy link
Contributor Author

@streino streino Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you have in mind?

I don't think you're supposed to play with __dict__ directly (quick web search suggests we shouldn't). Dataclasses having an asdict function is a pretty strong hint IMO.

"organization": self.organization.as_dict(),
}

def with_owner(self, organization: Organization) -> Self:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confusing if we ever introduce the owner for data.gouv.fr

Copy link
Contributor Author

@streino streino Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My plan was to later extend to with_owner(self, owner: User | Organization) or similar, so we don't have two different methods for attributing ownership.

Datagouv uses "owner" for both "general ownership" (e.g. the Owned mixin) and "individual owner" (i.e. a user, as opposed to an org), so I figured I'd try to use different terms. But you're right that "owner" does mean "user" in the API responses, so maybe we're stuck with that definition and should use something else for my notion of "individual or organizational owner".

Some other ideas: with_ownership, with_proprietor, ...? Any better idea?

return cls().with_owner(organization)

@classmethod
def some_owned(cls, n: int, organizations: list[Organization]) -> list[Self]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def some_owned(cls, n: int, organizations: list[Organization]) -> list[Self]:
def many_owned(cls, n: int, organizations: list[Organization]) -> list[Self]:

Or I expect at least one of them to not be owned.

Copy link
Contributor Author

@streino streino Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reworked those builder methods, so now it's one/some for all types. Does this make things clearer?

See also #25 (comment) about sticking with "some".

# TODO: support reset=True

for element_class in ElementClass:
target_elements = target_universe.elements(element_class)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

upcoming ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elements is a but confusing (objects ids in test code), but since it stems from a universe, we can probably keep it like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what you mean. There are elements (of class Element) and object ids, and an element as a reference to an object id. I.e.:

upcoming_elements = upcoming_universe.elements(element_class)
upcoming_object_ids = {e.object.id for e in upcoming_elements}

What test code are you referring to?

return cls()

@classmethod
def some(cls, n: int) -> list[Self]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def some(cls, n: int) -> list[Self]:
def many(cls, n: int) -> list[Self]:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like some better, because "many" suggests "a lot" and "several" suggests "at least two", and here n can take any value (didn't see a reason to enforce a floor value).

Does it matter now that #25 (comment) is addressed?

Comment on lines +15 to +17
existing_universe.clone()
.add_elements(datasets[2], dataservices[1])
.remove_elements(datasets[1])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice API 💅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right? :)

I'm not entirely sure I should use "elements" here, as it could be confused with instances of class Element (is that what you meant as "test code" in #25 (comment)?). But we do talk about datasets & co as topic "elements". I feel there's still a lot of naming confusion around topics, and we'd make discussion easier by clearing up the mess...

Since I renamed the datagouv object type superclasses, maybe {add,remove}_records would be clearer? (I'm not 100% sure "record" is the perfect term, but at least we'll be consistent)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels non-standard for the bulk of the real testing (ie assertions) to happen in the fixture rather than in the tests themselves. But I guess we don't have a simple way to move the assertions here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels the same to me, but I don't see how I can work around the fact that responses constructs bundle mocks and assertions together. Moving the few direct asserts (those testing the organizations-*.json files) IMO would make it even less clear since you'd have those in the main test (to be redefined each time) but the responses-related asserts would be hidden.

So I don't see how we could have the usual clear-cut test sequence of 1) declare objects and mocks 2) call function under test 3) declare asserts.

Maybe renaming run_mock_feed to something like mock_feed_and_assert or can help? I initially wanted to use test_feed but that would be too confusing with all the test_ functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants