gitea.utils.fields
fields
The field names this library hands back, and the names it still answers to.
Every payload a client method returns is the JSON the Gitea API sent, keyed as
the API keys it. Nothing here renames a field and nothing drops one: a project
column is title because that is what /projects/{id}/columns answers with, so
a caller reading a payload looks the key up in Gitea's own API reference rather
than in this library's source.
That rule is written down rather than left as a habit because breaking it is
invisible from the outside. A wrapper that renames one field of one resource -
title to name, because a column reads more naturally as having a name - is
indistinguishable, to the caller, from an API that spells it that way. The
caller writes column["name"], it works, and it breaks on the version that
notices the rename and removes it. Worse, a rename applied in one path and not
the other - in a CLI command that rebuilds a payload but not in the client
method feeding it - makes the same value arrive under two names depending on
which door it came through, which is the shape this was reported in: a caller
reading a column by one name in one place and the other name in the other.
So the canonical name is the API's, everywhere, in the client dictionaries and in the CLI's JSON envelope alike. What this module adds is the other half of that: a name callers already write can go on being read without becoming a second spelling of the field.
A field the API does not send may still be added, where it carries something
the API has no way of saying: column_id on the project entries of an issue is
resolved from the board, because the issue payload names the projects without
saying where on them its cards sit. Such a field is documented where the command
adding it is documented, and it is never another name for a field already in the
payload - which is the line between filling a gap and inventing a synonym.
Aliases
An alias is a name a payload can be read by. It is not a key: it is absent
from keys(), from iteration, from len(), and therefore from anything that
serializes the payload, so the JSON the CLI prints carries the canonical name
alone and no consumer can come to depend on the alias by reading the output.
AliasedDict is otherwise a dict - it compares equal to the plain dictionary
of the same items, and unpacking one with {**payload} gives that plain
dictionary back, alias-free.
Only reading is widened. Writing through an alias is not aliased either way:
payload["name"] = x sets a field named name, as it would on any dictionary,
and pop("name") and del payload["name"] raise unless such a field is really
there. Resolving a write to the canonical field would be the more surprising
rule - it would let column["name"] = x change the title of a column through a
name Gitea does not use - and a payload a caller has written a literal name
into is a payload the caller built, not one this library handed over.
The bar for adding an entry is a name callers have actually written, not a name
that reads well: every alias is a second way to spell one field, which is the
thing this module exists to prevent. The one recorded today is name on a
project column, because that is what code written against this library reached
for first.
Classes
gitea.utils.fields.AliasedDict
Bases: dict[str, Any]
An API payload readable by its canonical field names and by their aliases.
Reading is widened and nothing else is: payload["name"],
payload.get("name") and "name" in payload all resolve an alias to the
field it aliases, while keys(), iteration, len() and every serialization
see the canonical fields alone. Writing is not aliased: an alias assigned to
becomes a field of that name, and one deleted or popped has to be one.
Subclasses declare the aliases of one resource. A subclass declaring none behaves as a plain dictionary, which is what makes this usable as the base of a record type before anyone has needed an alias for it.
Methods:
gitea.utils.fields.AliasedDict.get
Read a field or an alias of one, falling back to a default.
dict.get never reaches __missing__, so it is widened here for the
same reason __getitem__ did not have to be: a caller reading an alias
defensively is the same caller.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
Any
|
The canonical field name, or an alias of one. |
required |
default
|
Any
|
What to answer when the payload carries neither. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The value of the field, or |
Source code in src/gitea/utils/fields.py
gitea.utils.fields.AliasedDict.copy
Copy the payload, keeping the aliases readable.
dict.copy answers with a plain dictionary, which would quietly drop
the aliases from the copy of a payload that had them.
Returns:
| Type | Description |
|---|---|
Self
|
A shallow copy of the same record type. |
Source code in src/gitea/utils/fields.py
gitea.utils.fields.ProjectColumn
Bases: AliasedDict
A project column as the API returns it.
Gitea names a column with title. name reads it too, because that is the
key code written against this library reached for before the convention was
recorded, and breaking it would buy nothing.
Functions:
gitea.utils.fields.as_records
Wrap the objects of a payload in the record type carrying their aliases.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The payload a client method is about to return: one object, a listing of them, or whatever an empty or unparsable response left in its place. |
required |
record
|
type[AliasedDict]
|
The record type of the resource the payload describes. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The payload with each object in it wrapped in |
Any
|
not an object is handed back untouched, so a body that did not come back |
Any
|
in the endpoint's shape reaches the caller as it arrived rather than |
Any
|
being turned into an error by the wrapping of it. |