Skip to content

Document properties as attributes#7068

Closed
flying-sheep wants to merge 28 commits intosphinx-doc:masterfrom
flying-sheep:patch-1
Closed

Document properties as attributes#7068
flying-sheep wants to merge 28 commits intosphinx-doc:masterfrom
flying-sheep:patch-1

Conversation

@flying-sheep
Copy link
Copy Markdown
Contributor

@flying-sheep flying-sheep commented Jan 27, 2020

Subject: Properties are more like attributes than they’re like methods.

Feature or Bugfix

  • Bugfix

Purpose

Fixes regression introduced by #6320 which resulted in properties being filed in as std:method instead of std:attribute and therefore people’s :py:attr: links to properties breaking.

Detail

A method is a descriptor that returns a bound method on __get__ (a bound method is always callable, with the first argument fixed to the instance).

A property is a descriptor that directly returns a value that’s usually not callable on __get__.

Therefore it makes no sense to view properties as “methods”. Besides, as said, #6320 broke links in many many projects.

You already list them as attributes in generate_autosummary_content too.

Relates

@flying-sheep flying-sheep requested a review from tk0miya January 27, 2020 11:28
@flying-sheep
Copy link
Copy Markdown
Contributor Author

Hi @tk0miya, my links are degrading more and more as other projects start compiling their docs with the buggy release. Could you please merge this soon and make a release?

@tk0miya
Copy link
Copy Markdown
Member

tk0miya commented Feb 5, 2020

@flying-sheep I think this changes policy of python domain. So we have to design how to mark up whole of python object. So I can't merge this soon. I believe #7061 is a workaround to avoid the broken references.

@flying-sheep
Copy link
Copy Markdown
Contributor Author

OK, no problem, #7061 circumvents the problem well enough for the time being.

I’m happy to work on this PR in the meantime until it fullfills your requirements 😃

@flying-sheep
Copy link
Copy Markdown
Contributor Author

flying-sheep commented Feb 22, 2020

Let’s continue the discussion here. I agree about :abstractproperty: being weird, so I think we have three options, right? A) simplicity B) familiarity C) Just B but with py:property

I’m not sure what we should go with, I think the first two of them have balanced pros and cons:

A) IMHO the following would be the best and simplest option if sphinx was designed that way from the start. It just pains me to see :XXXmethod: thrice and then :async:. So inconsistent! We already have the directives py:staticmethod and py:classmethod, so if we change a lot anyway, we can deprecate the corresponding options in favor of those two directives. Since instance methods, class methods, and static methods are mutually exclusive, this would make things much simpler!

But it would confuse the hell out of people accustomed to the old way of doing it! On the other hand just deprecating the old way for years to come is possible.

.. py:method:: hello()
   :abstract:
   :async:
   :return:
   :rtype:
.. py:classmethod:: hello()
   ...
.. py:staticmethod:: hello()
   ...

.. py:attribute:: name
   :abstract:
   :property:
   :type:
   :value:

B) If we decide to stay with :abstractmethod: and stay with py:attribute, we have a good package with little change: Everything but :abstractmethod: reflects how things are used and what all options that are introduced by a decorator match the decorator’s name. I think we gain little by going with a dedicated py:property if we also stay with :abstractmethod:. Properties and attributes are too similar in practice and share all other options.

.. py:method:: hello()
   :abstractmethod:
   :staticmethod:
   :classmethod:
   :async:
   :return:
   :rtype:

.. py:attribute:: name
   :abstractmethod:
   :property:
   :type:
   :value:

@tk0miya
Copy link
Copy Markdown
Member

tk0miya commented Feb 22, 2020

At this moment, I vote to plan C.

About A)

It just pains me to see :XXXmethod: thrice and then :async:. So inconsistent!

I don't think so. We usually see XXXmethod decorators and asyc keyword in python code. I believe writer of python docs must be a python programmer. So it is not inconsistent with python.

To me, a characteristic of plan A is to use simple keywords to describe their behavior: abstract, async and etc. But they are different with keywords in python, Sphinx originals. IMO, this plan is not easy to understand. I prefer to learn less keywords. This is why I hesitate to choose plan A.

About B)

Everything but :abstractmethod: reflects how things are used and what all options that are introduced by a decorator match the decorator’s name.

I think abstractmethod is also same as python's decorator. AFAIK, we usually use abc.abstractmethod decorator to make an abstract method, right?

Properties and attributes are too similar in practice and share all other options.

I think they can take different options. A property can take abstract (or abstractmethod), async and type options. On the other hand, an attribute can take only type and value options (It would be able to take :instance: option to describe it is an instance attribute in future). This is why I proposed py:property.

property attribute notes
async Y N Accurately, this is not allowed in official syntax. We can do it with async-property
abstract Y N
type Y Y
value N Y
instance? N Y Not implemented yet. But there are some requests to describe class attributes and instance attributes.

@flying-sheep
Copy link
Copy Markdown
Contributor Author

flying-sheep commented Feb 22, 2020

One counter argument is that properties must end up as py:attribute in objects.inv:

Say people have links like :attr:`mod_a.TestClass.test` . mod_a’s author decides to convert it from attribute to property. This shouldn’t have any impact on objects.inv and therefore the fact that people are referring to it via :attr: links.

And if the correct way to refer to attributes and properties is via :attr:, I think having py:property and py:attribute is a bit confusing: People will try to refer to them via :prop: or so which shouldn’t and won’t ever exist.

@tk0miya
Copy link
Copy Markdown
Member

tk0miya commented Feb 22, 2020

One counter argument is that properties must end up as py:attribute in objects.inv:

Why? If you worry about referencing role, type in object.inv is not related with it. As we did in 2.4 release, we can refer py:attribute entry via :py:attr: role (with some hacks). I don't think its structure is not a blocker for moving py:property.

I think providing new role :py:prop: as a formal way to refer properties. And :py:attr: is also allowed to refer properties as in the past. It may seem inconsistent to users, but I think it is useful.

(Note: please post a new comment if you rewrite it. Updates are not emailed.)

@flying-sheep
Copy link
Copy Markdown
Contributor Author

As said: Moving an API from property to attribute or the other way is a transparent act, that consumers of the API won’t notice. If Sphinx makes links break or behave in any way different beween attributes and properties, then Sphinx breaks this guarantee. Therefore :prop: shouldn’t exist.

@tk0miya
Copy link
Copy Markdown
Member

tk0miya commented Feb 23, 2020

Sorry, I'm confused. Adding :prop: role does not break links of :attr: role. I think there are no breaking changes for users. Users can continue to use :attr: role to refer both attributes and properties. What problem do you think?

@flying-sheep
Copy link
Copy Markdown
Contributor Author

OK, so you basically just want to introduce the prop role as an individually stylable link to attributes and properties – in case someone wants to style links to properties differently than links to attributes?

@flying-sheep
Copy link
Copy Markdown
Contributor Author

Okay, done!

Copy link
Copy Markdown
Member

@tk0miya tk0miya left a comment

Choose a reason for hiding this comment

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

Thank you for update. But it seems there are misunderstanding between us... I still don't understand what you meant API is.

@flying-sheep
Copy link
Copy Markdown
Contributor Author

The remaining mypy failures are not mine. No idea why they are there.

@flying-sheep
Copy link
Copy Markdown
Contributor Author

Haha me neither. Let’s try this: My motivation is that when a developer changes attribute to a property or the other way round:

  • autosummary should generate the correct directive, with .. py:property:: displaying different information than .. py:attribute::
  • the consumer/user shouldn’t have to change anything, and the links should look and work the same

Therefore I propose that

  • properties and attributes should be under the same header when autosummary documents a class:

    Attributes and properties

    some_propProperty documentation
    some_attrAttribute documentation
  • The different link roles should have a purpose or be removed. If people can refer to the same thing interchangably via :prop:, :meth:, :attr: and maybe more, I don’t understand why we have them. Why not deprecate them all and just use :obj:? Am I missing something?

  • Links to properties and attributes should render the same and be customizable together using the CSS class py-attr:

    • :obj:`Test.meth` → “Test.meth()” (with one of its CSS classes being py-func)
    • :obj:`Test.prop_or_attr` → “Test.prop_or_attr” (with both properties and attributes having the CSS class py-attr)

@tk0miya
Copy link
Copy Markdown
Member

tk0miya commented Mar 21, 2020

properties and attributes should be under the same header when autosummary documents a class:

I prefer your idea. It's nice for the default settings of autosummary. But it does not mean they are always grouped. It would be better that users can separate attributes and properties to different lists (using customized templates or other methods).

The different link roles should have a purpose or be removed. If people can refer to the same thing interchangably via :prop:, :meth:, :attr: and maybe more, I don’t understand why we have them. Why not deprecate them all and just use :obj:? Am I missing something?

It's a difficult question. Surely, :py:obj: will match all kinds of objects; a class, a method, an attribute, a property and anything else. In my understanding, Sphinx provides cross-reference roles for each object type to refer to a python object explicitly. The idea might be based on Zen of python; it says "Explicit is better than implicit". If not so, it is better to use :any: role for all references.

And I think directives and roles should be a pair (except :py:obj:). For example, the object described by py:attribute:: directive can be referred by :py:attr: role. So I need to add :py:prop: role.

Note: As discussed above, I agree to refer properties via :py:attr: role.

Links to properties and attributes should render the same and be customizable together using the CSS class py-attr:

-1. I don't think so because they are different things.

I think this is a root cause of this discussion. I consider they are different. And you consider they are same. I think both are correct. It's only different perspective. But it is difficult to get agreed.

@flying-sheep
Copy link
Copy Markdown
Contributor Author

flying-sheep commented Mar 21, 2020

It's nice for the default settings of autosummary. […] It would be better that users can separate attributes and properties to different lists

great, let’s do it that way then!

I think directives and roles should be a pair (except :py:obj:).

If we have a link role for most directives, it is only consistent to have one for every directive. I agree. My issue with having both is that if they’d do the same, they’d be purely cosmetic, see the next point:

In my understanding, Sphinx provides cross-reference roles for each object type to refer to a python object explicitly. The idea might be based on Zen of python; it says "Explicit is better than implicit".

I think that’s a great principle, but I think it refers to things with actual semantics attached to it, i.e. different behavior. If two or more things do exactly the same, then another principle applies: “There should be one – and preferably only one – obvious way to do it”

I consider they are different. And you consider they are same.

I don’t. They’re clearly different in implementation. I only consider them to be the same from a user/consumer perspective, i.e. for someone accessing an instance attribute/property (test_instace.thing) or linking to it (:any:`Test.thing` ).

Can we agree to give property links the py-attr and py-prop CSS classes? Then existing themes still work and people can still style them differently if they want.

If you really want both roles, there’s only one question left: should a :attr: link create a element with the py-prop class if the object is a property? It would make sense that the link reflects what it really links to if you really want the roles to be cosmetic.

@tk0miya
Copy link
Copy Markdown
Member

tk0miya commented Mar 21, 2020

I consider they are different. And you consider they are same.

I don’t. They’re clearly different in implementation. I only consider them to be the same from a user/consumer perspective, i.e. for someone accessing an instance attribute/property (test_instace.thing) or linking to it (:any:Test.thing ).

Sorry. It's my lack of words. But nobody object to they're same in implementation if they knows python. So this is a concern about documentation (in Sphinx). I believe they are different (but similar). And you're thinking they should be the same, right?

I think how to describe (author side) and how to represent (user/consumer side) is strongly coupled. So, based on user/consumer perspective you said, attributes and properties should be described as same in my idea. It means we should use py:attribute:: directive to describe a propery. No py:property directive is needed.

I think that’s a great principle, but I think it refers to things with actual semantics attached to it, i.e. different behavior. If two or more things do exactly the same, then another principle applies: “There should be one – and preferably only one – obvious way to do it”

The another principle does not match this case because they are different.

Can we agree to give property links the py-attr and py-prop CSS classes? Then existing themes still work and people can still style them differently if they want.

Agreed. I think they are similar. So no reason to object that.

If you really want both roles, there’s only one question left: should a :attr: link create a element with the py-prop class if the object is a property? It would make sense that the link reflects what it really links to if you really want the roles to be cosmetic.

Hmm... it's a difficult question. +0 for the idea if we can keep consistent. Now we can refer a class entry via :py:attr: role. But the reference is rendered as an element only with the py-attr. We need to refactor such (a bit broken) mechanism to realize the feature.

@flying-sheep
Copy link
Copy Markdown
Contributor Author

flying-sheep commented Mar 21, 2020

So this is a concern about documentation (in Sphinx). I believe they are different (but similar). And you're thinking they should be the same, right?

Documentation referring to them should be the same because using them from code is the same and the implementation can be transparently switched at any moment (i.e. without breaking anyone’s code).

E.g.:

  1. I use library testlib v1.0.0, which implements and documents a class Test.
  2. I use the attribute foo = testlib.Test().thing in my code.
  3. I link to the attribute via :attr:`Test.thing` in my code’s documentation
  4. The author of the Test class switches the implementation to a property and releases testlib v1.0.1 (i.e. no breaking changes, whatever the property does won’t affect my code)
  5. I expect my code to work exactly the same and I want my documentation to look and build exactly the same.

I don’t understand why in my use case my link should suddenly gain a py-prop class, or the “more correct” way to refer to Test.thing is now through :prop:, as nothing at all should change on my side.

@tk0miya
Copy link
Copy Markdown
Member

tk0miya commented Mar 21, 2020

I don’t understand why in my use case my link should suddenly gain a py-prop class, or the “more correct” way to refer to Test.thing is now through :prop:, as nothing at should change on my side.

What these expectations came from? About CSS rule was just proposed at above comment. Nothing determined. And who determines "more correct" way? I think both :py:prop: and :py:attr: are correct. There are no need to change their document that refers properties via :py:attr: role.

@flying-sheep
Copy link
Copy Markdown
Contributor Author

If there’s no correct way, there’s no reason to have two roles for the same job, no?

@tk0miya
Copy link
Copy Markdown
Member

tk0miya commented Mar 21, 2020

My answer is always same. Because they are different.

@shimizukawa
Copy link
Copy Markdown
Member

It's been a month since I've commented. I've read the discussions over the past month and my opinion has not changed.
As a document author, it's more intuitive to use the terminology of Python itself. For this reason, it is not uncomfortable to represent a property as a kind of method. (This is an answer to #7068 (comment)).

... py:method:: color
  :abstractmethod:
  :property:

  Describing string

  :return: color value
  :rtype: Color

While documentation should be written for the reader, I believe it is important to be able to directly describe the representation of the implementation in order to accurately convey the information that the reader expects. In that sense, it is not appropriate for a reader to see the property as py:attribute or its derivative. It is difficult for readers to understand that dynamic processing is performed when getting and assigning property values. If the implementation expression is py:method:: :property:, there is no difference between the document writer and the reader. In other words, I think we should treat ATTRIBUTE and PROPERTY as distinctly different things for the reader.

The problem of the above sample code is that it shows () because it treats color as a method, and this needs to be fixed.

I think merging the implementation of #7298 would "directly describe the representation of the implementation and allow the reader to get the information without misunderstanding". It also solves the problem of adding (), and you can use the :abstractmethod: option. The above sample code can now be described as follows

... py:property:: color
  :abstractmethod:
  :type: Color

  color value blah blah blah blah ...

My opinion summary:

@flying-sheep
Copy link
Copy Markdown
Contributor Author

OK, I’ll review it.

tk0miya added a commit that referenced this pull request Mar 13, 2021
py domain: Add py:property directive to describe a property (refs: #7068)
@flying-sheep flying-sheep deleted the patch-1 branch March 14, 2021 13:44
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

domains:py type:proposal a feature suggestion

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PropertyDocumenter causes inconsistent handling of Python descriptors

3 participants